您好,登錄后才能下訂單哦!
今天小編給大家分享一下Vue+TailWindcss怎么實現一個簡單的闖關小游戲的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
這是一款2d益智闖關游戲,玩家須躲避敵人與陷阱到達終點 擁有多個關卡
可進行關卡的自定義并留存數據
vue tailwindcss
自定義關卡
敵人自動索敵
低技術力
you win!
創建一個json文件,用來存放初始關卡的變量(只有一關。。。) 為方塊設定大小,初始化變量speed設置為176,棋盤的寬高就各位4個speed,方塊寬高就是1個speed,方塊移動一格就是speed * 1,兩格就是speed * 2
<!-- 棋盤 --> <div :> <!-- 每一個小方塊 --> <div :></div> </div>
const speed = ref(176);
Level是一個json文件,里面放著第一關的各種變量,用來在沒有關卡的時候初始化一個關卡
level.json
[ { "id": 1,// 第一關 "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,// 終點top值 "spot_left": 528// 終點left值 } ]
在加載頁面的時候判斷是否有數據如果沒有的話添加
import Level from "../../api/level.json"; let res = JSON.parse(localStorage.getItem("data")); if (!res) { localStorage.setItem("data", JSON.stringify(Level)); }
使用絕對定位,用transition-all讓方塊看起來有動畫效果
<div class="absolute transition-all"></div>
為小方塊設置特定的top和left,聲明變量然后設置給小方塊上
<!-- 終點,我用的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);
當按下相應按鍵后執行相應的函數
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; } });
四個函數的意思分別是主角塊上下左右的移動,本質其實都差不多,差別就在于每個的top和left是不同的,所以咱就挑一個詳細說明一下:
當想讓主角向左移動時
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); } };
函數整體的內容有點小多,咱們來分開解釋:
自殺判斷
因為在主角移動時,敵人的自動索敵功能也會開啟,所以導致當主角向敵人移動的時候因為敵人自動索敵的原因會與主角錯開,于是便誕生了這個邏輯,就是判斷如果主角的下一步有敵人的話,敵人原地不動,裝上敵人game over
// 自殺判斷 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);// 敵人1的索敵 freeFindEnemy(enemy_top_2, enemy_left_2);// 敵人2的索敵 }
也許你已經看到了(朵拉擺手),在索敵的最后使用的兩個函數,這個函數就是自動索敵的邏輯,接下來繼續深入~
當主角移動時敵人自動索敵
// 自動索敵 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); } } };
這個里面出現的函數moveEnemy系列是敵人方塊的方向移動,邏輯就是判斷主角距離敵人的top和left來決定敵人方塊的走向,Etop與Eleft需要分別傳入的敵人的top和left值,判斷拿邊距離大就往哪邊行動,有大于、小于等于兩種情況
由自動索敵又延申出了--敵人移動
敵人移動也是擁有四個函數,基本與主角移動沒有區別,但是敵人在碰到障礙的時候會選擇繞開,且敵人碰到陷阱的時候會被“吃掉”
拿敵人向下移動來舉例
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與陷阱一致的話則判斷敵人掉進了陷阱里,將終止敵人的所有移動
接下來是障礙,判斷如果敵人即將要走的方向有障礙擋著的話,就去判斷與主角的距離來向左或者向右避開
在勝利和失敗后肯定是要終止所有行動的,正好所有的行動也是由主角移動的函數來觸發的,所以先聲明一個變量用來控制游戲的進行,然后通過按鍵在判斷這個變量,如果游戲正在進行中則觸發移動函數函數,如果游戲未開始或已失敗則跳過觸發事件,即無響應
case "a": // is_run即聲明的變量,在游戲失敗或未開始階段該變量為false if (is_run.value) { moveProtagonistA(); } break;
當勝利條件符合(即主角碰到終點)時,觸發win,即顯示win字樣并使is_run置為false
// 主角的topleft是否與終點的topleft重合 if (top.value == spot_top.value && left.value == spot_left.value) { winShow.value = true; is_run.value = false; }
當失敗條件符合(即主角碰到敵人1或2或者陷阱)時,觸發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; }
最后一個return的作用是截斷,當觸發了lose后就不再繼續執行了(否則會接著執行win)
16個黑塊,通過鼠標移入移出判斷顏色
<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) => { // 如果該方塊已經被選中則什么都不做 if (!item.is_confirm) { for (let i in blockList.value) { // 選中相應的方塊進行變色 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) { // 如果該方塊已經被選中則什么都不做 if (blockList.value[i].is_confirm) { blockList.value[i].background = ""; } else { // 選中相應的方塊進行變色 blockList.value[i].background = "#000"; } } };
因為方塊被設置后是不能被改變顏色的,所以需要這兩個方法對已經被設置的方塊進行判斷
需先點擊左側圖例使顏色選中,再點擊方塊使其變色
圖例
<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: "終點", }, { 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是同一個div --> <!-- 重點看這句::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) { // 重點在這兩句 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 = "終點只能有一個"; 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 = "敵人只能有兩個"; 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 = "主角只能有一個"; return; } json.top = item.top; json.left = item.left; break; case "bg-gray-500": if (json.obstacle_top != 9999) { tips.value = "障礙只能有一個"; return; } json.obstacle_top = item.top; json.obstacle_left = item.left; break; case "bg-purple-500": if (json.trap_top != 9999) { tips.value = "陷阱只能有一個"; return; } json.trap_top = item.top; json.trap_left = item.left; break; default: tips.value = "請先選擇顏色~"; return; } // 狀態保留 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"; } } };
首先是通過點擊圖例來保存顏色,然后在鼠標移入黑塊的時候不再是白色,而是選中的顏色,在點擊的時候能將顏色固定到黑塊上
因為style的優先級要比class大(background比bg-red-500大),所以在懸浮時需要將背景顏色去掉:
blockList.value[i].background = ""; blockList.value[i].color = color.value;
在點擊的時候需要保留這個顏色,所以在點擊的時候要將本來的顏色改變,并且在懸浮上去后不會變色
blockList.value[i].background = ""; blockList.value[i].color = color.value; blockList.value[i].is_confirm = true;
is_confirm在上面已經出現過一兩次,表示的是這個塊是否被設置,如果被設置了則不對它做任何操作
const editMove = (event, item) => { if (!item.is_confirm) { ... } };
對每個被設置的塊記住位置,在點擊保存關卡的時候將它放到本地存儲里,這樣一個新的關卡就生成了
【gif保存關卡】
初始時將所有top left全都設置為9999,在點擊方塊的時候記錄方塊的top left和顏色來向一個數組中傳入數據,并且對塊的數量做出限制,這里拿主角來舉例:
switch(color.value){ case "bg-blue-500": if (json.top != 9999) { tips.value = "主角只能有一個"; return; } // 將主角的top lef填入對應的地方 json.top = item.top; json.left = item.left; break; }
在點擊保存關卡時將數組添加進本地存儲
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怎么實現一個簡單的闖關小游戲”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。