基于Vue3制作简单的消消乐游戏

发布时间:2023-02-28 10:30

目录
  • 游戏介绍
  • 核心思路
  • 核心代码
    • html
    • js
    • 游戏开始/循环
    • 删除
    • 下落
    • 添加
    • 生成矩阵/数据
    • 点击
    • 换位置

游戏介绍

先看一下

\"基于Vue3制作简单的消消乐游戏_第1张图片\"

好吧,我知道界面有点丑 →_→

核心思路

游戏步骤主要就是:消除、下落、补充、移动,采用三种状态来区分需要删除的(remove)、新添加的(add)、和正常的方块(normal)

  • 主要就是生成小方块列表后,马上保存每一个方块上下左右方块的信息
  • 然后判断每一个方块和上下或和左右类型相同即为需要消除,并把该方块状态改为 remove
  • 然后通过定位改变 top 来控制下落,同时要把消除的位置上移,这样补充的时候才能在对应空位上显示,这里专门用了一个矩阵来保存所有对应格子信息,区分出哪些格子是需要消除/补充的
  • 移动就比较简单了,由于每个方块上都保存了自己的上下左右信息,所以只需要交换就行了

有一个坑,就是 key,由于 diff 算法的原因,不需要重新渲染就要保证key是唯一的,比如下落的也重新渲染视觉效果会很奇怪

核心代码

html

以下是矩阵区域所有html,就是用一个div来做的,根据类型给不同类名,然后雪糕全是背景图片

js

js 部分主要是封装了一个类,方便统一管理操作

export default class Stage implements IXXL {
  x: number // x和y 是游戏舞台行列方块个数
  y: number
  size: number // 方块大小
  typeCount = 7 // 方块类型个数
  matrix: Array = [] // 方块矩阵,用于每次消除之后根据矩阵规则生成新的游戏棋盘
  data: Array = [] // 用于渲染页面
  isHandle = false // 游戏是否正在消除/下落/添加处理中
  isSelect = false // 是否有选择
  score = 0 // 分数
  target1: any = { active: false } // 选中的方块
  target2: any = {}
  constructor(x: number, y: number, size: number) {
    this.x = x
    this.y = y
    this.size = size
    this.getMatrix() // 生成矩阵
    this.init(true) // 生成 data 渲染用
  }
  getMatrix(){}
  init(){}
  // 循环执行
  gameLoop(){}
  // 点击
  click(){}
  // 换位
  swap(){}
  // 删除
  remove(){}
  // 下落
  down(){}
  // 补充
  add(){}
}

游戏开始/循环

// 要等动画执行完,所以用 await
async gameLoop(bool: boolean = false) {
    // 结束游戏后重新开始时分数清0
    if (bool) this.score = 0
    // 游戏状态改为正在执行中,控制在动画执行过程中不能点击交换
    this.isHandle = true
    // 找出需要删除的
    await this.remove()
    // 用于检测点击交换后判断有没有需要删除的,没有就再换回来
    let status = this.data.some((item) => item.status === \"remove\")
    // 只要有删除了的,执行上面的下落、补充,补充后再循环找有没有可以删除的
    while (this.data.some((item) => item.status === \"remove\")) {
      await this.down()
      await this.add()
      await this.remove()
    }
    // 所有能删除的删除后,更改状态,然后就可以点击了
    this.isHandle = false
    return status
}

删除

注意 状态为 remove 的实际没有删除,只是页面上看不到了,到补充的时候才会删除掉状态为 remove

// 清除
remove() {
    return new Promise((resolve, reject) => {
      const { data } = this
      data.forEach((item) => {
        const { left, right, top, bottom, type } = item
        // 如果自己 + 自己的左和右 类型都一样,状态变更为删除
        if (left?.type == type && right?.type == type) {
          left.status = \"remove\"
          item.status = \"remove\"
          right.status = \"remove\"
        }
        // 如果自己 + 自己的上和下 类型都一样,状态变更为删除
        if (top?.type == type && bottom?.type == type) {
          top.status = \"remove\"
          item.status = \"remove\"
          bottom.status = \"remove\"
        }
      })
      setTimeout(() => {
        // 执行删除动画,页面上看不到了,并统计分数,实际这时还没删除
        data.forEach((item, index) => {
          if (item.status === \"remove\") {
            item.scale = 0
            this.score += 1
          }
        })
        // 这里延迟100毫秒是首次进页面的时候,先看到格子有东西,不然会是空的
      }, 100)
      // 动画时长500毫秒 css 那边定义了,所以延迟500毫秒
      setTimeout(() => {
        resolve(true)
      }, 500)
    })
}

下落

这里有个坑。除了要把删除格子上面的下落下来之外,还需要把已经删除(状态为删除,页面上看不到了的)的格子上位到,上面的空位上,否则,新增的格子会从下面冒出来

// 下落
down() {
    return new Promise((resolve, reject) => {
      const { data, size, x, y } = this
      data.forEach((item, index) => {
        let distance = 0 // 移动格数
        if (item.status === \"remove\") {
          // 删除的位置上移,调整新增格子的位置
          let top = item.top
          // 统计需要上移多少步
          while (top) {
            if (top.status !== \"remove\") {
              distance += 1
            }
            top = top.top
          }
          // 上移
          if (distance) {
            item.y -= distance
            item.positionTop = item.positionTop - size * distance
          }
        } else {
          let bottom = item.bottom
          // 统计需要下落多少步
          while (bottom) {
            if (bottom.status === \"remove\") {
              distance += 1
            }
            bottom = bottom.bottom
          }
          // 下落
          if (distance) {
            item.y += distance
            item.positionTop = item.positionTop + size * distance
          }
        }
      })
      setTimeout(() => {
        resolve(true)
      }, 500)
    })
}

添加

可以想象到,在下落执行完之后,页面中的矩阵,是所有格子都有的,只是看起来空的格子,实际上是删除格子在那占位,然后只要根据顺序重新生成矩阵,并保留每个非remove格子的状态,是remove的就重新生成,达到替换补充的效果

// 添加
add() {
    return new Promise((resolve, reject) => {
      const { size, matrix } = this
      // 重置矩阵为空
      this.getMatrix()
      // 把当前所有格子信息保存为矩阵
      this.matrix = matrix.map((row, rowIndex) =>
        row.map((col: any, colIndex: number) => {
          return this.data.find((item) => {
            return colIndex == item.x && rowIndex == item.y
          })
        })
      )
      // 根据矩阵需要清除的位置替换新方块
      this.init()
      setTimeout(() => {
        // 新增的格子执行动画
        this.data.forEach((item) => {
          if (item.status === \"add\") {
            item.scale = 1
            item.status = \"normal\"
          }
        })
      }, 100)
      // 动画结束
      setTimeout(() => {
        resolve(true)
      }, 500)
    })
}

接下来后面的逻辑都比较简单了,没啥说的,都写在注释里了

生成矩阵/数据

// 生成全部为空的矩阵
getMatrix() {
    const { x, y } = this
    const row = new Array(x).fill(undefined)
    const matrix = new Array(y).fill(undefined).map((item) => row)
    this.matrix = matrix
}
// 生成小方块
init(bool: boolean = false) {
    const { x, y, typeCount, matrix, size } = this
    const data: Array = []
    // 这里用两个指针,没有用嵌套循环,减少复杂度
    let _x = 0
    let _y = 0
    for (let i = 0, len = Math.pow(x, 2); i < len; i++) {
      let item
      try {
        item = matrix[_y][_x]
      } catch (e) {}
      // 根据矩阵信息来生成方块
      let flag: boolean = item && item.status !== \"remove\"
      // 每一个方块的信息
      let obj = {
        type: flag ? item.type : Math.floor(Math.random() * typeCount),
        x: _x,
        y: _y,
        status: bool ? \"normal\" : flag ? \"normal\" : \"add\",
        positionLeft: flag ? item.positionLeft : size * _x,
        positionTop: flag ? item.positionTop : size * _y,
        left: undefined,
        top: undefined,
        bottom: undefined,
        right: undefined,
        scale: bool ? 1 : flag ? 1 : 0,
        key: item ? item.key + i : `${_x}${_y}`,
        active: false,
      }
      data.push(obj)
      _x++
      if (_x == x) {
        _x = 0
        _y++
      }
    }
    // 保存每个格子上下左右的格子信息
    data.forEach((square) => {
      square.left = data.find(
        (item) => item.x == square.x - 1 && item.y == square.y
      )
      square.right = data.find(
        (item) => item.x == square.x + 1 && item.y == square.y
      )
      square.top = data.find(
        (item) => item.x == square.x && item.y == square.y - 1
      )
      square.bottom = data.find(
        (item) => item.x == square.x && item.y == square.y + 1
      )
    })
    this.data = data
}

点击

// 点击小方块
click(target: any) {
    // 游戏动画正在处理中的时候,不给点击
    if (this.isHandle) return
    // console.log(target)
    const { isSelect } = this
    // 如果没有选择过的
    if (!isSelect) {
      // 选择第一个
      target.active = true
      this.target1 = target
      this.isSelect = true
    } else {
      // 选择第二个
      if (this.target1 === target) return
      this.target1.active = false
      // 如果是相邻的
      if (
        [\"left\", \"top\", \"bottom\", \"right\"].some(
          (item) => this.target1[item] == target
        )
      ) {
        this.target2 = target
        ;(async () => {
          // 调换位置
          await this.swap()
          // 会返回一个有没有可以删除的,的状态
          let res = await this.gameLoop()
          // 没有就再次调换位置,还原
          if (!res) {
            await this.swap()
          }
        })()
        this.isSelect = false
      } else {
        // 如果不是相邻的
        target.active = true
        this.target1 = target
        this.isSelect = true
      }
    }
}

换位置

这里的逻辑主要就是交换两个方块的位置信息,然后重新生成上下左右,就ok 了

// 换位置
swap() {
    return new Promise((resolve, reject) => {
      const { target1, target2, data } = this
      const { positionLeft: pl1, positionTop: pt1, x: x1, y: y1 } = target1
      const { positionLeft: pl2, positionTop: pt2, x: x2, y: y2 } = target2
      setTimeout(() => {
        target1.positionLeft = pl2
        target1.positionTop = pt2
        target1.x = x2
        target1.y = y2
        target2.positionLeft = pl1
        target2.positionTop = pt1
        target2.x = x1
        target2.y = y1
        data.forEach((square) => {
          square.left = data.find(
            (item) => item.x == square.x - 1 && item.y == square.y
          )
          square.right = data.find(
            (item) => item.x == square.x + 1 && item.y == square.y
          )
          square.top = data.find(
            (item) => item.x == square.x && item.y == square.y - 1
          )
          square.bottom = data.find(
            (item) => item.x == square.x && item.y == square.y + 1
          )
        })
      }, 0)
      setTimeout(() => {
        resolve(true)
      }, 500)
    })
}

到此这篇关于基于Vue3制作简单的消消乐游戏的文章就介绍到这了,更多相关Vue3消消乐游戏内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号