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 相关文章推荐
clientX,pageX,offsetX,x,layerX,screenX,offsetLeft区别分析
Mar 12 Javascript
JQuery获取浏览器窗口内容部分高度的代码
Feb 24 Javascript
那些年,我还在学习jquery 学习笔记
Mar 05 Javascript
Angular2中如何使用ngx-translate进行国际化
May 21 Javascript
Vue.js对象转换实例
Jun 07 Javascript
微信小程序学习之数据处理详解
Jul 05 Javascript
Vue axios 中提交表单数据(含上传文件)
Jul 06 Javascript
CheckBox多选取值及判断CheckBox选中是否为空的实例
Oct 31 Javascript
傻瓜式解读koa中间件处理模块koa-compose的使用
Oct 30 Javascript
jQuery实现左右两个列表框的内容相互移动功能示例
Jan 27 jQuery
详解微信小程序获取当前时间及日期的方法
Apr 28 Javascript
微信公众号获取用户地理位置并列出附近的门店的示例代码
Jul 25 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 MYSQL 数据备份类
2009/06/19 PHP
用mysql触发器自动更新memcache的实现代码
2009/10/11 PHP
PHP MySQL应用中使用XOR运算加密算法分享
2011/08/28 PHP
解析zend studio中直接导入svn中的项目的方法步骤
2013/06/21 PHP
Laravel下生成验证码的类
2017/11/15 PHP
PHP手机短信验证码实现流程详解
2018/05/17 PHP
php链式操作的实现方式分析
2019/08/12 PHP
说说掌握JavaScript语言的思想前提想学习js的朋友可以看看
2009/04/01 Javascript
Js动态添加复选框Checkbox的实例方法
2013/04/08 Javascript
javascript实现仿百度图片的瀑布流加载效果
2016/04/20 Javascript
基于BootStarp的Dailog
2016/04/28 Javascript
jquery对象访问是什么及使用方法介绍
2016/05/03 Javascript
Bootstrap select实现下拉框多选效果
2016/12/23 Javascript
JavaScript正则表达式替换字符串中图片地址(img src)的方法
2017/01/13 Javascript
Jquery根据浏览器窗口改变调整大小的方法
2017/02/07 Javascript
vue-cli配置环境变量的方法
2018/07/09 Javascript
react 应用多入口配置及实践总结
2018/10/17 Javascript
JS实现简单的抽奖转盘效果示例
2019/02/16 Javascript
浅谈vue使用axios的回调函数中this不指向vue实例,为undefined
2020/09/21 Javascript
JavaScript实现拖动对话框效果的实现代码
2020/10/12 Javascript
JavaScript中的几种继承方法示例
2020/12/06 Javascript
Python循环语句中else的用法总结
2016/09/11 Python
Python入门之后再看点什么好?
2018/03/05 Python
Python txt文件常用读写操作代码实例
2020/08/03 Python
浅析Python requests 模块
2020/10/09 Python
基于python爬取链家二手房信息代码示例
2020/10/21 Python
详解python os.path.exists判断文件或文件夹是否存在
2020/11/16 Python
全方位了解CSS3的Regions扩展
2015/08/07 HTML / CSS
浅谈CSS3动画的回调处理
2016/07/21 HTML / CSS
力学专业求职信
2014/07/23 职场文书
毕业实习证明(4篇)
2014/10/28 职场文书
2014年人大工作总结
2014/12/10 职场文书
入团介绍人意见范文
2015/06/04 职场文书
优秀共产党员主要事迹材料
2015/11/05 职场文书
MYSQL主从数据库同步备份配置的方法
2021/05/26 MySQL
python标准库ElementTree处理xml
2022/05/20 Python