4

跟着小白一起学鸿蒙—一起学做Tetris(上)

 1 year ago
source link: https://www.51cto.com/article/740939.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

跟着小白一起学鸿蒙—一起学做Tetris(上)

作者:王石 2022-11-29 16:35:02
小时候有个游戏叫俄罗斯方块,大人小孩都喜欢玩,我们就一起看看如何能用OpenHarmony学习做个Tetris。
37cbc4166f004e5297f271ab348d98112f965a.png

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

小时候有个游戏叫俄罗斯方块,大人小孩都喜欢玩,我们就一起看看如何能用OpenHarmony学习做个Tetris。

#盲盒+码##跟着小白一起学鸿蒙# [番外三]一起学做Tetris(上)-开源基础软件社区

1、HAP应用建立

《#跟着小白一起学鸿蒙#[六]如何编写一个hap应用》里我们介绍了简单的Hap应用的开发以及基础控件的介绍,这里我们就不赘述Hap项目的建立过程,以下就是基础的Hap的page文件:index.ets。

build() {
    Row() {
      Column() {
        Canvas(this.context)
          .width('100%')
          .height('100%')
          .onClick((ev: ClickEvent) => {
            console.info("click!!")
            this.doClick()
          })
          .onReady(() =>{
            this.context.imageSmoothingEnabled = false
            this.randomType()
            this.drawall()
          })
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor("#cccccc")
  }

build是基础页面的构造函数,用于界面的元素构造,其他的页面的生命周期函数如下:

declare class CustomComponent {
  /**
   * Customize the pop-up content constructor.
   * @since 7
   */
  build(): void;
  /**
   * aboutToAppear Method
   * @since 7
   */
  aboutToAppear?(): void;
  /**
   * aboutToDisappear Method
   * @since 7
   */
  aboutToDisappear?(): void;
  /**
   * onPageShow Method
   * @since 7
   */
  onPageShow?(): void;
  /**
   * onPageHide Method
   * @since 7
   */
  onPageHide?(): void;
  /**
   * onBackPress Method
   * @since 7
   */
  onBackPress?(): void;
}

2、Canvas介绍

canvas是画布组件用于自定义绘制图形,具体的API页面如下:

https://developer.harmonyos.com/cn/docs/documentation/doc-references/ts-components-canvas-canvas-0000001333641081。

页面显示前会调用aboutToAppear()函数,此函数为页面生命周期函数。

canvas组件初始化完毕后会调用onReady()函数,函数内部实现小游戏的初始页面的绘制。

(1)初始化页面数据
drawall() {
    this.drawBox()
    this.drawSideBlock()
    this.drawBoxBlock()
    this.drawScore()
  }

因为都是画布画的,所以布局有点麻烦,需要画几个部分:

  • 中间的大框:方块下落和堆叠区域。
  • 右边提升框:下个方块类型。
  • 中间方块:方块运动和堆叠。
  • 下方计分:行数得分。
(2)绘制大框
drawBox() {
    this.context.lineWidth = 4
    this.context.beginPath()
    this.context.lineCap = 'butt'
    this.context.moveTo(0, 100)
    this.context.lineTo(270, 100)
    this.context.moveTo(270, 100)
    this.context.lineTo(270, 690)
    this.context.moveTo(0, 690)
    this.context.lineTo(270, 690)
  }
(3)绘制提示方块
drawSideBlock() {
    this.context.fillStyle = 'rgb(250,0,0)'
    let bs = this.blockSize
    let coords = this.blockShapBasic[this.blockType]
    let x = this.sideStartX + coords[0][0]*this.blockSize
    let y = this.sideStartY + coords[0][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.sideStartX + coords[1][0]*this.blockSize
    y = this.sideStartY + coords[1][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.sideStartX + coords[2][0]*this.blockSize
    y = this.sideStartY + coords[2][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.sideStartX + coords[3][0]*this.blockSize
    y = this.sideStartY + coords[3][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    this.context.stroke()
  }
(4)绘制运动方块
drawBoxBlock() {
  this.setDirection()
  this.context.fillStyle = 'rgb(250,0,0)'
  let bs = this.blockSize
  let coords = this.curBlockShap
  let starty = this.slotStartY + this.step * this.blockSize
  let x = this.slotStartX + coords[0][0]*this.blockSize
  let y = starty + coords[0][1]*this.blockSize
  this.context.fillRect(x, y, bs, bs)
  this.context.rect(x, y, bs, bs)
  console.info("x,y"+x.toString()+":"+y.toString())
  x = this.slotStartX + coords[1][0]*this.blockSize
  y = starty + coords[1][1]*this.blockSize
  this.context.fillRect(x, y, bs, bs)
  this.context.rect(x, y, bs, bs)
  console.info("x,y"+x.toString()+":"+y.toString())
  x = this.slotStartX + coords[2][0]*this.blockSize
  y = starty + coords[2][1]*this.blockSize
  this.context.fillRect(x, y, bs, bs)
  this.context.rect(x, y, bs, bs)
  console.info("x,y"+x.toString()+":"+y.toString())
  x = this.slotStartX + coords[3][0]*this.blockSize
  y = starty + coords[3][1]*this.blockSize
  this.context.fillRect(x, y, bs, bs)
  this.context.rect(x, y, bs, bs)
  console.info("x,y"+x.toString()+":"+y.toString())
  this.context.stroke()
  this.slotBottomY = y
}
(5)绘制得分区域
drawScore() {
    this.context.fillStyle = 'rgb(0,0,0)'
    this.context.font = '80px sans-serif'
    this.context.fillText("Score:"+this.score.toString(), 20, 740)
  }

3、游戏逻辑

简单的小游戏主体游戏逻辑为:等待开始,开始,结束流程图如下:

graph LR
timer开始 --> 方块下落
timer开始 --> click[点击]
click[点击] --> 方块变形
方块下落 --> |落到底| 能消除 --> 计分 --> 堆积
方块下落 --> |落到底| 不能消除 --> 堆积
堆积 --> |堆积到顶| 满了 --> 游戏结束
堆积 --> |堆积到顶| 未满 --> 方块下落
doClick() {
    this.direction += 1
  }

4、完整逻辑

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
  private blockType: number = 0
  private blockSize: number = 30
  private blockShapBasic = [
    [[0,0],[0,1],[0,2],[0,3]],
    [[0,0],[0,1],[0,2],[1,2]],
    [[0,0],[0,1],[1,1],[0,2]],
    [[0,0],[0,1],[1,1],[1,2]],
    [[0,0],[0,1],[1,0],[1,1]],
  ]
  private blockShap = [
    [[0,0],[0,1],[0,2],[0,3]],
    [[0,0],[0,1],[0,2],[1,2]],
    [[0,0],[0,1],[1,1],[0,2]],
    [[0,0],[0,1],[1,1],[1,2]],
    [[0,0],[0,1],[1,0],[1,1]],
  ]
  private curBlockShap = []
  private sideStartX = 300;
  private sideStartY = 150;
  private slotStartX = 120;
  private slotStartY = 150;
  private slotBottomY = 150;;
  private score = 0;
  private step = 0;
  private direction = 0;
  aboutToDisappear() {
  }
  aboutToAppear() {
    this.sleep(1000)
  }
  async sleep(ms: number) {
    return new Promise((r) => {
      setInterval(() => {
        console.log(this.message)
        this.drawStep()
      }, ms)
    })
  }
  doClick() {
    this.direction += 1
  }
  drawBox() {
    this.context.lineWidth = 4
    this.context.beginPath()
    this.context.lineCap = 'butt'
    this.context.moveTo(0, 100)
    this.context.lineTo(270, 100)
    this.context.moveTo(270, 100)
    this.context.lineTo(270, 690)
    this.context.moveTo(0, 690)
    this.context.lineTo(270, 690)
  }
  setDirection() {
    this.curBlockShap = this.blockShap[this.blockType]
    if (this.direction > 0) {
      for (let i=0;i<4;i++) {
        let x = this.curBlockShap[i][0]
        this.curBlockShap[i][0] = this.curBlockShap[i][1]
        this.curBlockShap[i][1] = x
      }
      this.direction = 0
    }
  }
  drawSideBlock() {
    this.context.fillStyle = 'rgb(250,0,0)'
    let bs = this.blockSize
    let coords = this.blockShapBasic[this.blockType]
    let x = this.sideStartX + coords[0][0]*this.blockSize
    let y = this.sideStartY + coords[0][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.sideStartX + coords[1][0]*this.blockSize
    y = this.sideStartY + coords[1][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.sideStartX + coords[2][0]*this.blockSize
    y = this.sideStartY + coords[2][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.sideStartX + coords[3][0]*this.blockSize
    y = this.sideStartY + coords[3][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    this.context.stroke()
  }
  drawBoxBlock() {
    this.setDirection()
    this.context.fillStyle = 'rgb(250,0,0)'
    let bs = this.blockSize
    let coords = this.curBlockShap
    let starty = this.slotStartY + this.step * this.blockSize
    let x = this.slotStartX + coords[0][0]*this.blockSize
    let y = starty + coords[0][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.slotStartX + coords[1][0]*this.blockSize
    y = starty + coords[1][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.slotStartX + coords[2][0]*this.blockSize
    y = starty + coords[2][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    x = this.slotStartX + coords[3][0]*this.blockSize
    y = starty + coords[3][1]*this.blockSize
    this.context.fillRect(x, y, bs, bs)
    this.context.rect(x, y, bs, bs)
    console.info("x,y"+x.toString()+":"+y.toString())
    this.context.stroke()
    this.slotBottomY = y
  }
  drawScore() {
    this.context.fillStyle = 'rgb(0,0,0)'
    this.context.font = '80px sans-serif'
    this.context.fillText("Score:"+this.score.toString(), 20, 740)
  }
  randomType() {
    this.blockType = Math.floor(Math.random()*5)
    console.info("blocktype:"+this.blockType.toString())
  }
  drawStep() {
    this.context.clearRect(0,0,this.context.width,this.context.height)
    this.step += 1
    this.drawBox()
    this.drawSideBlock()
    this.drawBoxBlock()
    this.drawScore()
    if (this.slotBottomY >= 660) {
      this.step = 0
      this.randomType()
    }
  }
  drawall() {
    this.drawBox()
    this.drawSideBlock()
    this.drawBoxBlock()
    this.drawScore()
  }
  build() {
    Row() {
      Column() {
        Canvas(this.context)
          .width('100%')
          .height('100%')
          .onClick((ev: ClickEvent) => {
            console.info("click!!")
            this.doClick()
          })
          .onReady(() =>{
            this.context.imageSmoothingEnabled = false
            this.randomType()
            this.drawall()
          })
      }
      .width('100%')
    }
    .height('100%')
    .backgroundColor("#cccccc")
  }
}

遗留问题:

  1. 没实现堆积计分(接下来会做)。
  2. 可实现网络对战(分布式对战)。

5、获取源码

等游戏完整发布,会有两个版本,单机和联机版本。

本文主要介绍了小游戏的开发,画布功能的使用。

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​​。

责任编辑:jianghua 来源: 51CTO开源基础软件社区

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK