欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue+TailWindcss實現(xiàn)一個簡單的闖關(guān)小游戲

 更新時間:2022年04月11日 14:28:22   作者:白日夢企鵝  
本文將利用Vue+TailWindcss實現(xiàn)一個簡單的闖關(guān)小游戲,玩家須躲避敵人與陷阱到達(dá)終點且擁有多個關(guān)卡,感興趣的小伙伴可以了解一下

游戲介紹

這是一款2d益智闖關(guān)游戲,玩家須躲避敵人與陷阱到達(dá)終點 擁有多個關(guān)卡

可進(jìn)行關(guān)卡的自定義并留存數(shù)據(jù)

實現(xiàn)技術(shù)

vue tailwindcss

本游特色

  • 自定義關(guān)卡
  • 敵人自動索敵
  • 低技術(shù)力
  • you win!

技術(shù)實現(xiàn)

初始化頁面

創(chuàng)建一個json文件,用來存放初始關(guān)卡的變量(只有一關(guān)。。。) 為方塊設(shè)定大小,初始化變量speed設(shè)置為176,棋盤的寬高就各位4個speed,方塊寬高就是1個speed,方塊移動一格就是speed * 1,兩格就是speed * 2

<!-- 棋盤 -->
<div :style="{ width: `${speed * 4}px`, height: `${speed * 4}px` }">
    <!-- 每一個小方塊 -->
    <div :style="{ width: `${speed}px`,height: `${speed}px`,}"></div>
</div>
const speed = ref(176);

Level是一個json文件,里面放著第一關(guān)的各種變量,用來在沒有關(guān)卡的時候初始化一個關(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,// 終點top值
        "spot_left": 528// 終點left值
    }
]

在加載頁面的時候判斷是否有數(shù)據(jù)如果沒有的話添加

import Level from "../../api/level.json";
let res = JSON.parse(localStorage.getItem("data"));
if (!res) {
    localStorage.setItem("data", JSON.stringify(Level));
}

小方塊設(shè)置

使用絕對定位,用transition-all讓方塊看起來有動畫效果

<div class="absolute transition-all"></div>

為小方塊設(shè)置特定的top和left,聲明變量然后設(shè)置給小方塊上

<!-- 終點,我用的spot前綴 -->
<div :style="{ top: `${spot_top}px`,left: `${spot_left}px` }"></div>
<!-- 敵人,我用的enemy前綴(敵人2后綴直接-2) -->
<div :style="{ top: `${enemy_top}px`,left: `${enemy_left}px` }"></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;
  }
});

四個函數(shù)的意思分別是主角塊上下左右的移動,本質(zhì)其實都差不多,差別就在于每個的top和left是不同的,所以咱就挑一個詳細(xì)說明一下:

當(dāng)想讓主角向左移動時

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)容有點小多,咱們來分開解釋:

自殺判斷

因為在主角移動時,敵人的自動索敵功能也會開啟,所以導(dǎo)致當(dāng)主角向敵人移動的時候因為敵人自動索敵的原因會與主角錯開,于是便誕生了這個邏輯,就是判斷如果主角的下一步有敵人的話,敵人原地不動,裝上敵人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;// 如果碰到邊界則阻止像下面的索敵判斷
  }

障礙判斷 && 索敵

如果關(guān)卡中存在障礙的話,當(dāng)主角觸碰到障礙的時候,會跟邊界判斷擁有一樣回彈效果來提示此路不通

如果主角移動沒有碰到障礙阻攔的話,則執(zhí)行正常移動的命令并且執(zhí)行自動索敵

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的索敵
}

也許你已經(jīng)看到了(朵拉擺手),在索敵的最后使用的兩個函數(shù),這個函數(shù)就是自動索敵的邏輯,接下來繼續(xù)深入~

自動索敵

當(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);
    }
  }
};

這個里面出現(xiàn)的函數(shù)moveEnemy系列是敵人方塊的方向移動,邏輯就是判斷主角距離敵人的top和left來決定敵人方塊的走向,Etop與Eleft需要分別傳入的敵人的top和left值,判斷拿邊距離大就往哪邊行動,有大于、小于等于兩種情況

由自動索敵又延申出了--敵人移動

敵人移動

敵人移動也是擁有四個函數(shù),基本與主角移動沒有區(qū)別,但是敵人在碰到障礙的時候會選擇繞開,且敵人碰到陷阱的時候會被“吃掉”

拿敵人向下移動來舉例

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)了陷阱里,將終止敵人的所有移動

接下來是障礙,判斷如果敵人即將要走的方向有障礙擋著的話,就去判斷與主角的距離來向左或者向右避開

勝利與失敗

在勝利和失敗后肯定是要終止所有行動的,正好所有的行動也是由主角移動的函數(shù)來觸發(fā)的,所以先聲明一個變量用來控制游戲的進(jìn)行,然后通過按鍵在判斷這個變量,如果游戲正在進(jìn)行中則觸發(fā)移動函數(shù)函數(shù),如果游戲未開始或已失敗則跳過觸發(fā)事件,即無響應(yīng)

case "a":
  // is_run即聲明的變量,在游戲失敗或未開始階段該變量為false
  if (is_run.value) {
    moveProtagonistA();
  }
  break;

當(dāng)勝利條件符合(即主角碰到終點)時,觸發(fā)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;
  }

當(dāng)失敗條件符合(即主角碰到敵人1或2或者陷阱)時,觸發(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;
  }

最后一個return的作用是截斷,當(dāng)觸發(fā)了lose后就不再繼續(xù)執(zhí)行了(否則會接著執(zhí)行win)

編輯關(guān)卡

移入移出變色

16個黑塊,通過鼠標(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";
    }
  }
};

因為方塊被設(shè)置后是不能被改變顏色的,所以需要這兩個方法對已經(jīng)被設(shè)置的方塊進(jìn)行判斷

點擊設(shè)置

需先點擊左側(cè)圖例使顏色選中,再點擊方塊使其變色

圖例

<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;
  }
  // 狀態(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";
    }
  }
};

首先是通過點擊圖例來保存顏色,然后在鼠標(biāo)移入黑塊的時候不再是白色,而是選中的顏色,在點擊的時候能將顏色固定到黑塊上

因為style的優(yōu)先級要比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在上面已經(jīng)出現(xiàn)過一兩次,表示的是這個塊是否被設(shè)置,如果被設(shè)置了則不對它做任何操作

const editMove = (event, item) => {
  if (!item.is_confirm) {
    ...
  }
};

保存關(guān)卡

對每個被設(shè)置的塊記住位置,在點擊保存關(guān)卡的時候?qū)⑺诺奖镜卮鎯?,這樣一個新的關(guān)卡就生成了

【gif保存關(guān)卡】

初始時將所有top left全都設(shè)置為9999,在點擊方塊的時候記錄方塊的top left和顏色來向一個數(shù)組中傳入數(shù)據(jù),并且對塊的數(shù)量做出限制,這里拿主角來舉例:

switch(color.value){
  case "bg-blue-500":
      if (json.top != 9999) {
        tips.value = "主角只能有一個";
        return;
      }
      // 將主角的top lef填入對應(yīng)的地方
      json.top = item.top;
      json.left = item.left;
      break;
}

在點擊保存關(guān)卡時將數(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實現(xiàn)一個簡單的闖關(guān)小游戲的詳細(xì)內(nèi)容,更多關(guān)于Vue TailWindcss闖關(guān)游戲的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • axios取消請求與避免重復(fù)請求

    axios取消請求與避免重復(fù)請求

    在項目中經(jīng)常有一些場景會連續(xù)發(fā)送多個請求,而異步會導(dǎo)致最后得到的結(jié)果不是我們想要的,并且對性能也有非常大的影響,這篇文章主要給大家介紹了關(guān)于axios取消請求與避免重復(fù)請求的相關(guān)資料,需要的朋友可以參考下
    2021-06-06
  • vue3+ts代理的使用

    vue3+ts代理的使用

    本文主要介紹了vue3+ts代理的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-12-12
  • 深入理解Vue2.x的虛擬DOM diff原理

    深入理解Vue2.x的虛擬DOM diff原理

    本篇文章主要介紹了Vue2.x的虛擬DOM diff原理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • vue.js實現(xiàn)簡單輪播圖效果

    vue.js實現(xiàn)簡單輪播圖效果

    這篇文章主要為大家詳細(xì)介紹了vue.js實現(xiàn)簡單輪播圖效果的相關(guān)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • uni-popup手寫菜鳥上門取件時間選擇器

    uni-popup手寫菜鳥上門取件時間選擇器

    這篇文章主要為大家介紹了uni-popup手?jǐn)]了一個菜鳥上門取件時間選擇器,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • vue中更改數(shù)組中屬性,在頁面中不生效的解決方法

    vue中更改數(shù)組中屬性,在頁面中不生效的解決方法

    今天小編就為大家分享一篇vue中更改數(shù)組中屬性,在頁面中不生效的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-10-10
  • vue使用gitshot生成gif問題

    vue使用gitshot生成gif問題

    這篇文章主要介紹了vue使用gitshot生成gif問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • Vue實戰(zhàn)之掌握自定義指令

    Vue實戰(zhàn)之掌握自定義指令

    作為使用Vue的開發(fā)者,我們對Vue指令一定不陌生,諸如v-model、v-on、v-for、v-if等,同時Vue也為開發(fā)者提供了自定義指令的api,熟練的使用自定義指令可以極大的提高了我們編寫代碼的效率,讓我們可以節(jié)省時間開心的摸魚
    2022-11-11
  • 詳解在vue3中使用jsx的配置以及一些小問題

    詳解在vue3中使用jsx的配置以及一些小問題

    本文主要介紹了在vue3中使用jsx的配置以及一些小問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • vue+webpack實現(xiàn)異步加載三種用法示例詳解

    vue+webpack實現(xiàn)異步加載三種用法示例詳解

    這篇文章主要介紹了vue+webpack實現(xiàn)異步加載的三種用法,文中給大家提到了vue+webpack實現(xiàn)異步組件加載的代碼,非常不錯,具有參考借鑒價值,需要的朋友參考下吧
    2018-04-04

最新評論