canvas实现贪食蛇的实践


Posted in Javascript onFebruary 15, 2022

贪食蛇

最近 dan 有在油管上直播,播放量最多的就是手写一个贪食蛇。
本来想学一下大佬写代码的姿势,看了几分钟就没了耐性,心想我为什么不能自己写一个呢。
一步一步跟着敲代码,我实践了一段时间但是收效甚微,因为中间少了自己的思考。
初期可能有些作用,可以学到一些技巧和规范。
但是自己实现一个东西带来的成就感,你不断的 debug 和查文档查资料留下的记忆和习惯,这大概就是这个玩意带给我最大的收获吧。
canvas实现贪食蛇的实践
我的 github 一直是单机模式,如果这篇文章对您有所帮助的话欢迎点个 star

数据结构及变量

const canvas = document.getElementById("canvas")
const ctx = canvas.getContext("2d")

const width = 400
const height = 400
const cellLength = 20

let foodPosition

let initSnake = [
  [0, 0],
  [1, 0],
  [2, 0],
]

let snake = [...initSnake]

let direction = "right"

let canChangeDirection = true

canvas 绘制页面

//  背景
function drawBackground() {
  ctx.strokeStyle = "#bfbfbf"
  for (let i = 0; i <= height / cellLength; i++) {
    ctx.beginPath()
    ctx.moveTo(0, cellLength * i)
    ctx.lineTo(width, cellLength * i)
    ctx.stroke()
  }

  for (let i = 0; i <= width / cellLength; i++) {
    ctx.beginPath()
    ctx.moveTo(cellLength * i, 0)
    ctx.lineTo(cellLength * i, height)
    ctx.stroke()
  }
}

// 蛇
function drawSnake() {
  let step = 100 / (snake.length - 1)
  for (let i = 0; i < snake.length; i++) {
    // 这里做了渐变色的蛇,添加动态色彩。尾部有个最小白色阀值,免得跟背景混为一体
    const percent = Math.min(100 - step * i, 90)
    ctx.fillStyle = `hsl(0,0%,${percent}%)`

    ctx.fillRect(
      snake[i][0] * cellLength,
      snake[i][1] * cellLength,
      cellLength,
      cellLength
    )
  }
}

// 绘制食物

// 随机生成食物的位置
function generateRandomFood() {
  // 如果没有位置可以生成
  if (snake.length > width * height) {
    return alert("you win")
  }
  const randomX = Math.floor(Math.random() * (width / cellLength))
  const randomY = Math.floor(Math.random() * (height / cellLength))
  // 生成的位置如果跟蛇体积碰撞,则重新生成。
  for (let i = 0; i < snake.length; i++) {
    if (snake[i][0] === randomX && snake[i][1] === randomY) {
      return generateRandomFood()
    }
  }
  foodPosition = [randomX, randomY]
}

// 绘制
function drawFood() {
  ctx.fillStyle = "#ff7875"
  ctx.fillRect(
    foodPosition[0] * cellLength,
    foodPosition[1] * cellLength,
    cellLength,
    cellLength
  )
}

蛇的移动

// 蛇的移动
// 确定下一次移动的位置,将这个点push到数组末尾(头的位置),
// 将数组第一项shift出来(尾的位置)

// 吃食物的逻辑
// 如果食物的位置跟下一次移动的位置相同,将这个点加入头部,不推出尾部

function snakeMove() {
  let next
  let last = snake[snake.length - 1]
  // 根据方向确定下一个蛇头的位置
  switch (direction) {
    case "up": {
      next = [last[0], last[1] - 1]
      break
    }
    case "down": {
      next = [last[0], last[1] + 1]
      break
    }
    case "left": {
      next = [last[0] - 1, last[1]]
      break
    }
    case "right": {
      next = [last[0] + 1, last[1]]
      break
    }
  }

  // 边缘碰撞
  const boundary =
    next[0] < 0 ||
    next[0] >= width / cellLength ||
    next[1] < 0 ||
    next[1] >= height / cellLength

  // 自身碰撞
  const selfCollision = snake.some(([x, y]) => next[0] === x && next[1] === y)

  // 碰撞重新开始游戏
  if (boundary || selfCollision) {
    return restart()
  }

  snake.push(next)

  // 如果下一个点是食物的位置,不推出头部
  if (next[0] === foodPosition[0] && next[1] === foodPosition[1]) {
    generateRandomFood()
    return
  }
  snake.shift()

  canChangeDirection = true
}

事件监听

document.addEventListener("keydown", (e) => {
  switch (e.key) {
    case "ArrowUp":
      if (direction === "down" || !canChangeDirection) return
      direction = "up"
      canChangeDirection = false
      break
    case "ArrowDown":
      if (direction === "up" || !canChangeDirection) return
      direction = "down"
      canChangeDirection = false
      break
    case "ArrowLeft":
      if (direction === "right" || !canChangeDirection) return
      direction = "left"
      canChangeDirection = false
      break
    case "ArrowRight":
      if (direction === "left" || !canChangeDirection) return
      direction = "right"
      canChangeDirection = false
      break
  }
})

requestAnimationFrame 实现动画

// 默认的requestAnimationFrame循环应该是60帧,对于这个游戏来说太快了。
// 所以做了限制,5次loop才渲染(蛇移动一格)一次
function animate() {
  let count = 0
  function loop() {
    if (++count > 5) {
      draw()
      count = 0
    }
    requestAnimationFrame(loop)
  }
  requestAnimationFrame(loop)
}

BUG 解决

游戏规则中,蛇是不能反向移动的。
但是在事件回调中,如果改变方向过快,(5 次 loop 才执行一次重绘),就会出现方向变了,但是蛇的位置没变(比如蛇向右移动,我们先按上再按左),他就和自身碰撞了
解决方案:
我加了一个 canChangeDirection 变量,
当你改变方向之后,必须等待蛇移动了才能再次改变方向
// 事件回调
case "ArrowUp":
  if (direction === "down" |!canChangeDirection) return
  direction = "up"
  canChangeDirection = false
  break

到此这篇关于canvas实现贪食蛇的实践的文章就介绍到这了,更多相关 canvas贪食蛇内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章,希望大家以后多多支持三水点靠木!

 
Javascript 相关文章推荐
jquery.combobox中文api和例子,修复了上面的小bug
Mar 28 Javascript
Function.prototype.bind用法示例
Sep 16 Javascript
Javascript中setTimeOut和setInterval的定时器用法
Jun 12 Javascript
js实现简洁的TAB滑动门效果代码
Sep 06 Javascript
jQuery获取checkboxlist的value值的方法
Sep 27 Javascript
JavaScript如何动态创建table表格
Aug 02 Javascript
详解ES6语法之可迭代协议和迭代器协议
Jan 13 Javascript
微信小程序Getuserinfo解决方案图解
Aug 24 Javascript
JavaScript和TypeScript中的void的具体使用
Sep 12 Javascript
vue在路由中验证token是否存在的简单实现
Nov 11 Javascript
解决vant title-active-color与title-inactive-color不生效问题
Nov 03 Javascript
JS中循环遍历数组的四种方式总结
Jan 23 Javascript
Vue自定义铃声提示音组件的实现
Jan 22 #Vue.js
JavaScript实例 ODO List分析
Jan 22 #Javascript
JavaScript ES6的函数拓展
Jan 18 #Javascript
Vue提供的三种调试方式你知道吗
Jan 18 #Vue.js
详解Vue项目的打包方式(生成dist文件)
Jan 18 #Vue.js
html5调用摄像头截图功能
Jan 18 #Javascript
在 HTML 页面中使用 React的场景分析
Jan 18 #Javascript
You might like
PHP实现微信公众平台音乐点播
2014/03/20 PHP
php中的mongodb select常用操作代码示例
2014/09/06 PHP
php curl请求信息和返回信息设置代码实例
2015/04/27 PHP
PHP实现清除MySQL死连接的方法
2016/07/23 PHP
PHP编程计算日期间隔天数的方法
2017/04/26 PHP
PHP基于进程控制函数实现多线程
2020/12/09 PHP
原生js实现fadein 和 fadeout淡入淡出效果
2014/06/05 Javascript
浅谈javascript 归并方法
2015/01/21 Javascript
JavaScript变量的作用域全解析
2015/08/14 Javascript
详解Bootstrap创建表单的三种格式(一)
2016/01/04 Javascript
网页中JS函数自动执行常用三种方法
2016/03/30 Javascript
AngularJS基础 ng-include 指令简单示例
2016/08/01 Javascript
jQuery实现圣诞节礼物传送(花式轮播)
2016/12/25 Javascript
jquery+ajax实现省市区三级联动效果简单示例
2017/01/04 Javascript
javascript中apply/call和bind的使用
2017/02/15 Javascript
vue 实现 ios 原生picker 效果及实现思路解析
2017/12/06 Javascript
解决使用vue.js路由后失效的问题
2018/03/17 Javascript
JavaScript中join()、splice()、slice()和split()函数用法示例
2018/08/24 Javascript
layui中select,radio设置不生效的解决方法
2019/09/05 Javascript
python实现监控windows服务并自动启动服务示例
2014/04/17 Python
详解基于django实现的webssh简单例子
2018/07/17 Python
python进阶之自定义可迭代的类
2019/08/20 Python
Python socket模块ftp传输文件过程解析
2019/11/05 Python
python pyenv多版本管理工具的使用
2019/12/23 Python
法国购买二手电子产品网站:Asgoodasnew
2020/03/27 全球购物
什么是数据抽象
2016/11/26 面试题
求职简历自荐信范文
2013/10/21 职场文书
2013年保送生自荐信格式
2013/11/20 职场文书
仓管员岗位责任制
2014/02/19 职场文书
2014年医学生毕业自我鉴定
2014/03/26 职场文书
小学兴趣小组活动总结
2014/07/07 职场文书
单位委托书
2014/10/15 职场文书
阿凡达观后感
2015/06/10 职场文书
同乡会致辞
2015/07/30 职场文书
2019最新婚庆对联集锦!
2019/07/10 职场文书
python设置 matplotlib 正确显示中文的四种方式
2021/05/10 Python