如何利用vue3實現(xiàn)一個俄羅斯方塊
前言
之前寫了一個貪吃蛇的demo,但是公司最近沒業(yè)務(wù)還得繼續(xù)摸魚??。所以趁熱打鐵,在下又去攢了一個俄羅斯方塊。代碼地址
游戲相關(guān)設(shè)置
游戲界面設(shè)置
構(gòu)建一個16×25的游戲界面,使用響應(yīng)式數(shù)據(jù)存儲一個二維數(shù)組,該二維數(shù)據(jù)中存儲著每一個有關(guān)該方格信息的數(shù)組,該數(shù)組格式為[從上到下的坐標(biāo)位置,從左到右的坐標(biāo)位置, 該方格顏色(0為白色,1為紅色,2為綠色)]
//游戲界面(作為俄羅斯方塊在各個格子上渲染顏色的依據(jù)) let checkerboardInfo = reactive([]); //設(shè)置游戲界面 const setCheckerboard = () =>{ for (let i = 0; i < 25; i++) { for (let j = 0; j < 16; j++) { checkerboardInfo.push([i, j, 0]) } } }
存儲還在移動的俄羅斯方塊信息
可移動的俄羅斯方塊一共分為7種:正方形、長方形、L形、反L形、z形、反z形、三角形,使用隨機(jī)數(shù)選取每個可移動的俄羅斯方塊,使用二維數(shù)組存儲可移動的俄羅斯方塊四個坐標(biāo)點,并確保其從游戲界面中間落下(實力有限,直接將七種形態(tài)用代碼窮舉了(●'?'●))
/存儲當(dāng)前還在墜落的方格坐標(biāo) let moveSquareCoordinate = null; /方塊類型(長方形、三角形、正方形,z字型, 反z字型,l型, 反l型) let squareType = [0, 1, 2, 3, 4, 5, 6]; //方塊類型下標(biāo) let squareTypeIndex = -1; //隨機(jī)選取方塊 const randomSquareType = ()=>{ squareTypeIndex = Math.floor(Math.random()*(7)); }; //構(gòu)造方塊 const setSquare = () =>{ randomSquareType(); switch(squareTypeIndex) { //長方形 case 0: moveSquareCoordinate = [[0, 6], [0, 7], [0, 8], [0, 9]] break; //三角形 case 1: moveSquareCoordinate = [[0, 8], [0, 9], [0, 10], [1, 9]] break; //正方形 case 2: moveSquareCoordinate = [[0, 7], [0, 8], [1, 7], [1, 8]] break; //z字型 case 3: moveSquareCoordinate = [[0, 7], [0, 8], [1, 8], [1, 9]] break; //反z字型 case 4: moveSquareCoordinate = [[0, 8], [0, 7], [1, 7], [1, 6]] break; //l型 case 5: moveSquareCoordinate = [[0, 8], [0, 9], [0, 10], [1, 8]] break; //反l型 case 6: moveSquareCoordinate = [[0, 8], [0, 9], [0, 10], [1, 10]] break; } }
存儲已經(jīng)不能移動的俄羅斯方塊信息
當(dāng)俄羅斯方塊觸碰到底部邊界或者,觸碰到不能移動的俄羅斯方塊時,可移動的俄羅斯方塊就會變成不可移動的方塊,所以需要也需要用一個專門的數(shù)組存儲
//存儲當(dāng)前已經(jīng)穩(wěn)定墜落的方塊的坐標(biāo) let stabilitySquareCoordinate = [];
使用之前在貪吃蛇中使用的顏色渲染工具
//改變棋盤格子顏色([A, B]為坐標(biāo),color是需要渲染為什么顏色(0為白色,1為紅色,2為綠色)) const changeCheckerboard = ([A, B], color) => { for (let i = 0; i < checkerboardInfo.length; i++) { let [x, y, num] = checkerboardInfo[i]; if( A===x && B===y ){ checkerboardInfo[i][2]=color; break } } }; //清空棋盤顏色 const clearCheckerboard = () => { for (let index = 0; index < checkerboardInfo.length; index++) { checkerboardInfo[index][2] = 0 } };
讓方塊移動起來(不考慮切換方塊的形態(tài)切換)
可移動的俄羅斯方塊會一直向下移動,能通過鍵盤左右鍵控制其向左右移動,通過鍵盤下鍵加速向下移動,不能向上移動(鍵盤上鍵控制該方塊形態(tài)的切換),移動時不能超過該游戲界面的范圍,如果移動的俄羅斯方塊觸碰到不可移動的俄羅斯方塊,則停止移動,并且會積累在不可移動的俄羅斯方塊上
在移動之前得先設(shè)置工具函數(shù),用于檢測移動后的俄羅斯方塊二維數(shù)組中存儲的坐標(biāo)點數(shù)組是否會超出游戲界面的范圍,以及是否會觸碰到不可移動的俄羅斯方塊上
檢測移動的俄羅斯方塊二維數(shù)組在移動后它的每個坐標(biāo)點數(shù)組是否有超出范圍的
在范圍之中則不管,不在范圍之中則不允許移動
//判斷是否碰到邊界(arr為可移動俄羅斯方塊上單個的坐標(biāo)點) const judgeBoundary = (arr) => { if((arr[0]<0||arr[0]>24)||(arr[1]<0||arr[1]>15)){ return true }; return false }
檢測移動的俄羅斯方塊二維數(shù)組在移動后它的每個坐標(biāo)點數(shù)組是否會觸碰到不可移動的俄羅斯方塊上
觀察可知,移動的俄羅斯方塊和不可移動的俄羅斯方塊觸碰到一起就是在它們存儲的坐標(biāo)點數(shù)組下標(biāo)為0的值相差一,而下標(biāo)為1的值相等時,該移動的俄羅斯方塊變成不可移動的俄羅斯方塊
特殊情況:當(dāng)移動方塊沒碰到不可移動的方塊,但是觸碰到游戲界面的最底部,即存儲的坐標(biāo)點數(shù)組下標(biāo)為0的值為24時該移動方塊亦會變成不可移動的方塊
//查看方塊是否碰到已經(jīng)存在的方格中(arr為可移動俄羅斯方塊上單個的坐標(biāo)點) const judgeStabilitySquareCoordinate = (arr) =>{ //移動到最后一格時 if( arr[0] === 24 ){ return true } //遍歷不可移動的俄羅斯方塊與該點做比較 for (let index = 0; index < stabilitySquareCoordinate.length; index++) { if(stabilitySquareCoordinate[index][0]-1 === arr[0] && stabilitySquareCoordinate[index][1] === arr[1]){ return true } }; return false; }
特殊情況(得分)
得分是在可移動的俄羅斯變?yōu)椴豢梢苿拥亩砹_斯方塊發(fā)生的一個判斷
如果在不可移動的俄羅斯方塊二維數(shù)組中,有坐標(biāo)點數(shù)組下標(biāo)為0的值相同且存在16個,那么這16個點應(yīng)該被刪除出不可移動的俄羅斯方塊二維數(shù)組,這16個點被刪除后,在這些點上的坐標(biāo)得向下移動一格(如下圖所示)
//得分(arr為可移動俄羅斯方塊上單個的坐標(biāo)點) const score = (arr)=>{ let num = 0; for (let index = 0; index < stabilitySquareCoordinate.length; index++) { if(arr[0] === stabilitySquareCoordinate[index][0]){ num++; //等于16后滿足當(dāng)前銷毀需求(直接跳出循環(huán)減少性能消耗) if(num === 16){ break; } } }; if(num === 16){ //刪除已經(jīng)在該數(shù)組中湊齊能得分的行數(shù)(小技巧,倒著循環(huán)數(shù)組能保證每個符合刪除條件的數(shù)據(jù)都能被刪除) for (let index = stabilitySquareCoordinate.length-1; index >-1; index--) { if(arr[0] === stabilitySquareCoordinate[index][0]){ stabilitySquareCoordinate.splice(index, 1) } } //將所有在銷毀行上面的穩(wěn)定方塊移動至下一行去 for (let index = 0; index < stabilitySquareCoordinate.length; index++) { if(arr[0] > stabilitySquareCoordinate[index][0]){ stabilitySquareCoordinate[index][0]++ } } } }
移動方法
移動就是通過監(jiān)聽鍵盤彈起事件,通過對應(yīng)的keycode更改可移動的俄羅斯方塊中每個坐標(biāo)點的值,然后再將獲取到的新的二維數(shù)組去執(zhí)行之前的三個方法依次判斷
//事件(方便后期摘除事件) const listener = (event)=>{ //只監(jiān)聽上下左右四個按鍵 const keyCodeArr = [37, 39, 40]; if(keyCodeArr.includes(event.keyCode)){ //監(jiān)聽方向鍵變化(執(zhí)行方塊移動方向) moveSquare(event.keyCode); } } //定時器(一直會有一個下移方塊指令執(zhí)行) let timer = setInterval(()=>{ moveSquare(40) },500); //在window上掛載事件監(jiān)聽器 window.addEventListener("keydown", listener);
//移動方塊的指令 const moveSquare = (num) => { if( !isShowSquare.value ){ return }; //移動 for (let index = 0; index < moveSquareCoordinate.length; index++) { switch (num) { //鍵盤對應(yīng)數(shù)字如下 //40:下;37:左;39:右; case 37: moveSquareCoordinate[index][1] = moveSquareCoordinate[index][1]-1 break; case 39: moveSquareCoordinate[index][1] = moveSquareCoordinate[index][1]+1 break; case 40: moveSquareCoordinate[index][0] = moveSquareCoordinate[index][0]+1 break; }; }; //是否超過邊界的標(biāo)桿 let flag1 = false; for (let index = 0; index < moveSquareCoordinate.length; index++) { if(judgeBoundary(moveSquareCoordinate[index])){ flag1 = true; } } //標(biāo)桿滿足后方塊復(fù)位 if(flag1){ for (let index = 0; index < moveSquareCoordinate.length; index++) { switch (num) { case 37: moveSquareCoordinate[index][1] = moveSquareCoordinate[index][1]+1 break; case 39: moveSquareCoordinate[index][1] = moveSquareCoordinate[index][1]-1 break; case 40: moveSquareCoordinate[index][0] = moveSquareCoordinate[index][0]-1 break; }; }; } //能否觸碰到已穩(wěn)定方塊的標(biāo)桿 let flag2 = false; let coordinate = []; for (let index = 0; index < moveSquareCoordinate.length; index++) { if(judgeStabilitySquareCoordinate(moveSquareCoordinate[index])){ flag2 = true; } }; //只要碰到了 if (flag2) { //添加進(jìn)入不移動的方塊坐標(biāo)數(shù)組 for (let index = 0; index < moveSquareCoordinate.length; index++) { stabilitySquareCoordinate.push(moveSquareCoordinate[index]); if(moveSquareCoordinate[index][0]-1 === 0){ //輸了就跳出循環(huán),不必再給已穩(wěn)定的方塊坐標(biāo)添加新坐標(biāo)了 isFail.value = true; break } } //如果已經(jīng)失敗則停止移動 if (isFail.value) { return } isShowSquare.value = false; //將移動方塊中每個點坐標(biāo)去做得分判斷 for (let index = 0; index < moveSquareCoordinate.length; index++) { score(moveSquareCoordinate[index]); } } //重新渲染游戲界面顏色 clearCheckerboard(); for (let index = 0; index < moveSquareCoordinate.length; index++) { changeCheckerboard(moveSquareCoordinate[index], 2) } for (let index = 0; index < stabilitySquareCoordinate.length; index++) { changeCheckerboard(stabilitySquareCoordinate[index], 1) } };
經(jīng)過一系列的操作,我們已經(jīng)得到了一個可以玩的俄羅斯方塊,它能夠移動方塊,也能得分,但是不能將移動的俄羅斯方塊切換形態(tài),十分沒有游戲體驗感,那就再加切換形態(tài)的操作??(這個操作把我差點帶走,看到這里多少給個贊唄o( ̄▽ ̄)ブ)
切換操作書寫
先窮舉七種形狀的不同形態(tài)(如下圖)
形態(tài)變化 | |
---|---|
長方形 | 2種 |
正方形 | 1種 |
z形 | 2種 |
反z形 | 2種 |
三角形 | 4種 |
L形 | 4種 |
反L形 | 4種 |
設(shè)計檢測該形狀形態(tài)的方法
檢測長方形形態(tài)的方法
通過判斷A、B兩點的下標(biāo)為0是的值是否相同來返回形態(tài)
const detectionToolAboutRectangle = () => { if (moveSquareCoordinate[0][0] !== moveSquareCoordinate[1][0]) { //豎直的方塊 return 0 } //橫著的長方形方塊 return 1 };
檢測z形和反z形形態(tài)的方法
通過判斷A、B兩點的下標(biāo)為0是的值是否相同來返回形態(tài)
//檢驗工具(z字形和反z字形) const detectionToolAboutZOr_Z = () => { if(moveSquareCoordinate[0][0] === moveSquareCoordinate[1][0]){ //z字形 return 0 } //n字形 return 1 };
檢測L形、反L形、三角形形態(tài)的方法
通過判斷判斷A、B、C三點構(gòu)成的線與D點的相對位置來判斷形態(tài),第一種形態(tài)為D點在線段ABC下方,第二種形態(tài)為D點在線段ABC左邊,第三種形態(tài)為D點在線段ABC上方,第四種形態(tài)為D點在線段ABC右邊,
//檢驗工具(三角形、l型、反l型) const detectionToolAboutTriangle = () => { //判斷四種形態(tài) if ((moveSquareCoordinate[0][0] === moveSquareCoordinate[1][0] && moveSquareCoordinate[1][0] === moveSquareCoordinate[2][0]) && moveSquareCoordinate[1][0]<moveSquareCoordinate[3][0]) { return 0 } if ((moveSquareCoordinate[0][1] === moveSquareCoordinate[1][1] && moveSquareCoordinate[1][1] === moveSquareCoordinate[2][1]) && moveSquareCoordinate[2][1]>moveSquareCoordinate[3][1]) { return 1 } if ((moveSquareCoordinate[0][0] === moveSquareCoordinate[1][0] && moveSquareCoordinate[1][0] === moveSquareCoordinate[2][0]) && moveSquareCoordinate[1][0]>moveSquareCoordinate[3][0]) { return 2 } if ((moveSquareCoordinate[0][1] === moveSquareCoordinate[1][1] && moveSquareCoordinate[1][1] === moveSquareCoordinate[2][1]) && moveSquareCoordinate[2][1]<moveSquareCoordinate[3][1]) { return 3 } };
設(shè)計切換方法
需要判斷其現(xiàn)有形態(tài),然后判斷切換后是否會超出游戲范圍界面,或者觸碰到不能移動,如果不超出且不觸碰則切換狀態(tài)
切換L形形態(tài)(代碼過多只解釋一個,其余的可以看我倉庫代碼)
由于業(yè)務(wù)水平緣故,在下直接保證每次切換形態(tài)都是向右旋轉(zhuǎn)90度(如圖旋轉(zhuǎn))
在每一次旋轉(zhuǎn)后,都以之前的B點坐標(biāo)重構(gòu)方塊,(沒錯在下直接窮舉了7種方塊的18個類型變化,如果有其他好方法,歡迎評論區(qū)留言,謝謝大哥??)
const toggleSquareShapeAboutL= () => { //改變后的俄羅斯方塊數(shù)組 let arr = null; //滿足哪種形態(tài)則將后一種形態(tài)的坐標(biāo)點存儲進(jìn)該數(shù)組中 switch (detectionToolAboutTriangle()) { case 0: arr = [[moveSquareCoordinate[1][0]-1,moveSquareCoordinate[1][1]],[moveSquareCoordinate[1][0],moveSquareCoordinate[1][1]],[moveSquareCoordinate[1][0]+1,moveSquareCoordinate[1][1]],[moveSquareCoordinate[1][0]-1,moveSquareCoordinate[1][1]-1]]; //判斷每個點會不會超出游戲界面或者觸碰到不可移動的俄羅斯方塊上 for (let index = 0; index < arr.length; index++) { if(judgeBoundary(arr[index]) || judgeStabilitySquareCoordinate(arr[index])){ return } } moveSquareCoordinate = arr; clearCheckerboard(); for (let index = 0; index < moveSquareCoordinate.length; index++) { changeCheckerboard(moveSquareCoordinate[index], 2) } for (let index = 0; index < stabilitySquareCoordinate.length; index++) { changeCheckerboard(stabilitySquareCoordinate[index], 1) } break; case 1: arr = [[moveSquareCoordinate[1][0],moveSquareCoordinate[1][1]+1],[moveSquareCoordinate[1][0],moveSquareCoordinate[1][1]],[moveSquareCoordinate[1][0],moveSquareCoordinate[1][1]-1],[moveSquareCoordinate[1][0]-1,moveSquareCoordinate[1][1]+1]]; for (let index = 0; index < arr.length; index++) { if(judgeBoundary(arr[index]) || judgeStabilitySquareCoordinate(arr[index])){ return } } moveSquareCoordinate = arr; clearCheckerboard(); for (let index = 0; index < moveSquareCoordinate.length; index++) { changeCheckerboard(moveSquareCoordinate[index], 2) } for (let index = 0; index < stabilitySquareCoordinate.length; index++) { changeCheckerboard(stabilitySquareCoordinate[index], 1) } break; case 2: arr = [[moveSquareCoordinate[1][0]+1,moveSquareCoordinate[1][1]],[moveSquareCoordinate[1][0],moveSquareCoordinate[1][1]],[moveSquareCoordinate[1][0]-1,moveSquareCoordinate[1][1]],[moveSquareCoordinate[1][0]+1,moveSquareCoordinate[1][1]+1]]; for (let index = 0; index < arr.length; index++) { if(judgeBoundary(arr[index]) || judgeStabilitySquareCoordinate(arr[index])){ return } } moveSquareCoordinate = arr; clearCheckerboard(); for (let index = 0; index < moveSquareCoordinate.length; index++) { changeCheckerboard(moveSquareCoordinate[index], 2) } for (let index = 0; index < stabilitySquareCoordinate.length; index++) { changeCheckerboard(stabilitySquareCoordinate[index], 1) } break; case 3: arr = [[moveSquareCoordinate[1][0],moveSquareCoordinate[1][1]-1],[moveSquareCoordinate[1][0],moveSquareCoordinate[1][1]],[moveSquareCoordinate[1][0],moveSquareCoordinate[1][1]+1],[moveSquareCoordinate[1][0]+1,moveSquareCoordinate[1][1]-1]]; for (let index = 0; index < arr.length; index++) { if(judgeBoundary(arr[index]) || judgeStabilitySquareCoordinate(arr[index])){ return } } //給移動的俄羅斯方塊數(shù)組重新賦值 moveSquareCoordinate = arr; //重新渲染游戲界面 clearCheckerboard(); for (let index = 0; index < moveSquareCoordinate.length; index++) { changeCheckerboard(moveSquareCoordinate[index], 2) } for (let index = 0; index < stabilitySquareCoordinate.length; index++) { changeCheckerboard(stabilitySquareCoordinate[index], 1) } break; } };
單個方格組件展示
CheckerboardItem.vue
只通過存入的方格顏色信息(方格信息數(shù)組中的第三個值)來判斷當(dāng)前方格顯示的顏色
const props = defineProps({ checkerboardItemInfo:Array, }); //通過toRefs解構(gòu)方格信息數(shù)組中的第三個值(只有使用toRefs才能保持該引用數(shù)據(jù)解構(gòu)后的數(shù)據(jù)依然保持響應(yīng)式) let [ x, y, num] = toRefs(props.checkerboardItemInfo) let color = ref(''); //使用監(jiān)聽器完成數(shù)據(jù)監(jiān)聽,給背景色設(shè)置不同值 watchEffect(()=>{ switch (num.value) { case 0: color.value = 'while' break; case 1: color.value = 'red' break; case 2: color.value = 'green' break; } })
<style lang="less" scoped> .checkerboardItem{ //vue3.2能在css中使用v-bind綁定響應(yīng)式數(shù)據(jù) background-color: v-bind(color); } </style>
好了,俄羅斯方塊搞定
總結(jié)
到此這篇關(guān)于如何利用vue3實現(xiàn)一個俄羅斯方塊的文章就介紹到這了,更多相關(guān)vue3寫俄羅斯方塊內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解vue-cli快速構(gòu)建項目以及引入bootstrap、jq
本篇文章主要介紹了vue-cli快速構(gòu)建項目以及引入bootstrap、jq,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05vue中v-for通過動態(tài)綁定class實現(xiàn)觸發(fā)效果
這篇文章主要介紹了vue中v-for通過動態(tài)綁定class實現(xiàn)觸發(fā)效果,本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下2018-12-12關(guān)于vuepress部署出現(xiàn)樣式的問題及解決
這篇文章主要介紹了關(guān)于vuepress部署出現(xiàn)樣式的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09vue如何通過id從列表頁跳轉(zhuǎn)到對應(yīng)的詳情頁
這篇文章主要介紹了vue如何通過id從列表頁跳轉(zhuǎn)到對應(yīng)的詳情頁 ,需要的朋友可以參考下2018-05-05vue清除瀏覽器全部cookie的問題及解決方法(絕對有效!)
最近項目要實現(xiàn)關(guān)閉瀏覽器清除用戶緩存的功能,下面這篇文章主要給大家介紹了關(guān)于vue清除瀏覽器全部cookie的問題及解決方法,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06Vue3中級指南之如何在vite中使用svg圖標(biāo)詳解
在以webpack為構(gòu)建工具的開發(fā)環(huán)境中我們可以很方便的實現(xiàn)SVG圖標(biāo)的組件化,下面這篇文章主要給大家介紹了關(guān)于Vue3中級指南之如何在vite中使用svg圖標(biāo)的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04