小游戏管理平台-贪吃蛇

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第三天,点击查看活动详情

线上地址:http://codeape.site

源码:https://gitee.com/wooden-joint/my-game/blob/master/src/components/Snake.vue

开发技术

vue2 + js + canvas

需求

蛇会自动往前走,键盘上下左右控制方向,当蛇吃到蛋后,长度加一,并重新生成蛋,撞到墙或咬到自己游戏结束。附加功能:每隔10秒墙会自动打开/关闭,蛇可以在打开时穿越墙并传送到另一边,并为其穿梭途中增加一个传送圈。

代码讲解

初始化页面

获取 canvas 画板2d上下文,将画板根据自定义的 size 裁剪行列的方格;然后创建蛇和蛋的数据,再通过 canvas 绘画出来。

init() {
  setTimeout(() => {
    this.canvas = this.$refs.canvas;
    // let canvas = document.getElementById("mycanvas");
    this.ctx = this.canvas.getContext("2d");
    // 定义行列
    this.row = this.canvasWidth / this.size;
    this.col = this.canvasHeight / this.size;
    // 刚开始先画出蛇和蛋来
    this.updateEgg();
    this.drawEgg();
    this.drawSnake();
    // 监听键盘事件
    this.onKeyDown();
    if(!this.isPause) this.startGame();
  }, 200);
},

蛋的绘制

首先是需要更新蛋的位置,由于该函数后面还需用到,所以需要判断蛋的出现位置是否在蛇身体里,如果在,则需要重新更改其位置。

updateEgg() {
  // 当蛋出现在蛇身体里面的时候,重新执行
  do {
    this.egg.x =
      (Math.floor(Math.random() * this.col - 1) + 1) * this.egg.size;
    this.egg.y =
      (Math.floor(Math.random() * this.row - 1) + 1) * this.egg.size;
  } while (this.snake.body.includes({ x: this.egg.x, y: this.egg.y }));
},

紧接着就是将蛋绘画出来,填充颜色并根据蛋的位置绘画出代表蛋的方块。

drawEgg() {
  this.ctx.fillStyle = "#adff2f";
  this.ctx.fillRect(this.egg.x, this.egg.y, this.egg.size, this.egg.size);
},

蛇的绘制

遍历蛇身的对象数组,根据坐标将蛇绘制出来,每一段蛇身对应一个方块;当蛇移动时,还需保存好蛇穿越墙的坐标,用于后面绘制蛇穿越墙壁的光圈。

drawSnake() {
  this.ctx.fillStyle = "#fff";
  for (let i = 0; i < this.snake.body.length; i++) {
    const { x, y } = this.snake.body[i];
    this.ctx.fillRect(x, y, this.snake.size, this.snake.size);
  }
  this.ctx.fillRect(
    this.snake.x,
    this.snake.y,
    this.snake.size,
    this.snake.size
  );
  // 处理穿墙 并保存穿墙的位置
  if (!this.isWall) {
    const { width, height } = this.canvas;
    if (this.snake.x >= width) {
      this.throughWall.x = width - this.size;
      this.throughWall.y = this.snake.y;
      this.throughWall.isTurn = false;
      this.snake.x = 0;
    }
    if (this.snake.x < 0) {
      this.throughWall.x = 0;
      this.throughWall.y = this.snake.y;
      this.throughWall.isTurn = false;
      this.snake.x = width;
    }
    if (this.snake.y >= height) {
      this.throughWall.x = this.snake.x;
      this.throughWall.y = height - this.size;
      this.throughWall.isTurn = true;
      this.snake.y = 0;
    }
    if (this.snake.y < 0) {
      this.throughWall.x = this.snake.x;
      this.throughWall.y = 0;
      this.throughWall.isTurn = true;
      this.snake.y = height;
    }
  }
},

开始游戏

创建计时器,然后不断的清空画板,然后绘制蛋,更新蛇的位置,画蛇(如果蛇吃到了蛋,就需要更新蛇的数据和对应蛋的数据),以此进行循环绘画。

// 开启游戏
startGame() {
  this.gameTimer = setInterval(() => {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.drawEgg();
    this.updateSnake();
    this.drawSnake();
    if (this.eatEgg()) {
      this.updateEgg();
    }
  }, this.drawTime);
  // 开启墙的定时器
  this.wallTimer = setInterval(() => {
    this.isWall ? this.wallTime-- : this.wallTime++;
  }, 1000);
},

蛇的移动

遍历蛇身,将蛇前面一节的位置给到后面那节即可,当蛇吃到了蛋,则需要把当前蛋的位置作为新的蛇头,即把该位置坐标给到蛇,然后让它来带领蛇身移动。

updateSnake() {
  // 让蛇的后一节等于前一节的位置
  for (let i = 0; i < this.snake.body.length - 1; i++) {
    this.snake.body[i] = this.snake.body[i + 1];
  }

  // 吃到东西了, 保存蛇的身体位置,并且将其作为蛇头,不断带领蛇身移动
  if (this.snake.length > 0) {
    this.snake.body[this.snake.length - 1] = {
      x: this.snake.x,
      y: this.snake.y,
    };
  }
  this.snake.x += this.snake.xSpeed;
  this.snake.y += this.snake.ySpeed;
},

键盘对蛇的移动

使用 document.onkeydown 方法监听键盘事件,左右下,都是调用 move 函数。

  • 注:this.$store.commit(“setCurrentKey”, “left”); 只是我用来设置项目中方向键的样式的。可以不用管。
// 监听用户的键盘事件
onKeyDown() {
  document.onkeydown = (e) => {
    switch (e.code) {
      case "ArrowLeft":
        // 判断不能反方向
        if (this.direction == "right") break;
        this.$store.commit("setCurrentKey", "left");
        this.move(-this.size * 1, 0);
        break;
      case "ArrowUp":
        if (this.direction == "down") break;
        this.$store.commit("setCurrentKey", "up");
        this.move(0, -this.size * 1);
        break;
      case "ArrowRight":
        if (this.direction == "left") break;
        this.$store.commit("setCurrentKey", "right");
        this.move(this.size * 1, 0);
        break;
      case "ArrowDown":
        if (this.direction == "up") break;
        this.$store.commit("setCurrentKey", "down");
        this.move(0, this.size * 1);
        break;
      case "Space":
        this.pause();
        break;
    }
  };
},

move 函数其实就是将蛇往前移动一格即可

move(x, y) {
  (this.snake.xSpeed = x), (this.snake.ySpeed = y);
},

蛇吃到蛋

蛇吃蛋,其实就是判断蛇和蛋的坐标是否相等,相等就把蛇长度加一,增加得分就好

eatEgg() {
  if (this.snake.x == this.egg.x && this.snake.y == this.egg.y) {
    this.snake.length++;
    this.score += 10;
    return true;
  }
  return false;
},

watch 监听蛇的移动

首先需要判断当前蛇的移动方向,当蛇头坐标等于自身任何一个坐标,则代表蛇咬到了自己,则蛇死亡。而当蛇身还在穿墙,则需要控制光圈持续展示(代表当前在穿墙)。当墙存在时,如果蛇头触碰到了墙,则蛇死亡。

snake: {
  handler(s) {
    let isThrough = false;
    // 判断当前的方向
    if (
      s.body.length > 0 &&
      s.body[s.length - 1] &&
      s.body[s.length - 1].x
    ) {
      // 将跟着头部的那节身体坐标 解构出来
      let { x, y } = s.body[s.length - 1];
      if (s.y == y && s.x - x > 0) {
        this.direction = "right";
      } else if (s.x - x < 0) {
        this.direction = "left";
      } else if (s.x == x && s.y - y > 0) {
        this.direction = "down";
      } else if (s.y - y < 0) {
        this.direction = "up";
      }
    }
    for (let item of s.body) {
      // 判断蛇咬到了自己
      if (s.x == item.x && s.y == item.y) {
        this.snakeDie();
      }
      // 只要有一个蛇身还在穿墙,就一直显示旋转的圈
      if (item.x == this.throughWall.x && item.y == this.throughWall.y) {
        this.isThroughWall = true;
        isThrough = true;
      }
    }
    if (!isThrough) {
      this.isThroughWall = false;
    }
    // 判断蛇是否触碰了边界;
    const { width, height } = this.canvas;
    if (
      this.isWall &&
      (s.x < 0 || s.x >= width || s.y < 0 || s.y >= height)
    ) {
      this.snakeDie();
    }
  },
  deep: true,
},

收获

该贪吃蛇小游戏是我的一个 canvas 小游戏,一开始学习 canvas 学了大概一个星期吧。而且当时暑假在家想到了一个毕设:小游戏管理平台;(因为之前已经写过两个小游戏了,不想浪费掉)

然后就想着用 canvas 来试着来做个小游戏,后来就在b站找了个视频作为参考,视频中是 js 版本的,由于当时也刚学 vue,所以就改写成 vue2 的版本。整个小游戏下来,对 js 基本功提升了不少,同时对 canvas 的绘制更加熟悉了。了解到canvas 绘画比直接 js 操作 dom,在性能上好不少。

参考视频地址:https://www.bilibili.com/video/BV1NC4y1x7Sk

小游戏管理平台:https://juejin.cn/post/7149169293714784293

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容