最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于Vue實(shí)現(xiàn)簡單的貪食蛇游戲

 更新時(shí)間:2022年04月22日 10:14:35   作者:AntPro  
貪食蛇是一個(gè)非常經(jīng)典的游戲,?在游戲中,?玩家操控一條細(xì)長的直線,?它會(huì)不停前進(jìn),?玩家只能操控蛇的頭部朝向,?一路拾起觸碰到之物。本文將用Vue實(shí)現(xiàn)這一游戲,感興趣的可以嘗試一下

貪食蛇是一個(gè)非常經(jīng)典的游戲, 在游戲中, 玩家操控一條細(xì)長的直線(俗稱蛇或蟲), 它會(huì)不停前進(jìn), 玩家只能操控蛇的頭部朝向(上下左右), 一路拾起觸碰到之物(或稱作“豆”), 并要避免觸碰到自身或者邊界. 每次貪吃蛇吃掉一件食物, 它的身體便增長一些.

本項(xiàng)目使用的技術(shù)棧和標(biāo)題一樣非常的簡單, 只有用到 vue, 主要實(shí)現(xiàn)使用的是 HTML + CSS 動(dòng)畫

代碼實(shí)現(xiàn)可以參考: CodeSandbox

實(shí)現(xiàn)游戲棋盤

在游戲描述中有提到, 玩家操縱的蛇要避免觸碰到自身或者邊界. 這就需要我們實(shí)現(xiàn)一個(gè)有邊界的游戲棋盤.

在 html 中, 我們可以使用 css 的 width、border 和 height 屬性來實(shí)現(xiàn)一個(gè)簡單的、具有邊界的容器:

在 App.vue 中的實(shí)現(xiàn)(功能節(jié)選)

<template>
  <div class="game-box"></div>
</template>
<style>
body {
  display: flex;
  width: 100vw;
  height: 100vh;
  margin: 0;
}
.game-box {
  position: relative;
  width: 500px;
  height: 500px;
  border: 1px solid #ddd;
  margin: auto;
}
</style>

其中 position: relative; 是為了之后的 position: absolute 元素能夠在游戲棋盤中的顯示正確的位置.

實(shí)現(xiàn)蛇與豆的實(shí)體

展示豆的方式可以使用一個(gè) div 元素, 使用 position: absolute 與 left、top 屬性來實(shí)現(xiàn)豆的位置:

在 App.vue 中的實(shí)現(xiàn)(功能節(jié)選)

<template>
  <div class="game-box">
    <div class="snake-food" :style="{ top: foodPos.y + 'px', left: foodPos.x + 'px' }" />
  </div>
</template>
<script>
export default {
  data() {
    return {
      foodPos: {},
    };
  },
};
</script>
<style>
.snake-foot {
  position: absolute;
  /* 保證初始位置不可見 */
  top: -9999px;
  left: -9999px;
  width: 10px;
  height: 10px;
  /* 你也可以與眾不同 */
  background-color: rgb(207, 38, 38);
  z-index: 2;
}
</style>

實(shí)現(xiàn)蛇就需要稍稍拆解一下需求. 我們知道蛇在吃了豆之后, 就會(huì)增長一些. 這看起來就像是一條單向的鏈表, 在蛇吃到豆之后便插入一條. 而且插入數(shù)據(jù)的部分只有在其尾部, 并不需要鏈表的便捷插入特性, 所以我們可以使用一個(gè)保存位置信息的數(shù)組來實(shí)現(xiàn)蛇的身體. 并且獨(dú)立出蛇的頭部來引導(dǎo)蛇的移動(dòng). 在這里我們保留了指向尾部的引用, 以便在蛇吃到豆之后, 可以快速的將新的蛇尾插入到最后:

在 App.vue 中的實(shí)現(xiàn)(功能節(jié)選)

<template>
  <div class="game-box">
    <div ref="snake" class="snake">
      <!-- 蛇的頭部用來引導(dǎo)蛇的移動(dòng) -->
      <div :style="{ top: headerPos.y + 'px', left: headerPos.x + 'px' }" ref="snakeHeader" class="snake-header" />
      <!-- 蛇的身體, 使用連續(xù)的數(shù)組實(shí)現(xiàn) -->
      <div
        :key="uuid"
        :uid="uuid"
        v-for="{ pos: { y, x }, uuid } in snakeBodyList"
        :style="{ top: y + 'px', left: x + 'px' }"
        class="snake-body"
      />
    </div>
  </div>
</template>
<script>
// 蛇身的大小單位
const defaultUnit = 10;

function updatePos(pos, direction) {
  // 規(guī)避引用
  const newPos = { ...pos };
  switch (direction) {
    case directionKeys.up:
      newPos.y -= defaultUnit;
      break;
    case directionKeys.down:
      newPos.y += defaultUnit;
      break;
    case directionKeys.left:
      newPos.x -= defaultUnit;
      break;
    case directionKeys.right:
      newPos.x += defaultUnit;
      break;
    default:
      throw new Error('NotFind');
  }
  return newPos;
}

export default {
  data() {
    return {
      // 蛇身自增的 uuid
      id: 0,
      // 蛇的頭部位置
      headerPos: {},
      // 保存尾部的位置信息
      lastPos: {},
      // 保存蛇的身體位置信息
      snakeBodyList: [],
    };
  },
  methods: {
    init() {
      // 初始化數(shù)據(jù)
      const initData = { x: 250, y: 250 };
      this.direction = directionKeys.left;
      this.lastPos = { ...initData, direction: this.direction };
      this.headerPos = { ...initData };
      this.snakeBodyList = Array(defaultUnit).fill(0).map(this.createBody);
    },
    createBody() {
      const { x, y } = this.lastPos;
      // 判斷是否屬于同水平方向
      const isLower = this.direction === directionKeys.up || this.direction === directionKeys.left;
      const pos = {
        // 同水平方向剛好差 2 的數(shù)值, 40 - 38 = 2, 39 - 37 = 2
        ...updatePos({ x, y }, isLower ? this.direction + 2 : this.direction - 2),
      };
      // 保存尾部的位置信息
      this.lastPos = pos;
      return {
        uuid: this.id++,
        pos,
      };
    },
  },
};
</script>

當(dāng)我們需要添加新的蛇身時(shí), 只需要調(diào)用 createBody 方法, 并將其添加至蛇的身體數(shù)組尾部即可:

// 使用push方法添加蛇身至身體數(shù)組尾部
this.snakeBodyList.push(this.createBody());

實(shí)現(xiàn)蛇的移動(dòng)方向(輸入控制)

我們知道, 用戶在鍵入一個(gè)按鍵時(shí), 如果我們有監(jiān)聽 keydown 事件, 瀏覽器會(huì)觸發(fā)回調(diào)函數(shù)并提供一個(gè)KeyboardEvent 對(duì)象. 當(dāng)我們要使用鍵盤來控制蛇的移動(dòng)方向時(shí), 就可以使用該事件對(duì)象的 keyCode 屬性來獲取鍵盤按鍵的編碼.

其中 keyCode 屬性的值可以參考 鍵盤編碼.

實(shí)現(xiàn)這個(gè)功能我們可以在全局對(duì)象 window 上添加一個(gè) keydown 事件監(jiān)聽函數(shù), 并將鍵盤按鍵的編碼保存在實(shí)例中, 考慮到用戶可能會(huì)輸入多個(gè)鍵盤按鍵, 所以我們需要檢查是否為方向鍵, 并且跳過同一個(gè)水平方向上的輸入:

在 App.vue 中的實(shí)現(xiàn)(功能節(jié)選)

<script>
// 方向鍵的鍵盤按鍵的編碼
const directionKeys = {
  up: 38,
  down: 40,
  left: 37,
  right: 39,
};

// 檢查是否在水平方向上
function checkIsLevel(direction) {
  return direction === directionKeys.right || direction === directionKeys.left;
}

export default {
  data () {
    return {
      // 當(dāng)前的方向鍵的編碼
      direction: undefined,
      // 最終輸入的方向鍵的編碼
      lastInputDirection: undefined,
    }
  }
  mounted() {
    window.addEventListener('keydown', this.onKeydown);
  },
  methods: {
    onKeydown(e) {
      if (
        // 檢查是否為方向鍵
        ![38, 40, 37, 39].includes(keyCode) ||
        // 檢查是否在同一個(gè)水平方向上
        checkIsLevel(keyCode) === checkIsLevel(this.direction)
      ) {
        return;
      }
      // 保存輸入的方向
      this.lastInputDirection = keyCode;
    },
  },
};
</script>

碰撞檢測(cè)

游戲要求玩家避免觸碰到自身或者邊界, 我們自然而然的就需要去檢測(cè)它們是否發(fā)生了碰撞.

檢測(cè)與自身碰撞的方法是, 判斷蛇頭的位置是否與蛇身體的位置相同:

// 檢測(cè)是否發(fā)生碰撞
function isRepeat(list, pos) {
  return list.some(({ pos: itemPos }) => pos.x === itemPos.x && pos.y === itemPos.y);
}

// 使用的地方傳入蛇身體數(shù)組和蛇頭的位置
isRepeat(snakeBodyList, headerPos);

而檢測(cè)與邊界碰撞的方法是, 判斷蛇頭的位置是否超出了游戲區(qū)域:

const MAX_X = 500;
const MAX_Y = 500;

// 檢測(cè)是否超出邊界
function isCrossedLine(x, y) {
  // 因?yàn)槭鞘褂胮osition, 我們的位置計(jì)算需要考慮到 { x: 0, y: 0 } 的位置不為邊界
  return x >= MAX_X || x < 0 || y >= MAX_Y || y < 0;
}

當(dāng)蛇頭的位置將要超出了游戲區(qū)域或者與蛇身體的位置相同時(shí), 游戲結(jié)束:

const next = updatePos(this.headerPos, this.direction);
if (isCrossedLine(next.x, next.y) || isRepeat(this.snakeBodyList, next)) {
  alert('你輸了');
  return;
}

實(shí)現(xiàn)渲染動(dòng)畫

為了寫出渲染動(dòng)畫, 我們需要嘗試?yán)斫馍叩倪\(yùn)動(dòng)方式.

當(dāng)玩家輸入操作的時(shí)候, 蛇會(huì)根據(jù)用戶輸入的方向進(jìn)行移動(dòng), 在這個(gè)過程中蛇頭的位置會(huì)發(fā)生變化, 而蛇身體的位置也會(huì)隨之發(fā)生變化. 仔細(xì)觀察可以發(fā)現(xiàn), 其實(shí)不斷變化的每個(gè)蛇身就是將它的位置替換成上一個(gè)蛇身的位置:

let head = this.headerPos;
const snakeBodyList = this.snakeBodyList;
for (const body of snakeBodyList) {
  const nextPos = body.pos;
  body.pos = head;
  head = nextPos;
}

除了這種逐步更新的方式也可以使用更簡單的直接更新數(shù)組的方式, 比如:

這樣會(huì)使 uuid 無法更新, vue 不會(huì)重新渲染 DOM, 導(dǎo)致 transition 無法生效

// 移除蛇尾
const snakeBodyList = this.snakeBodyList.slice(0, this.snakeBodyList.length - 1);
// 添加當(dāng)前的蛇頭至蛇身的最前方
snakeBodyList.unshift({ pos: this.headerPos, uuid: this.id++ });

而當(dāng)蛇頭觸碰到豆的時(shí)候, 豆會(huì)被消除并且延長蛇身:

if (isRepeat(snakeBodyList, this.foodPos)) {
  snakeBodyList.push(this.createBody());
}

有了檢測(cè)邏輯, 我們?cè)賹?dòng)畫添加上. 因?yàn)樯呤且徊揭徊降囊苿?dòng), 所以可以使用 setTimeout 來實(shí)現(xiàn)動(dòng)畫:

render 函數(shù)最終會(huì)掛載在 vue 實(shí)例上

function render() {
  const next = updatePos(this.headerPos, this.lastInputDirection);
  if (isCrossedLine(next.x, next.y) || isRepeat(this.snakeBodyList, next)) {
    clearTimeout(this._timer);
    alert('你輸了');
    return;
  }
  const snakeBodyList = this.snakeBodyList.slice(0, this.snakeBodyList.length - 1);
  snakeBodyList.unshift({ pos: this.headerPos, uuid: this.id++ });
  this.headerPos = next;
  this.lastPos = snakeBodyList[snakeBodyList.length - 1].pos;
  if (isRepeat(snakeBodyList, this.foodPos)) {
    snakeBodyList.push(this.createBody());
  }
  this.snakeBodyList = snakeBodyList;
  this.direction = this.lastInputDirection;
  this._timer = setTimeout(() => this.render(), 100);
}

最后的潤色

我們添加一下生成豆的方法, 并且保證它的位置不會(huì)出現(xiàn)在游戲區(qū)域的邊界或者蛇身體的位置上:

genFoot 函數(shù)最終會(huì)掛載在 vue 實(shí)例上

// 生成隨機(jī)數(shù)
function genRandom(max, start) {
  return start + (((Math.random() * (max - start)) / start) >>> 0) * start;
}

// 隨機(jī)生成豆的位置
function genFoot() {
  const x = genRandom(MAX_X, defaultUnit);
  const y = genRandom(MAX_Y, defaultUnit);
  // 如果出現(xiàn)在游戲區(qū)域的邊界或者蛇身體的位置上則重新生成
  if (isRepeat(this.snakeBodyList, { x, y }) || isCrossedLine(x, y)) {
    this.genFoot();
  } else {
    this.foodPos = { x, y };
  }
}

// 添加到render方法中
function render() {
  // ...
  if (isRepeat(snakeBodyList, this.foodPos)) {
    snakeBodyList.push(this.createBody());
    this.genFoot();
  }
  // ...
}

再添加一下開始與結(jié)束游戲, 以及一些展示當(dāng)前蛇的信息的地方:

在 App.vue 中的實(shí)現(xiàn)(功能節(jié)選)

<template>
  <div class="game-box">
    <div class="tools">
      <button @click="playGame">
        {{ isPlaying ? '停止' : isLose ? '重新開始' : '開始' }}
      </button>
      <div class="info-bar">
        <p>?? 的長度: {{ snakeBodyList.length }}</p>
      </div>
      <p class="count">得分: {{ count }}</p>
    </div>
  </div>
</template>
<script>
export default {
  data: () => ({
    // 游戲狀態(tài)
    isPlaying: false,
    // 是否失敗
    isLose: false,
    // 蛇的步行速度
    speed: 100,
  }),
  methods: {
    playGame() {
      if (this.isPlaying) {
        clearTimeout(this._timer);
      } else {
        this.isLose = false;
        this.init();
        this.genFoot();
        this.render();
      }
      this.isPlaying = !this.isPlaying;
    },
  },
};
</script>

這樣我們就使用 vue 實(shí)現(xiàn)了一個(gè)簡單的貪吃蛇游戲了.

效果圖

以上就是基于Vue實(shí)現(xiàn)簡單的貪食蛇游戲的詳細(xì)內(nèi)容,更多關(guān)于Vue貪食蛇游戲的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解uniapp如何解決H5頁面雙標(biāo)題

    詳解uniapp如何解決H5頁面雙標(biāo)題

    Uniapp打包到線上,會(huì)出現(xiàn)雙標(biāo)題問題,原因是:通過地址鏈接訪問時(shí),會(huì)有個(gè)瀏覽器自身的標(biāo)題,和uniapp自身的導(dǎo)航欄標(biāo)題重復(fù),本文給大家介紹了uniapp如何解決H5頁面雙標(biāo)題,需要的朋友可以參考下
    2024-10-10
  • 一起寫一個(gè)即插即用的Vue Loading插件實(shí)現(xiàn)

    一起寫一個(gè)即插即用的Vue Loading插件實(shí)現(xiàn)

    這篇文章主要介紹了一起寫一個(gè)即插即用的Vue Loading插件實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Vue中在新窗口打開頁面及Vue-router的使用

    Vue中在新窗口打開頁面及Vue-router的使用

    這篇文章主要介紹了Vue中在新窗口打開頁面 及 Vue-router的使用,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-06-06
  • vue3中使用keepAlive緩存路由組件不生效的問題解決

    vue3中使用keepAlive緩存路由組件不生效的問題解決

    這篇文章主要介紹了vue3中使用keepAlive緩存路由組件不生效的問題解決,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-06-06
  • vue 彈出框 引入另一個(gè)vue頁面的示例代碼

    vue 彈出框 引入另一個(gè)vue頁面的示例代碼

    這篇文章主要介紹了vue 彈出框引入另一個(gè)vue頁面,這種方式適用于在一個(gè)頁面邏輯比較多的時(shí)候,可以搞多個(gè)頁面,防止出錯(cuò),本文通過示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-08-08
  • vue 使用 vue-pdf 實(shí)現(xiàn)pdf在線預(yù)覽的示例代碼

    vue 使用 vue-pdf 實(shí)現(xiàn)pdf在線預(yù)覽的示例代碼

    這篇文章主要介紹了vue 使用 vue-pdf 實(shí)現(xiàn)pdf在線預(yù)覽的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • vue跨域處理方式(vue項(xiàng)目中baseUrl設(shè)置問題)

    vue跨域處理方式(vue項(xiàng)目中baseUrl設(shè)置問題)

    這篇文章主要介紹了vue跨域處理方式(vue項(xiàng)目中baseUrl設(shè)置問題),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • 教你使用vue3寫Json-Preview的示例代碼

    教你使用vue3寫Json-Preview的示例代碼

    這篇文章主要介紹了用vue3寫了一個(gè)Json-Preview的相關(guān)知識(shí),引入后直接<json-preview?v-model="jsonData"></json-preview>就可以使用了,本文通過示例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-06-06
  • vue打包的時(shí)候自動(dòng)將px轉(zhuǎn)成rem的操作方法

    vue打包的時(shí)候自動(dòng)將px轉(zhuǎn)成rem的操作方法

    這篇文章主要介紹了vue打包的時(shí)候自動(dòng)將px轉(zhuǎn)成rem的操作方法,需要的朋友可以參考下
    2018-06-06
  • Vue3中如何使用異步請(qǐng)求示例詳解

    Vue3中如何使用異步請(qǐng)求示例詳解

    Vue3增加了很多讓人眼前一亮的特征,suspense 組件就是其中之一,對(duì)處理異步請(qǐng)求數(shù)據(jù)非常實(shí)用,下面這篇文章主要給大家介紹了關(guān)于Vue3中如何使用異步請(qǐng)求的相關(guān)資料,需要的朋友可以參考下
    2022-06-06

最新評(píng)論

高雄市| 佳木斯市| 岑溪市| 汤原县| 沽源县| 澎湖县| 旅游| 宝兴县| 长顺县| 郓城县| 玉山县| 滦南县| 西林县| 西贡区| 清新县| 闵行区| 诸城市| 九江市| 进贤县| 仙游县| 蛟河市| 镇赉县| 繁昌县| 郓城县| 姜堰市| 海伦市| 泽普县| 麻栗坡县| 深州市| 哈巴河县| 安岳县| 东至县| 凤翔县| 内黄县| 顺义区| 宜章县| 雷州市| 原阳县| 张家川| 瑞金市| 仪陇县|