基于Vue实现简单的贪食蛇游戏

目录
  • 实现游戏棋盘
  • 实现蛇与豆的实体
  • 实现蛇的移动方向(输入控制)
  • 碰撞检测
  • 实现渲染动画
  • 最后的润色

贪食蛇是一个非常经典的游戏, 在游戏中, 玩家操控一条细长的直线(俗称蛇或虫), 它会不停前进, 玩家只能操控蛇的头部朝向(上下左右), 一路拾起触碰到之物(或称作“豆”), 并要避免触碰到自身或者边界. 每次贪吃蛇吃掉一件食物, 它的身体便增长一些.

本项目使用的技术栈和标题一样非常的简单, 只有用到 vue, 主要实现使用的是 HTML + CSS 动画

代码实现可以参考: CodeSandbox

实现游戏棋盘

在游戏描述中有提到, 玩家操纵的蛇要避免触碰到自身或者边界. 这就需要我们实现一个有边界的游戏棋盘.

在 html 中, 我们可以使用 css 的 width、border 和 height 属性来实现一个简单的、具有边界的容器:

在 App.vue 中的实现(功能节选)

<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 元素能够在游戏棋盘中的显示正确的位置.

实现蛇与豆的实体

展示豆的方式可以使用一个 div 元素, 使用 position: absolute 与 left、top 属性来实现豆的位置:

在 App.vue 中的实现(功能节选)

<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>

实现蛇就需要稍稍拆解一下需求. 我们知道蛇在吃了豆之后, 就会增长一些. 这看起来就像是一条单向的链表, 在蛇吃到豆之后便插入一条. 而且插入数据的部分只有在其尾部, 并不需要链表的便捷插入特性, 所以我们可以使用一个保存位置信息的数组来实现蛇的身体. 并且独立出蛇的头部来引导蛇的移动. 在这里我们保留了指向尾部的引用, 以便在蛇吃到豆之后, 可以快速的将新的蛇尾插入到最后:

在 App.vue 中的实现(功能节选)

<template>
  <div class="game-box">
    <div ref="snake" class="snake">
      <!-- 蛇的头部用来引导蛇的移动 -->
      <div :style="{ top: headerPos.y + 'px', left: headerPos.x + 'px' }" ref="snakeHeader" class="snake-header" />
      <!-- 蛇的身体, 使用连续的数组实现 -->
      <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) {
  // 规避引用
  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() {
      // 初始化数据
      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 的数值, 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>

当我们需要添加新的蛇身时, 只需要调用 createBody 方法, 并将其添加至蛇的身体数组尾部即可:

// 使用push方法添加蛇身至身体数组尾部
this.snakeBodyList.push(this.createBody());

实现蛇的移动方向(输入控制)

我们知道, 用户在键入一个按键时, 如果我们有监听 keydown 事件, 浏览器会触发回调函数并提供一个KeyboardEvent 对象. 当我们要使用键盘来控制蛇的移动方向时, 就可以使用该事件对象的 keyCode 属性来获取键盘按键的编码.

其中 keyCode 属性的值可以参考 键盘编码.

实现这个功能我们可以在全局对象 window 上添加一个 keydown 事件监听函数, 并将键盘按键的编码保存在实例中, 考虑到用户可能会输入多个键盘按键, 所以我们需要检查是否为方向键, 并且跳过同一个水平方向上的输入:

在 App.vue 中的实现(功能节选)

<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 {
      // 当前的方向键的编码
      direction: undefined,
      // 最终输入的方向键的编码
      lastInputDirection: undefined,
    }
  }
  mounted() {
    window.addEventListener('keydown', this.onKeydown);
  },
  methods: {
    onKeydown(e) {
      if (
        // 检查是否为方向键
        ![38, 40, 37, 39].includes(keyCode) ||
        // 检查是否在同一个水平方向上
        checkIsLevel(keyCode) === checkIsLevel(this.direction)
      ) {
        return;
      }
      // 保存输入的方向
      this.lastInputDirection = keyCode;
    },
  },
};
</script>

碰撞检测

游戏要求玩家避免触碰到自身或者边界, 我们自然而然的就需要去检测它们是否发生了碰撞.

检测与自身碰撞的方法是, 判断蛇头的位置是否与蛇身体的位置相同:

// 检测是否发生碰撞
function isRepeat(list, pos) {
  return list.some(({ pos: itemPos }) => pos.x === itemPos.x && pos.y === itemPos.y);
}

// 使用的地方传入蛇身体数组和蛇头的位置
isRepeat(snakeBodyList, headerPos);

而检测与边界碰撞的方法是, 判断蛇头的位置是否超出了游戏区域:

const MAX_X = 500;
const MAX_Y = 500;

// 检测是否超出边界
function isCrossedLine(x, y) {
  // 因为是使用position, 我们的位置计算需要考虑到 { x: 0, y: 0 } 的位置不为边界
  return x >= MAX_X || x < 0 || y >= MAX_Y || y < 0;
}

当蛇头的位置将要超出了游戏区域或者与蛇身体的位置相同时, 游戏结束:

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

实现渲染动画

为了写出渲染动画, 我们需要尝试理解蛇的运动方式.

当玩家输入操作的时候, 蛇会根据用户输入的方向进行移动, 在这个过程中蛇头的位置会发生变化, 而蛇身体的位置也会随之发生变化. 仔细观察可以发现, 其实不断变化的每个蛇身就是将它的位置替换成上一个蛇身的位置:

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

除了这种逐步更新的方式也可以使用更简单的直接更新数组的方式, 比如:

这样会使 uuid 无法更新, vue 不会重新渲染 DOM, 导致 transition 无法生效

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

而当蛇头触碰到豆的时候, 豆会被消除并且延长蛇身:

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

有了检测逻辑, 我们再将动画添加上. 因为蛇是一步一步的移动, 所以可以使用 setTimeout 来实现动画:

render 函数最终会挂载在 vue 实例上

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

最后的润色

我们添加一下生成豆的方法, 并且保证它的位置不会出现在游戏区域的边界或者蛇身体的位置上:

genFoot 函数最终会挂载在 vue 实例上

// 生成随机数
function genRandom(max, start) {
  return start + (((Math.random() * (max - start)) / start) >>> 0) * start;
}

// 随机生成豆的位置
function genFoot() {
  const x = genRandom(MAX_X, defaultUnit);
  const y = genRandom(MAX_Y, defaultUnit);
  // 如果出现在游戏区域的边界或者蛇身体的位置上则重新生成
  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();
  }
  // ...
}

再添加一下开始与结束游戏, 以及一些展示当前蛇的信息的地方:

在 App.vue 中的实现(功能节选)

<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: () => ({
    // 游戏状态
    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 实现了一个简单的贪吃蛇游戏了.

效果图

以上就是基于Vue实现简单的贪食蛇游戏的详细内容,更多关于Vue贪食蛇游戏的资料请关注我们其它相关文章!

(0)

相关推荐

  • Vue实现Chrome小恐龙游戏的示例代码

    目录 前言 复刻画面 动画效果 路面动画 障碍物动画 恐龙动画 响应事件 碰撞检测 部署 总结 前言 几年前,Google 给 Chrome 浏览器加了一个有趣的彩蛋:如果你在未联网的情况下访问网页,会看到 “Unable to connect to the Internet” 或 “No internet” 的提示,旁边是一只像素恐龙. 许多人可能觉得这只恐龙只是一个可爱的小图标,在断网的时候陪伴用户.但是后来有人按下空格键,小恐龙开始奔跑! 这只可爱的小恐龙是设计师 Sebastien Ga

  • 基于Vue uniapp实现贪吃蛇游戏

    目录 游戏演示 代码结构 渲染蛇身 控制蛇的方向 游戏演示 代码结构 详细代码结构如果需要请到github查看 <template> <view ref="body" class="content"> <view>蛇蛇目前:{{snakes.length}}米长</view> <view class="game-field"> <!-- 地面板块 --> <view c

  • vue实现打地鼠小游戏

    本文实例为大家分享了vue实现打地鼠小游戏的具体代码,供大家参考,具体内容如下 效果图如下: 代码如下: <template> <div class="game"> <h2>打地鼠游戏</h2> <div class="wraper"> <div class="item" v-for="n in TOTAL" :key="n"> <

  • Vue3+Canvas实现坦克大战游戏

    目录 前言 架构搭建 Canvas构造函数 画布绘制 文本渲染 画布重绘前的clear 核心:绘制函数 BattleCity构造函数 实现坦克的移动 坦克发射子弹的逻辑 总结 前言 记得几年前刚做前端开发的时候,跟着师傅用纯 es5 实现了这款坦克大战,可以说我入行前端是从 javaScript 小游戏开始的,时间已匆匆过去了数年,前端发展日新月异,各种新框架.新概念层出不穷,很容易就迷失在对各种新技术的盲目学习和应用中,真正的编程是什么呢?值得思考的问题. 我准备用 vue3 重新实现一下这款

  • VUE+Canvas实现财神爷接元宝小游戏

    之前的canvas小游戏系列欢迎大家戳: <VUE实现一个Flappy Bird~~~> <猜单词游戏> <VUE+Canvas 实现桌面弹球消砖块小游戏> <VUE+Canvas实现雷霆战机打字类小游戏> 如标题,这个游戏大家也玩过,随处可见,左右方向键控制财神移动,接住从天而降的金元宝等,时间一到,则游戏结束.先来看一下效果: 相比于之前的雷霆战机要打出四处飞的子弹,这次元素的运动轨迹就很单一了,垂直方向的珠宝和水平移动的财神爷,类似于之前的代码,这里就

  • Vue实现红包雨小游戏的示例代码

    目录 0 写在前面 1 准备工作 2 设计HTML+CSS样式 3 设计JavaScript逻辑 4 完整代码 0 写在前面 红包也叫压岁钱,是过农历春节时长辈给小孩儿用红纸包裹的礼金.据传明清时期,压岁钱大多数是用红绳串着赐给孩子.民国以后,则演变为用红纸包裹.中国的红包传统民族文化在民间如此,社区.公司也奉行如仪.除了春节以外,在其他喜庆场合,例如婚礼.新店开张等亦有送红包的习俗. 本期迎新春专题用代码制作一个 红包雨小游戏 ,效果如下所示.看完本文相信你也可以完成这样一个小游戏设计. 1

  • 基于Vue实现简单的贪食蛇游戏

    目录 实现游戏棋盘 实现蛇与豆的实体 实现蛇的移动方向(输入控制) 碰撞检测 实现渲染动画 最后的润色 贪食蛇是一个非常经典的游戏, 在游戏中, 玩家操控一条细长的直线(俗称蛇或虫), 它会不停前进, 玩家只能操控蛇的头部朝向(上下左右), 一路拾起触碰到之物(或称作“豆”), 并要避免触碰到自身或者边界. 每次贪吃蛇吃掉一件食物, 它的身体便增长一些. 本项目使用的技术栈和标题一样非常的简单, 只有用到 vue, 主要实现使用的是 HTML + CSS 动画 代码实现可以参考: CodeSan

  • 基于VUE实现简单的学生信息管理系统

    一.主要功能 本次任务主要是使用VUE来实现一个简单的学生信息管理系统,主要功能为: 1.显示所有学生的信息(默认为10个) 2. 点击按钮,显示出学号尾号为单数(或双数)的学生信息 3. 增加学生信息 4. 要求使用VUE中 父子组件间通信 二.实现思路 1.数据管理:使用json数组的方式来管理储存数据 2.显示学生信息:因为组件是可复用的 Vue 实例,所以在这里引入子组件(用来显示每个学生的信息),将主页作为父组件.主页(父组件)使用v-for循环显示子组件. 3.按单双号筛选查找学生:

  • 基于Pygame实现简单的贪吃蛇游戏

    目录 导入相关的包 设置屏幕大小以及基本参数 设置贪吃蛇的位置,以及移动的大小 绘制蛇 让蛇动起来 实现贪吃蛇拐弯 实现随机食物 吃食物 完整代码  导入相关的包 import pygame, sys, random from pygame.locals import * 设置屏幕大小以及基本参数 设置屏幕大小为400*400,mainClock = pygame.time.Clock()用来设置时间同步,不会根据计算机的运行来决定运行多少次, mainClock.tick(1) 一秒只会运行一

  • 基于vue.js路由参数的实例讲解——简单易懂

    vue中,我们构建单页面应用时候,一定必不可少用到vue-router vue-router 就是我们的路由,这个由vue官方提供的插件 首先在我们项目中安装vue-router路由依赖 第一种,我们提供命令行来安装 npm install vue-router --save 第二种,我们直接去官方github下载 https://github.com/vuejs/vue-router 路由参数设置 1,实例化一个路由,然后路由映射表中的地址带参数,这个参数就是路由的参数 接着给映射表中的路由设

  • 基于Vue.js实现数字拼图游戏

    先来看看效果图: 功能分析 当然玩归玩,作为一名Vue爱好者,我们理应深入游戏内部,一探代码的实现.接下来我们就先来分析一下要完成这样的一个游戏,主要需要实现哪些功能.下面我就直接将此实例的功能点罗列在下了: 1.随机生成1~15的数字格子,每一个数字都必须出现且仅出现一次 2.点击一个数字方块后,如其上下左右有一处为空,则两者交换位置 3.格子每移动一步,我们都需要校验其是否闯关成功 4.点击重置游戏按钮后需对拼图进行重新排序 以上便是本实例的主要功能点,可见游戏功能并不复杂,我们只需一个个攻

  • 基于vue-cli、elementUI的Vue超简单入门小例子(推荐)

    这个例子还是比较简单的,独立完成后,能大概知道vue是干嘛的,可以写个todoList的小例子. 开始写例子之前,先对环境的部署做点简单的介绍,其实和Vue官方的差不多. #如若没有安装过vue-cli,先全局安装一下vue-cli $ cnpm i -g vue-cli #到自己喜欢的目录下创建一个基于 webpack 模板的新项目 $ vue init webpack my-project # # #之后会有如下询问 ? Project name (my-project) #回车 ? Pro

  • 基于JS实现简单滑块拼图游戏

    成品效果 <body> <div id="game" style="position:relative"></div> </body> /** * js配置 */ var config = { width: 300, height: 300, img: "./img/fj.jpg", gameDom: document.getElementById("game"), row: 3

  • 原生js实现贪食蛇小游戏的思路详解

    先不多说先上图 下面是代码部分(这里你可以根据需要改变蛇头和身体还有食物的图片,然后默认的样式是使用纯颜色的如果没有更改我的背景图片的话------改这些图开始是想搞笑一下朋友哈哈哈,请不要在意哈),还有操作键是使用 ↑ ↓ ← → ) <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title>贪食蛇</title>

  • C#基于TCP实现简单游戏客户端的完整实例

    目录 一.界面 二.代码 1.播放音频 2.播放图片 3.登录和退出游戏 4.命令交互 5.信息展示 6.最终代码 三.效果 四.总结 五.参考 一.界面 左上方播放音频按钮和停止播放下面是图片展示再下面是进入游戏以及退出游戏最后是命令输入框右边是消息框 二.代码 1.播放音频 需要用到WindowsMediaPlayer组件,在常规组件的选择项里面可以找到. 实现代码 private void start_Click(object sender, EventArgs e) { //新建线程打开

  • 基于Matlab制作一款简单的龙舟小游戏

    效果图: 没找到合适的背景就自己画了个,大家如果有更好看的可以换一下... 步骤 1 创建Axes及图片导入 窗口创建: Mainfig=figure('units','pixels','position',[50 100 760 400],... 'Numbertitle','off','menubar','none','resize','off',... 'name','dragonBoat'); axes('parent',Mainfig,'position',[0 0 1 1],...

随机推荐