基于Vue3制作簡單的消消樂游戲
游戲介紹
先看一下

好吧,我知道界面有點(diǎn)丑 →_→
核心思路
游戲步驟主要就是:消除、下落、補(bǔ)充、移動(dòng),采用三種狀態(tài)來區(qū)分需要?jiǎng)h除的(remove)、新添加的(add)、和正常的方塊(normal)
- 主要就是生成小方塊列表后,馬上保存每一個(gè)方塊上下左右方塊的信息
- 然后判斷每一個(gè)方塊和上下或和左右類型相同即為需要消除,并把該方塊狀態(tài)改為
remove - 然后通過定位改變
top來控制下落,同時(shí)要把消除的位置上移,這樣補(bǔ)充的時(shí)候才能在對應(yīng)空位上顯示,這里專門用了一個(gè)矩陣來保存所有對應(yīng)格子信息,區(qū)分出哪些格子是需要消除/補(bǔ)充的 - 移動(dòng)就比較簡單了,由于每個(gè)方塊上都保存了自己的上下左右信息,所以只需要交換就行了
有一個(gè)坑,就是 key,由于 diff 算法的原因,不需要重新渲染就要保證key是唯一的,比如下落的也重新渲染視覺效果會很奇怪
核心代碼
html
以下是矩陣區(qū)域所有html,就是用一個(gè)div來做的,根據(jù)類型給不同類名,然后雪糕全是背景圖片
<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 部分主要是封裝了一個(gè)類,方便統(tǒng)一管理操作
export default class Stage implements IXXL {
x: number // x和y 是游戲舞臺行列方塊個(gè)數(shù)
y: number
size: number // 方塊大小
typeCount = 7 // 方塊類型個(gè)數(shù)
matrix: Array<any> = [] // 方塊矩陣,用于每次消除之后根據(jù)矩陣規(guī)則生成新的游戲棋盤
data: Array<any> = [] // 用于渲染頁面
isHandle = false // 游戲是否正在消除/下落/添加處理中
isSelect = false // 是否有選擇
score = 0 // 分?jǐn)?shù)
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(){}
// 循環(huán)執(zhí)行
gameLoop(){}
// 點(diǎn)擊
click(){}
// 換位
swap(){}
// 刪除
remove(){}
// 下落
down(){}
// 補(bǔ)充
add(){}
}游戲開始/循環(huán)
// 要等動(dòng)畫執(zhí)行完,所以用 await
async gameLoop(bool: boolean = false) {
// 結(jié)束游戲后重新開始時(shí)分?jǐn)?shù)清0
if (bool) this.score = 0
// 游戲狀態(tài)改為正在執(zhí)行中,控制在動(dòng)畫執(zhí)行過程中不能點(diǎn)擊交換
this.isHandle = true
// 找出需要?jiǎng)h除的
await this.remove()
// 用于檢測點(diǎn)擊交換后判斷有沒有需要?jiǎng)h除的,沒有就再換回來
let status = this.data.some((item) => item.status === "remove")
// 只要有刪除了的,執(zhí)行上面的下落、補(bǔ)充,補(bǔ)充后再循環(huán)找有沒有可以刪除的
while (this.data.some((item) => item.status === "remove")) {
await this.down()
await this.add()
await this.remove()
}
// 所有能刪除的刪除后,更改狀態(tài),然后就可以點(diǎn)擊了
this.isHandle = false
return status
}刪除
注意 狀態(tài)為 remove 的實(shí)際沒有刪除,只是頁面上看不到了,到補(bǔ)充的時(shí)候才會刪除掉狀態(tài)為 remove 的
// 清除
remove() {
return new Promise((resolve, reject) => {
const { data } = this
data.forEach((item) => {
const { left, right, top, bottom, type } = item
// 如果自己 + 自己的左和右 類型都一樣,狀態(tài)變更為刪除
if (left?.type == type && right?.type == type) {
left.status = "remove"
item.status = "remove"
right.status = "remove"
}
// 如果自己 + 自己的上和下 類型都一樣,狀態(tài)變更為刪除
if (top?.type == type && bottom?.type == type) {
top.status = "remove"
item.status = "remove"
bottom.status = "remove"
}
})
setTimeout(() => {
// 執(zhí)行刪除動(dòng)畫,頁面上看不到了,并統(tǒng)計(jì)分?jǐn)?shù),實(shí)際這時(shí)還沒刪除
data.forEach((item, index) => {
if (item.status === "remove") {
item.scale = 0
this.score += 1
}
})
// 這里延遲100毫秒是首次進(jìn)頁面的時(shí)候,先看到格子有東西,不然會是空的
}, 100)
// 動(dòng)畫時(shí)長500毫秒 css 那邊定義了,所以延遲500毫秒
setTimeout(() => {
resolve(true)
}, 500)
})
}下落
這里有個(gè)坑。除了要把刪除格子上面的下落下來之外,還需要把已經(jīng)刪除(狀態(tài)為刪除,頁面上看不到了的)的格子上位到,上面的空位上,否則,新增的格子會從下面冒出來
// 下落
down() {
return new Promise((resolve, reject) => {
const { data, size, x, y } = this
data.forEach((item, index) => {
let distance = 0 // 移動(dòng)格數(shù)
if (item.status === "remove") {
// 刪除的位置上移,調(diào)整新增格子的位置
let top = item.top
// 統(tǒng)計(jì)需要上移多少步
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
// 統(tǒng)計(jì)需要下落多少步
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)
})
}添加
可以想象到,在下落執(zhí)行完之后,頁面中的矩陣,是所有格子都有的,只是看起來空的格子,實(shí)際上是刪除格子在那占位,然后只要根據(jù)順序重新生成矩陣,并保留每個(gè)非remove格子的狀態(tài),是remove的就重新生成,達(dá)到替換補(bǔ)充的效果
// 添加
add() {
return new Promise((resolve, reject) => {
const { size, matrix } = this
// 重置矩陣為空
this.getMatrix()
// 把當(dāng)前所有格子信息保存為矩陣
this.matrix = matrix.map((row, rowIndex) =>
row.map((col: any, colIndex: number) => {
return this.data.find((item) => {
return colIndex == item.x && rowIndex == item.y
})
})
)
// 根據(jù)矩陣需要清除的位置替換新方塊
this.init()
setTimeout(() => {
// 新增的格子執(zhí)行動(dòng)畫
this.data.forEach((item) => {
if (item.status === "add") {
item.scale = 1
item.status = "normal"
}
})
}, 100)
// 動(dòng)畫結(jié)束
setTimeout(() => {
resolve(true)
}, 500)
})
}接下來后面的邏輯都比較簡單了,沒啥說的,都寫在注釋里了
生成矩陣/數(shù)據(jù)
// 生成全部為空的矩陣
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> = []
// 這里用兩個(gè)指針,沒有用嵌套循環(huán),減少復(fù)雜度
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) {}
// 根據(jù)矩陣信息來生成方塊
let flag: boolean = item && item.status !== "remove"
// 每一個(gè)方塊的信息
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++
}
}
// 保存每個(gè)格子上下左右的格子信息
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
}點(diǎn)擊
// 點(diǎn)擊小方塊
click(target: any) {
// 游戲動(dòng)畫正在處理中的時(shí)候,不給點(diǎn)擊
if (this.isHandle) return
// console.log(target)
const { isSelect } = this
// 如果沒有選擇過的
if (!isSelect) {
// 選擇第一個(gè)
target.active = true
this.target1 = target
this.isSelect = true
} else {
// 選擇第二個(gè)
if (this.target1 === target) return
this.target1.active = false
// 如果是相鄰的
if (
["left", "top", "bottom", "right"].some(
(item) => this.target1[item] == target
)
) {
this.target2 = target
;(async () => {
// 調(diào)換位置
await this.swap()
// 會返回一個(gè)有沒有可以刪除的,的狀態(tài)
let res = await this.gameLoop()
// 沒有就再次調(diào)換位置,還原
if (!res) {
await this.swap()
}
})()
this.isSelect = false
} else {
// 如果不是相鄰的
target.active = true
this.target1 = target
this.isSelect = true
}
}
}換位置
這里的邏輯主要就是交換兩個(gè)方塊的位置信息,然后重新生成上下左右,就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)
})
}到此這篇關(guān)于基于Vue3制作簡單的消消樂游戲的文章就介紹到這了,更多相關(guān)Vue3消消樂游戲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue+Element-ui彈窗?this.$alert?is?not?a?function問題
這篇文章主要介紹了Vue+Element-ui彈窗?this.$alert?is?not?a?function問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
使用Vue開發(fā)動(dòng)態(tài)刷新Echarts組件的教程詳解
這篇文章主要介紹了使用Vue開發(fā)動(dòng)態(tài)刷新Echarts組件的教程詳解,需要的朋友可以參考下2018-03-03
Vue 實(shí)現(xiàn)復(fù)制功能,不需要任何結(jié)構(gòu)內(nèi)容直接復(fù)制方式
今天小編就為大家分享一篇Vue 實(shí)現(xiàn)復(fù)制功能,不需要任何結(jié)構(gòu)內(nèi)容直接復(fù)制方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-11-11
Element?table?上下移需求的實(shí)現(xiàn)
本文主要介紹了Element?table?上下移需求的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07

