您好,登錄后才能下訂單哦!
今天小編給大家分享一下Vue+TailWindcss怎么實(shí)現(xiàn)一個(gè)簡單的闖關(guān)小游戲的相關(guān)知識點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
這是一款2d益智闖關(guān)游戲,玩家須躲避敵人與陷阱到達(dá)終點(diǎn) 擁有多個(gè)關(guān)卡
可進(jìn)行關(guān)卡的自定義并留存數(shù)據(jù)
vue tailwindcss
自定義關(guān)卡
敵人自動(dòng)索敵
低技術(shù)力
you win!
創(chuàng)建一個(gè)json文件,用來存放初始關(guān)卡的變量(只有一關(guān)。。。) 為方塊設(shè)定大小,初始化變量speed設(shè)置為176,棋盤的寬高就各位4個(gè)speed,方塊寬高就是1個(gè)speed,方塊移動(dòng)一格就是speed * 1,兩格就是speed * 2
<!-- 棋盤 --> <div :> <!-- 每一個(gè)小方塊 --> <div :></div> </div>
const speed = ref(176);
Level是一個(gè)json文件,里面放著第一關(guān)的各種變量,用來在沒有關(guān)卡的時(shí)候初始化一個(gè)關(guān)卡
level.json
[ { "id": 1,// 第一關(guān) "speed": 176,// 方塊大小 "top": 528,// 主角top值 "left": 0,// 主角left值 "enemy_top": 0,// 敵人top值 "enemy_left": 352,// 敵人left值 "enemy_top_2": 528,// 敵人2的top值 "enemy_left_2": 352,// 敵人2的left值 "obstacle_top": 176,// 障礙top值 "obstacle_left": 352,// 障礙left值 "trap_top": 352,// 陷阱top值 "trap_left": 176,// 陷阱left值 "spot_top": 0,// 終點(diǎn)top值 "spot_left": 528// 終點(diǎn)left值 } ]
在加載頁面的時(shí)候判斷是否有數(shù)據(jù)如果沒有的話添加
import Level from "../../api/level.json"; let res = JSON.parse(localStorage.getItem("data")); if (!res) { localStorage.setItem("data", JSON.stringify(Level)); }
使用絕對定位,用transition-all讓方塊看起來有動(dòng)畫效果
<div class="absolute transition-all"></div>
為小方塊設(shè)置特定的top和left,聲明變量然后設(shè)置給小方塊上
<!-- 終點(diǎn),我用的spot前綴 --> <div :></div> <!-- 敵人,我用的enemy前綴(敵人2后綴直接-2) --> <div :></div>
const Level = JSON.parse(localStorage.getItem("data")); const spot_top = ref(Level[index].spot_top); const spot_left = ref(Level[index].spot_left); const enemy_top = ref(Level[index].enemy_top); const enemy_left = ref(Level[index].enemy_left);
當(dāng)按下相應(yīng)按鍵后執(zhí)行相應(yīng)的函數(shù)
document.addEventListener("keydown", (e) => { switch (e.key) { case "a": if (is_run.value) { moveProtagonistA(); } break; case "w": if (is_run.value) { moveProtagonistW(); } break; case "d": if (is_run.value) { moveProtagonistD(); } break; case "s": if (is_run.value) { moveProtagonistS(); } break; case "r": againGame();// 重新開始 break; } });
四個(gè)函數(shù)的意思分別是主角塊上下左右的移動(dòng),本質(zhì)其實(shí)都差不多,差別就在于每個(gè)的top和left是不同的,所以咱就挑一個(gè)詳細(xì)說明一下:
當(dāng)想讓主角向左移動(dòng)時(shí)
const moveProtagonistA = () => { // 自殺判斷 if ( left.value == enemy_left.value + speed.value && top.value == enemy_top.value ) { left.value -= speed.value; return false; } if (left.value == 0) { // 邊界判斷 left.value = -20; setTimeout(() => { left.value = 0; }, 100); return false; } // 障礙判斷 obstacle = obstacle_left.value + speed.value; if (top.value == obstacle_top.value && left.value == obstacle) { left.value = obstacle - 20; setTimeout(() => { left.value = obstacle; }, 100); } else { left.value -= speed.value; freeFindEnemy(enemy_top, enemy_left); freeFindEnemy(enemy_top_2, enemy_left_2); } };
函數(shù)整體的內(nèi)容有點(diǎn)小多,咱們來分開解釋:
自殺判斷
因?yàn)樵谥鹘且苿?dòng)時(shí),敵人的自動(dòng)索敵功能也會開啟,所以導(dǎo)致當(dāng)主角向敵人移動(dòng)的時(shí)候因?yàn)閿橙俗詣?dòng)索敵的原因會與主角錯(cuò)開,于是便誕生了這個(gè)邏輯,就是判斷如果主角的下一步有敵人的話,敵人原地不動(dòng),裝上敵人game over
// 自殺判斷 if ( left.value == enemy_left.value + speed.value && top.value == enemy_top.value ) { left.value -= speed.value; return false;// 如自殺成功則阻止下面的索敵判斷 }
邊界判斷
如果出界會被攔截并且給一個(gè)被攔截的效果提示,因?yàn)檫@個(gè)示例是想左移動(dòng)的時(shí)候,所以判斷條件也是左邊
if (left.value == 0) { // 這個(gè)效果可以讓方塊回彈一下 left.value = -20; setTimeout(() => { left.value = 0; }, 100); return false;// 如果碰到邊界則阻止像下面的索敵判斷 }
障礙判斷 && 索敵
如果關(guān)卡中存在障礙的話,當(dāng)主角觸碰到障礙的時(shí)候,會跟邊界判斷擁有一樣回彈效果來提示此路不通
如果主角移動(dòng)沒有碰到障礙阻攔的話,則執(zhí)行正常移動(dòng)的命令并且執(zhí)行自動(dòng)索敵
obstacle = obstacle_left.value + speed.value; if (top.value == obstacle_top.value && left.value == obstacle) { // 跟上面一樣,回彈一下 left.value = obstacle - 20; setTimeout(() => { left.value = obstacle; }, 100); } else { left.value -= speed.value;// 移動(dòng)命令 freeFindEnemy(enemy_top, enemy_left);// 敵人1的索敵 freeFindEnemy(enemy_top_2, enemy_left_2);// 敵人2的索敵 }
也許你已經(jīng)看到了(朵拉擺手),在索敵的最后使用的兩個(gè)函數(shù),這個(gè)函數(shù)就是自動(dòng)索敵的邏輯,接下來繼續(xù)深入~
當(dāng)主角移動(dòng)時(shí)敵人自動(dòng)索敵
// 自動(dòng)索敵 const freeFindEnemy = (Etop: any, Eleft: any) => { let _top = top.value - Etop.value; let _left = left.value - Eleft.value; if (Math.abs(_top) > Math.abs(_left)) { if (_top > 0) { moveEnemyS(Etop, Eleft); } else { moveEnemyW(Etop, Eleft); } } else { if (_left > 0) { moveEnemyD(Etop, Eleft); } else { moveEnemyA(Etop, Eleft); } } };
這個(gè)里面出現(xiàn)的函數(shù)moveEnemy系列是敵人方塊的方向移動(dòng),邏輯就是判斷主角距離敵人的top和left來決定敵人方塊的走向,Etop與Eleft需要分別傳入的敵人的top和left值,判斷拿邊距離大就往哪邊行動(dòng),有大于、小于等于兩種情況
由自動(dòng)索敵又延申出了--敵人移動(dòng)
敵人移動(dòng)也是擁有四個(gè)函數(shù),基本與主角移動(dòng)沒有區(qū)別,但是敵人在碰到障礙的時(shí)候會選擇繞開,且敵人碰到陷阱的時(shí)候會被“吃掉”
拿敵人向下移動(dòng)來舉例
const moveEnemyS = (Etop: any, Eleft: any) => { // 陷阱判斷 if (trap_top.value == Etop.value && trap_left.value == Eleft.value) return; // 障礙檢測判斷 obstacle = obstacle_top.value - speed.value; if (Etop.value == obstacle && Eleft.value == obstacle_left.value) { // 判斷如果碰到障礙 let _left = left.value - Eleft.value; if (_left > 0) { Eleft.value += speed.value; } else { Eleft.value -= speed.value; } } else { Etop.value += speed.value; } };
首先是陷阱的判斷,如果敵人的top和left與陷阱一致的話則判斷敵人掉進(jìn)了陷阱里,將終止敵人的所有移動(dòng)
接下來是障礙,判斷如果敵人即將要走的方向有障礙擋著的話,就去判斷與主角的距離來向左或者向右避開
在勝利和失敗后肯定是要終止所有行動(dòng)的,正好所有的行動(dòng)也是由主角移動(dòng)的函數(shù)來觸發(fā)的,所以先聲明一個(gè)變量用來控制游戲的進(jìn)行,然后通過按鍵在判斷這個(gè)變量,如果游戲正在進(jìn)行中則觸發(fā)移動(dòng)函數(shù)函數(shù),如果游戲未開始或已失敗則跳過觸發(fā)事件,即無響應(yīng)
case "a": // is_run即聲明的變量,在游戲失敗或未開始階段該變量為false if (is_run.value) { moveProtagonistA(); } break;
當(dāng)勝利條件符合(即主角碰到終點(diǎn))時(shí),觸發(fā)win,即顯示win字樣并使is_run置為false
// 主角的topleft是否與終點(diǎn)的topleft重合 if (top.value == spot_top.value && left.value == spot_left.value) { winShow.value = true; is_run.value = false; }
當(dāng)失敗條件符合(即主角碰到敵人1或2或者陷阱)時(shí),觸發(fā)lose,即顯示lose字樣并使is_run置為false
if ( (top.value == enemy_top.value && left.value == enemy_left.value) || (top.value == enemy_top_2.value && left.value == enemy_left_2.value) || (top.value == trap_top.value && left.value == trap_left.value) ) { is_run.value = false; loseShow.value = true; return; }
最后一個(gè)return的作用是截?cái)?,?dāng)觸發(fā)了lose后就不再繼續(xù)執(zhí)行了(否則會接著執(zhí)行win)
16個(gè)黑塊,通過鼠標(biāo)移入移出判斷顏色
<div v-for="(item, index) in blockList" :key="index" :style="{ width: `${speed}px`, height: `${speed}px`, background: item.background, }" @mousemove="editMove($event, item)" @mouseleave="editLeave" class="transition-all" ></div> <!-- transition-all使樣式變換具有過渡效果 -->
const editMove = (event, item) => { // 如果該方塊已經(jīng)被選中則什么都不做 if (!item.is_confirm) { for (let i in blockList.value) { // 選中相應(yīng)的方塊進(jìn)行變色 if (blockList.value[i].id == item.id) { blockList.value[i].background = ""; } else if (blockList.value[i].is_confirm) { blockList.value[i].background = ""; } else { blockList.value[i].background = "#000"; } } } }; const editLeave = () => { for (let i in blockList.value) { // 如果該方塊已經(jīng)被選中則什么都不做 if (blockList.value[i].is_confirm) { blockList.value[i].background = ""; } else { // 選中相應(yīng)的方塊進(jìn)行變色 blockList.value[i].background = "#000"; } } };
因?yàn)榉綁K被設(shè)置后是不能被改變顏色的,所以需要這兩個(gè)方法對已經(jīng)被設(shè)置的方塊進(jìn)行判斷
需先點(diǎn)擊左側(cè)圖例使顏色選中,再點(diǎn)擊方塊使其變色
圖例
<div v-for="(item, index) in legendList" :key="index" class="flex mb-4 items-center text-xl" @click="colorClick($event, item)" > <div class="legend_sign" :class="item.color"></div> <div class="w-10"></div> <div class="transition-all p-2 rounded-lg" :class="color == item.color ? color : ''" > {{ item.introduce }} </div> </div>
const legendList = [ { id: 0, color: "bg-green-500", introduce: "終點(diǎn)", }, { id: 1, color: "bg-red-500", introduce: "敵人", }, { id: 2, color: "bg-blue-500", introduce: "主角", }, { id: 3, color: "bg-gray-500", introduce: "障礙", }, { id: 4, color: "bg-purple-500", introduce: "陷阱", }, ];
變色邏輯
<!-- 跟移入移出變色的div是同一個(gè)div --> <!-- 重點(diǎn)看這句::class="item.color" --> <div v-for="(item, index) in blockList" :key="index" :style="{ width: `${speed}px`, height: `${speed}px`, background: item.background, }" :class="item.color" @click="editClick($event, item)" @mousemove="editMove($event, item)" @mouseleave="editLeave" class="transition-all" ></div>
const editMove = (event, item) => { if (!item.is_confirm) { for (let i in blockList.value) { if (blockList.value[i].id == item.id) { // 重點(diǎn)在這兩句 blockList.value[i].background = ""; blockList.value[i].color = color.value; } else if (blockList.value[i].is_confirm) { blockList.value[i].background = ""; } else { blockList.value[i].background = "#000"; } } } }; const editClick = (event, item) => { // json添加 switch (color.value) { case "bg-green-500": if (json.spot_top != 9999) { tips.value = "終點(diǎn)只能有一個(gè)"; return; } json.spot_top = item.top; json.spot_left = item.left; break; case "bg-red-500": if (json.enemy_top != 9999) { if (json.enemy_top_2 != 9999) { tips.value = "敵人只能有兩個(gè)"; return; } json.enemy_top_2 = item.top; json.enemy_left_2 = item.left; break; } json.enemy_top = item.top; json.enemy_left = item.left; break; case "bg-blue-500": if (json.top != 9999) { tips.value = "主角只能有一個(gè)"; return; } json.top = item.top; json.left = item.left; break; case "bg-gray-500": if (json.obstacle_top != 9999) { tips.value = "障礙只能有一個(gè)"; return; } json.obstacle_top = item.top; json.obstacle_left = item.left; break; case "bg-purple-500": if (json.trap_top != 9999) { tips.value = "陷阱只能有一個(gè)"; return; } json.trap_top = item.top; json.trap_left = item.left; break; default: tips.value = "請先選擇顏色~"; return; } // 狀態(tài)保留 for (let i in blockList.value) { if (blockList.value[i].id == item.id) { blockList.value[i].background = ""; blockList.value[i].color = color.value; blockList.value[i].is_confirm = true; } else if (blockList.value[i].is_confirm) { blockList.value[i].background = ""; } else { blockList.value[i].background = "#000"; } } };
首先是通過點(diǎn)擊圖例來保存顏色,然后在鼠標(biāo)移入黑塊的時(shí)候不再是白色,而是選中的顏色,在點(diǎn)擊的時(shí)候能將顏色固定到黑塊上
因?yàn)閟tyle的優(yōu)先級要比class大(background比bg-red-500大),所以在懸浮時(shí)需要將背景顏色去掉:
blockList.value[i].background = ""; blockList.value[i].color = color.value;
在點(diǎn)擊的時(shí)候需要保留這個(gè)顏色,所以在點(diǎn)擊的時(shí)候要將本來的顏色改變,并且在懸浮上去后不會變色
blockList.value[i].background = ""; blockList.value[i].color = color.value; blockList.value[i].is_confirm = true;
is_confirm在上面已經(jīng)出現(xiàn)過一兩次,表示的是這個(gè)塊是否被設(shè)置,如果被設(shè)置了則不對它做任何操作
const editMove = (event, item) => { if (!item.is_confirm) { ... } };
對每個(gè)被設(shè)置的塊記住位置,在點(diǎn)擊保存關(guān)卡的時(shí)候?qū)⑺诺奖镜卮鎯?,這樣一個(gè)新的關(guān)卡就生成了
【gif保存關(guān)卡】
初始時(shí)將所有top left全都設(shè)置為9999,在點(diǎn)擊方塊的時(shí)候記錄方塊的top left和顏色來向一個(gè)數(shù)組中傳入數(shù)據(jù),并且對塊的數(shù)量做出限制,這里拿主角來舉例:
switch(color.value){ case "bg-blue-500": if (json.top != 9999) { tips.value = "主角只能有一個(gè)"; return; } // 將主角的top lef填入對應(yīng)的地方 json.top = item.top; json.left = item.left; break; }
在點(diǎn)擊保存關(guān)卡時(shí)將數(shù)組添加進(jìn)本地存儲
const Level = JSON.parse(localStorage.getItem("data")); let json = { id: Level.length + 1, speed: 176, top: 9999, left: 9999, enemy_top: 9999, enemy_left: 9999, enemy_top_2: 9999, enemy_left_2: 9999, obstacle_top: 9999, obstacle_left: 9999, trap_top: 9999, trap_left: 9999, spot_top: 9999, spot_left: 9999, }; ... const saveClick = () => { Level.push(json); localStorage.setItem("data", JSON.stringify(Level)); button_text.value = "保存成功"; router.push("/main"); };
以上就是“Vue+TailWindcss怎么實(shí)現(xiàn)一個(gè)簡單的闖關(guān)小游戲”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學(xué)習(xí)更多的知識,請關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。