[从零开始]使用Vue-cli制作俄罗斯方块小游戏(三)游戏逻辑实现

目录在此(点我)。

STEP ONE:定义相关游戏变量

好了,让我们打开GameCanvas文件,在data中加入如下变量。

NO_BLOCK: 0,
HAVE_BLOCK: 1,
tetris_status: null,
currentFall: null

其中,tetris_status是存放地图上方块有无的数组,NO_BLOCK是没有方块的状态,HAVE_BLOCK是有方块的状态。如果你想设计五颜六色的 方块,可以添加相应的变量,我们这里为了简单起见,只有一种颜色的方块。currentFall存储正在掉落的方块。

然后再用computed定义我们的几种方块。

computed: {
    // 定义几种可能出现的方块组合
    blockArr: function () {
      return [
      // Z
        [
          {x: this.TETRIS_COLS / 2 - 1, y: 0},
          {x: this.TETRIS_COLS / 2, y: 0},
          {x: this.TETRIS_COLS / 2, y: 1},
          {x: this.TETRIS_COLS / 2 + 1, y: 1}
        ],
        // 反Z
        [
          {x: this.TETRIS_COLS / 2 + 1, y: 0},
          {x: this.TETRIS_COLS / 2, y: 0},
          {x: this.TETRIS_COLS / 2, y: 1},
          {x: this.TETRIS_COLS / 2 - 1, y: 1}
        ],
        //
        [
          {x: this.TETRIS_COLS / 2 - 1, y: 0},
          {x: this.TETRIS_COLS / 2, y: 0},
          {x: this.TETRIS_COLS / 2 - 1, y: 1},
          {x: this.TETRIS_COLS / 2, y: 1}
        ],
        // L
        [
          {x: this.TETRIS_COLS / 2 - 1, y: 0},
          {x: this.TETRIS_COLS / 2 - 1, y: 1},
          {x: this.TETRIS_COLS / 2 - 1, y: 2},
          {x: this.TETRIS_COLS / 2, y: 2}
        ],
        // J
        [
          {x: this.TETRIS_COLS / 2, y: 0},
          {x: this.TETRIS_COLS / 2, y: 1},
          {x: this.TETRIS_COLS / 2, y: 2},
          {x: this.TETRIS_COLS / 2 - 1, y: 2}
        ],
        // □□□□
        [
          {x: this.TETRIS_COLS / 2, y: 0},
          {x: this.TETRIS_COLS / 2, y: 1},
          {x: this.TETRIS_COLS / 2, y: 2},
          {x: this.TETRIS_COLS / 2, y: 3}
        ],
        //
        [
          {x: this.TETRIS_COLS / 2, y: 0},
          {x: this.TETRIS_COLS / 2 - 1, y: 1},
          {x: this.TETRIS_COLS / 2, y: 1},
          {x: this.TETRIS_COLS / 2 + 1, y: 1}
        ]
      ]
    }
  }

这样我们新生成的方块就能在地图最顶上的中间出现。

定义了变量,接下来让我们初始化他们。

在mounted()方法里,我们在最后加上这一句:this.newGame()

然后我们在methods里定义newGame()


 newGame () {
      // 数据初始化
      this.initData()
    }
initData () {
      this.tetris_status = []
      for (let i = 0; i < this.TETRIS_ROWS; i++) {
        this.tetris_status[i] = []
        for (let j = 0; j < this.TETRIS_COLS; j++) {
          this.tetris_status[i][j] = this.NO_BLOCK
        }
      }
    }

 以上。

STEP TWO:方块生成。

首先,每次刷新都要重新绘制方块的位置,我们将其放在drawBlocks里面。

drawBlocks () {
      // 清空地图
      for (let i = 0; i < this.TETRIS_ROWS; i++) {
        for (let j = 0; j < this.TETRIS_COLS; j++) {
          this.canvasCtx.clearRect(j * this.CELL_SIZE + 1, i * this.CELL_SIZE + 1, this.CELL_SIZE - 2, this.CELL_SIZE - 2)
        }
      }
      // 绘制地图
      for (let i = 0; i < this.TETRIS_ROWS; i++) {
        for (let j = 0; j < this.TETRIS_COLS; j++) {
          if (this.tetris_status[i][j] !== this.NO_BLOCK) {
            this.canvasCtx.fillRect(j * this.CELL_SIZE + 1, i * this.CELL_SIZE + 1, this.CELL_SIZE - 2, this.CELL_SIZE - 2)// 中间留点缝隙
          }
        }
      }
      // 绘制currentFall
      for (let i = 0; i < this.currentFall.length; i++) {
        this.canvasCtx.fillRect(this.currentFall[i].x * this.CELL_SIZE + 1, this.currentFall[i].y * this.CELL_SIZE + 1, this.CELL_SIZE - 2, this.CELL_SIZE - 2)
      }
    }

 现在方块可以绘制了。但——没有方块啊!

那就创建方块!

    createBlock () {
      var rand = Math.floor(Math.random() * this.blockArr.length)
      // 随机生成正在下掉的方块
      this.currentFall = [
        {x: this.blockArr[rand][0].x, y: this.blockArr[rand][0].y},
        {x: this.blockArr[rand][1].x, y: this.blockArr[rand][1].y},
        {x: this.blockArr[rand][2].x, y: this.blockArr[rand][2].y},
        {x: this.blockArr[rand][3].x, y: this.blockArr[rand][3].y}
      ]
        this.drawBlocks()
    }

 然后我们先做个小测试。

往我们的newGame()里添加

 this.createBlock()
 this.drawBlocks()

然后打开我们的localhost:8080

好了,我们的方块生成也成功了!

STEP THREE:编写游戏逻辑

方块有了,是时候让他们动起来了 !

// judge can move down and if touch others or arrive at the bottom then return true, else return false
    moveDown () {
      for (let i = 0; i < this.currentFall.length; i++) {
        if (this.currentFall[i].y >= this.TETRIS_ROWS - 1 || this.tetris_status[this.currentFall[i].y + 1][this.currentFall[i].x] !== this.NO_BLOCK) {
          // 记录block
          for (let i = 0; i < this.currentFall.length; i++) {
            this.tetris_status[this.currentFall[i].y][this.currentFall[i].x] = this.HAVE_BLOCK
          }
          // 判断有没有满行的
          for (let j = 0; j < this.currentFall.length; j++) {
            for (let i = 0; i < this.TETRIS_COLS; i++) {
              if (this.tetris_status[this.currentFall[j].y][i] === this.NO_BLOCK) {
                break
              }
              // 最后一行满了
              if (i === this.TETRIS_COLS - 1) {
                // 消除最后一行
                for (let i = this.currentFall[j].y; i > 0; i--) {
                  for (let j = 0; j < this.TETRIS_COLS; j++) {
                    this.tetris_status[i][j] = this.tetris_status[i - 1][j]
                  }
                }
              }
            }
          }
          return true
        }
      }
      for (let i = 0; i < this.currentFall.length; i++) {
        this.currentFall[i].y += 1
      }
      this.drawBlocks()
      return false
    },
    // 右移
    moveRight () {
      for (let i = 0; i < this.currentFall.length; i++) {
        if (this.currentFall[i].x + 1 >= this.TETRIS_ROWS || this.tetris_status[this.currentFall[i].y][this.currentFall[i].x + 1] !== this.NO_BLOCK) {
          return
        }
      }
      for (let i = 0; i < this.currentFall.length; i++) {
        this.currentFall[i].x++
      }
      this.drawBlocks()
    },
    // 左移
    moveLeft () {
      for (let i = 0; i < this.currentFall.length; i++) {
        if (this.currentFall[i].x < 1 || this.tetris_status[this.currentFall[i].y][this.currentFall[i].x - 1] !== this.NO_BLOCK) {
          return
        }
      }
      for (let i = 0; i < this.currentFall.length; i++) {
        this.currentFall[i].x--
      }
      this.drawBlocks()
    },
    // 旋转
    rotate () {
      // 定义记录能否旋转的旗标
      var canRotate = true
      for (var i = 0; i < this.currentFall.length; i++) {
        var preX = this.currentFall[i].x
        var preY = this.currentFall[i].y
        // 始终以第三个方块作为旋转的中心,
        // i == 2时,说明是旋转的中心
        if (i !== 2) {
          // 计算方块旋转后的x、y坐标
          var afterRotateX = this.currentFall[2].x + preY - this.currentFall[2].y
          var afterRotateY = this.currentFall[2].y + this.currentFall[2].x - preX
          // 如果旋转后所在位置已有方块,表明不能旋转
          if (this.tetris_status[afterRotateY][afterRotateX + 1] !== this.NO_BLOCK) {
            canRotate = false
            break
          }
          // 如果旋转后的坐标已经超出了最左边边界
          if (afterRotateX < 0 || this.tetris_status[afterRotateY - 1][afterRotateX] !== this.NO_BLOCK) {
            this.moveRight()
            afterRotateX = this.currentFall[2].x + preY - this.currentFall[2].y
            afterRotateY = this.currentFall[2].y + this.currentFall[2].x - preX
            break
          }
          if (afterRotateX < 0 || this.tetris_status[afterRotateY - 1][afterRotateX] !== this.NO_BLOCK) {
            this.moveRight()
            break
          }
          // 如果旋转后的坐标已经超出了最右边边界
          if (afterRotateX >= this.TETRIS_COLS - 1 || this.tetris_status[afterRotateY][afterRotateX + 1] !== this.NO_BLOCK) {
            this.moveLeft()
            afterRotateX = this.currentFall[2].x + preY - this.currentFall[2].y
            afterRotateY = this.currentFall[2].y + this.currentFall[2].x - preX
            break
          }
          if (afterRotateX >= this.TETRIS_COLS - 1 || this.tetris_status[afterRotateY][afterRotateX + 1] !== this.NO_BLOCK) {
            this.moveLeft()
            break
          }
        }
      }
      if (canRotate) {
        for (var j = 0; j < this.currentFall.length; j++) {
          preX = this.currentFall[j].x
          preY = this.currentFall[j].y
          if (j !== 2) {
            this.currentFall[j].x = this.currentFall[2].x + preY - this.currentFall[2].y
            this.currentFall[j].y = this.currentFall[2].y + this.currentFall[2].x - preX
          }
        }
        this.drawBlocks()
      }
    }

这四个方法,只有moveDown有点特殊,它有返回值。如果碰到了别的方块的顶部,或者到了最底下,那么就返回true,否则,返回false。

然后我们就可以封装一个方法next。

为了让GameCanvas仅实现游戏地图的绘制,我们将定时下移的方法定义在Index.vue里。

 1 next () { // 方块下移
 2       if (this.$refs.gameCanvas.moveDown()) {
 3         // 判断是否触顶
 4         for (let i = 0; i < this.$refs.gameCanvas.currentFall.length; i++) {
 5           if (this.$refs.gameCanvas.currentFall[i].y === 0) {
 6             // gameEnd()
 7             return
 8           }
 9         }
10         // 新的block
11         this.$refs.gameCanvas.createBlock()
12       }
13     }

在第二行里我们调用了gameCanvas里的moveDown()。

为了实现这一点,我们要对Index.vue里的html代码做一点修改。

将<game-canvas></game-canvas>改为<game-canvas ref="gameCanvas"></game-canvas>

这样就好了!

然后让我们在这个文件里也创建一个newGame方法。

newGame () {
      this.curScore = 0
      this.curSpeed = 1
      this.$refs.gameCanvas.newGame()// 调用gameCanvas里的newGame
    }

同时在mounted里调用它。

mounted () {
    this.newGame()
  }

然后让我们把gameCanvas里mounted里的this.newGame()删掉。

这样我们的newGame方法就封装好了。之后新建游戏就只需要调用Index里的newGame了!

最后,我们可以开始编写方向键移动了。

在mounted方法里,我们绑定事件监听。

document.onkeydown = ({ keyCode }) => {
      switch (keyCode) {
        // 向下
        case 40://
        case 83:// S
          this.next()
          break
        // 向左
        case 37://
        case 65:// A
          this.$refs.gameCanvas.moveLeft()
          break
        // 向右
        case 39://
        case 68:// D
          this.$refs.gameCanvas.moveRight()
          break
        // 旋转
        case 38://
        case 87:// W
          this.$refs.gameCanvas.rotate()
          break
      }
    }

ok!

只剩定时下移了。

让我们在data里定义一个timer,并在newGame里为他赋值。

this.timer = setInterval(() => this.next(), 500 / this.speed)
搞定,可以开玩了!

 详细代码我已经传到github上了,大家有兴趣的可以去看一下。

PS:我弟今天玩的时候发现出了一个bug:会在按下的时候方块会重叠。不过是偶然出现,目前仍未找到原因。大家如果有什么想法的可以在下面评论,或者发邮件到1179713734@qq.com

原文地址:https://www.cnblogs.com/zyyz1126/p/12258889.html