基於Vue3制作簡單的消消樂遊戲
遊戲介紹
先看一下
好吧,我知道界面有點醜 →_→
核心思路
遊戲步驟主要就是:消除、下落、補充、移動,采用三種狀態來區分需要刪除的(remove
)、新添加的(add
)、和正常的方塊(normal
)
- 主要就是生成小方塊列表後,馬上保存每一個方塊上下左右方塊的信息
- 然後判斷每一個方塊和上下或和左右類型相同即為需要消除,並把該方塊狀態改為
remove
- 然後通過定位改變
top
來控制下落,同時要把消除的位置上移,這樣補充的時候才能在對應空位上顯示,這裡專門用瞭一個矩陣來保存所有對應格子信息,區分出哪些格子是需要消除/補充的 - 移動就比較簡單瞭,由於每個方塊上都保存瞭自己的上下左右信息,所以隻需要交換就行瞭
有一個坑,就是 key,由於 diff 算法的原因,不需要重新渲染就要保證key是唯一的,比如下落的也重新渲染視覺效果會很奇怪
核心代碼
html
以下是矩陣區域所有html,就是用一個div來做的,根據類型給不同類名,然後雪糕全是背景圖片
<div class="stage"> <div v-for="item in data" :style="{ left: `${item.positionLeft}px`, top: `${item.positionTop}px`, }" :key="item.key" :class="[ 'square', `type${item.type}`, `scale${item.scale}`, { active: item.active }, ]" @click="handleClick(item)" ></div> </div>
js
js 部分主要是封裝瞭一個類,方便統一管理操作
export default class Stage implements IXXL { x: number // x和y 是遊戲舞臺行列方塊個數 y: number size: number // 方塊大小 typeCount = 7 // 方塊類型個數 matrix: Array<any> = [] // 方塊矩陣,用於每次消除之後根據矩陣規則生成新的遊戲棋盤 data: Array<any> = [] // 用於渲染頁面 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<any> = [] // 這裡用兩個指針,沒有用嵌套循環,減少復雜度 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消消樂遊戲內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- C++實現LeetCode(74.搜索一個二維矩陣)
- 避免地獄async await的使用及原理解析
- elementui中使用el-tree控件懶加載和局部刷新
- Vue.$set 失效的坑 問題發現及解決方案
- Vue 實現可視化拖拽頁面編輯器