摸鱼之vue3+konva写一个贪吃蛇的游戏

大家好,又是没活干的一天,今天用vue3和konva写一个贪吃蛇的游戏

先看一个效果

功能列表/玩法

  • 开始游戏和重新开始:初始块(初始为几块)
  • 蛇的移动:根据上下左右键盘四个方向移动,每次移动占据新的位置,释放以前的位置
  • 食物生成:开始的时候会在画布随机生成一个食物,蛇吃到后会在另一个位置重新生成
  • 蛇的成长:当吃到食物后蛇会加长一节
  • 碰撞检测:蛇撞到边界后游戏结束
  • 计分:每吃到一个食物就加1
  • 难易度:根据三个难度,加快蛇移动的速度

模板与样式

vue 复制代码
<template>
  <p>分数: {{ score }}</p>
  <div id="container" ref="stageContainer"></div>
  
  <div class="btn-group">
    <button @click="startGame">开始游戏</button>
    <button @click="stopGame">停止游戏</button>
    <button @click="restartGame">重新开始</button>
  </div>

  <div>
    <label for="difficulty">难度:</label>
    <select id="difficulty" v-model="difficulty">
      <option value="1">简单</option>
      <option value="2">中等</option>
      <option value="3">困难</option>
    </select>
  </div>
</template>

<style>
#container {
  width: 500px;
  height: 500px;
  background-color: #f0f0f0;
  margin: 0 auto;

  background-size: 20px 20px;
  background-image:
    linear-gradient(to right, grey 1px, transparent 1px),
    linear-gradient(to bottom, grey 1px, transparent 1px);
}

.btn-group {
  margin-top: 15px;
}
</style>

效果如下:

样式介绍

background-size: 20px 20px: 这个属性设置了背景图片的尺寸大小,宽高为20px background-image:这个属性使用了两个 linear-gradient 函数来创建背景图像

第一个 linear-gradient 函数用于创建一个水平方向的线性渐变,从左到右。它的参数是 to right,表示渐变的方向是从左到右,渐变的起始颜色是灰色 (grey),终止颜色是透明 (transparent),渐变的宽度是 1 像素。因此,这个渐变效果会在水平方向上创建一条细细的灰色线,然后后面是透明的

第二个 linear-gradient 函数用于创建一个垂直方向的线性渐变,从上到下。它的参数是 to bottom,表示渐变的方向是从上到下,渐变的起始颜色是灰色 (grey),终止颜色是透明 (transparent),渐变的高度是 1 像素。因此,这个渐变效果会在垂直方向上创建一条细细的灰色线,然后后面是透明的

通过将这两个线性渐变的背景图像叠加在一起,就可以实现一个背景上有网格线的效果,每个渐变都只有 1 像素宽度或高度,所以它们会形成一个细小的网格,然后网格的大小是 20 像素乘以 20 像素。

游戏开发步骤

首先创建舞台和图层

ini 复制代码
const stageContainer = ref(null);
stage = new Konva.Stage({
    container: stageContainer.value,
    width: 500,
    height: 500,
  });

layer = new Konva.Layer();
stage.add(layer);

初始化蛇和食物的状态

然后body是数组,表示初始化蛇分为三块,xy是起始位置,食物位置随机

css 复制代码
let stage: { destroy: () => void; add: (arg0: any) => void; },
food: SnakeBody
snake = {
    body: [{ x: 2, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 0 }],
    direction: 'right',
};
food = { x: Math.floor(Math.random() * 25), y: Math.floor(Math.random() * 25) };

绘制蛇的身体

  • snake.body.forEach((dot: { x: number; y: number; }) => { ... });:这是一个 forEach 循环,遍历了 snake.body 数组中的每个点。对于数组中的每个点,我们执行一个回调函数。
  • const rect = new Konva.Rect({ ... });:在回调函数中,我们创建一个 Konva 库中的矩形对象 rect。
  • x: dot.x * 20, 和 y: dot.y * 20,:通过将点的 x 坐标和 y 坐标乘以 20,得到矩形的实际位置,因为每个点的位置是以格子为单位的,每个格子的大小设为 20x20。
  • width: 20, 和 height: 20,:指定矩形的宽度和高度,都设为 20,与格子的大小一致。
  • fill: 'green',:指定矩形的填充颜色为绿色,表示贪吃蛇的身体。
  • layer.add(rect);:将矩形对象添加到 Konva 的图层中,以便后续可以将其显示在画布上。
php 复制代码
snake.body.forEach((dot: { x: number; y: number; }) => {
    const rect = new Konva.Rect({
      x: dot.x * 20,
      y: dot.y * 20,
      width: 20,
      height: 20,
      fill: 'green',
    });
    layer.add(rect);
  });

绘制食物

php 复制代码
const foodRect = new Konva.Rect({
    x: food.x * 20,
    y: food.y * 20,
    width: 20,
    height: 20,
    fill: 'red',
  });
  layer.add(foodRect);

蛇的移动

我们需要写一个方法来更新蛇的位置

scss 复制代码
setInterval(() => {
  updateGame();
}, 1000 / 60);

首先使用 Object.assign() 来创建副本是为了避免直接修改原始的蛇头位置,然后snake.direction是蛇移动的方向,然后通过方向判断,如果蛇向上移动('up'),则将 head.y 减去 1,表示向上移动一格,如果向下移('down'),则将 head.y 加1,表示向下移一格,左右也一样,head.x 减1,表示向左移一格,head.x 加1,表示向右移一格。

ini 复制代码
const head = Object.assign({}, snake.body[0]);
  switch (snake.direction) {
    case 'up':
      head.y -= 1;
      break;
    case 'down':
      head.y += 1;
      break;
    case 'left':
      head.x -= 1;
      break;
    case 'right':
      head.x += 1;
      break;
  }

过这个逻辑,我们可以根据蛇的移动方向来更新蛇头的位置

蛇的成长与新的食物

首先检查蛇头的位置是否和食物的位置相同,如果相同,表示蛇吃到了食物。然后,它会生成新的食物,当蛇吃到食物时,蛇的身体会增长,这是通过不移除蛇身的最后一部分来实现的。

如果蛇头的位置和食物的位置不相同,表示蛇没有吃到食物,那么就会移除蛇身的最后一部分,使蛇保持原来的长度

ini 复制代码
if (food.x === head.x && food.y === head.y) {
    food = { x: Math.floor(Math.random() * 25), y: Math.floor(Math.random() * 25) };
    score.value += 1;
  } else {
    snake.body.pop();
  }

snake.body.pop();:删除蛇身的最后一个点。这是为了保持贪吃蛇的长度不变,因为蛇没有吃到食物,所以蛇尾会向前移动一格,即删除最后一个点。

碰撞检测

  • head.x < 0:如果蛇头的 x 坐标小于 0,表示蛇头越过了左边界,发生了碰撞。
  • head.y < 0:如果蛇头的 y 坐标小于 0,表示蛇头越过了上边界,发生了碰撞。
  • head.x >= 25:如果蛇头的 x 坐标大于等于 25,表示蛇头越过了右边界,发生了碰撞。
  • head.y >= 25:如果蛇头的 y 坐标大于等于 25,表示蛇头越过了下边界,发生了碰撞。
typescript 复制代码
const checkCollision = (head: { x: number; y: number; }) => {
  return (
    head.x < 0 ||
    head.y < 0 ||
    head.x >= 25 ||
    head.y >= 25 ||
    snake.body.some((dot: { x: any; y: any; }) => dot.x === head.x && dot.y === head.y)
  );
};

通过键盘上下左右控制

javascript 复制代码
onMounted(() => {
  window.addEventListener('keydown', (e) => {
    switch (e.key) {
      case 'ArrowUp':
        if (snake.direction !== 'down') snake.direction = 'up';
        e.preventDefault()
        break;
      case 'ArrowDown':
        if (snake.direction !== 'up') snake.direction = 'down';
        e.preventDefault()
        break;
      case 'ArrowLeft':
        if (snake.direction !== 'right') snake.direction = 'left';
        e.preventDefault()
        break;
      case 'ArrowRight':
        if (snake.direction !== 'left') snake.direction = 'right';
        e.preventDefault()
        break;
    }
  });
})

这个应该没什么可说的,添加一个键盘事件监听器,监听用户的按键操作。当用户按下键盘时,根据按下的按键进行判断,使用 switch 语句来处理不同的按键情况。比如第一个分支,如果当前贪吃蛇的移动方向不是向下,则将贪吃蛇的移动方向设置为向上

第一次写,可能有点乱,源码地址:wangshaojie/moyu (github.com)

相关推荐
康小汪7 小时前
AI 写 Vue 总写成同一个味道:给它装 Skills,装的不是知识是边界
前端·vue.js·人工智能·ai编程·最佳实践
智能运维指南8 小时前
研制过程“两张皮“怎么破:研制一体化支撑平台的定位、能力边界与落地前提
java·前端·人工智能·嘉为蓝鲸
白山编程大哥9 小时前
Java 后端如何优雅地把 List 集合返回给前端
java·前端·list
Flynt10 小时前
我把热榜那篇"禁用 axios"逐条实测了一遍:结论大体对,坑在它没讲的地方
前端·javascript·axios
Csvn11 小时前
bundle优化
前端
北冥有鱼11 小时前
发版后动态路由点击报 `Failed to fetch dynamically imported module`」问题上的完整排查与修复过程
前端·vue.js
静默回滚11 小时前
视频压到 25 MB 以内,码率要算、音轨要扣、余量要留
前端
用户372037492601811 小时前
把 10MB 中文语义模型塞进浏览器:STSB 做到教师模型的 93%,但线上 top1 只有 42%
前端
光锥智能11 小时前
海康机器人举行2026移动机器人新品发布会:箱式存拣升级,「明智」系列发布
java·前端·机器人
计算机魔术师11 小时前
Anthropic 上市文件最吓人的一幕,不是亏损 807 亿
前端