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 相关文章推荐
childNodes.length与children.length的区别
May 14 Javascript
js写一个弹出层并锁屏效果实现代码
Dec 07 Javascript
js实现拉伸拖动iframe的具体代码
Aug 03 Javascript
JS实现OCX控件的事件响应示例
Sep 17 Javascript
jquery制作漂亮的弹出层提示消息特效
Dec 23 Javascript
js实现可得到不同颜色值的颜色选择器实例
Feb 28 Javascript
js实现表单检测及表单提示的方法
Aug 14 Javascript
javascript实现添加附件功能的方法
Nov 18 Javascript
JS中from 表单序列化提交的代码
Jan 20 Javascript
详解angular2封装material2对话框组件
Mar 03 Javascript
微信小程序实现评论功能
Nov 28 Javascript
详解Vue数据驱动原理
Nov 17 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
建立动态的WML站点(三)
2006/10/09 PHP
php 调用远程url的六种方法小结
2009/11/02 PHP
PHP中使用crypt()实现用户身份验证的代码
2012/09/05 PHP
zend framework配置操作数据库实例分析
2012/12/06 PHP
PHP连接MySQL的2种方法小结以及防止乱码
2014/03/11 PHP
php中file_get_contents与curl性能比较分析
2014/11/08 PHP
Thinkphp搜索时首页分页和搜索页保持条件分页的方法
2014/12/05 PHP
支持中文、字母、数字的PHP验证码
2015/05/04 PHP
PHP超全局数组(Superglobals)介绍
2015/07/01 PHP
PHP魔术方法使用方法汇总
2016/02/14 PHP
nginx下安装php7+php5
2016/07/31 PHP
JavaScript QueryString解析类代码
2010/01/17 Javascript
javascirpt实现2个iframe之间传值的方法
2016/06/30 Javascript
jquery radio的取值_radio的选中_radio的重置方法
2016/09/20 Javascript
bootstrap侧边栏圆点导航
2017/01/11 Javascript
使用vue的transition完成滑动过渡的示例代码
2018/06/25 Javascript
详解微信小程序实现仿微信聊天界面(各种细节处理)
2019/02/17 Javascript
JS简单数组排序操作示例【sort方法】
2019/05/17 Javascript
微信小程序实现多行文字超出部分省略号显示功能
2019/10/23 Javascript
JavaScript实现简单计算器功能
2019/12/19 Javascript
Django中URL视图函数的一些高级概念介绍
2015/07/20 Python
浅谈Python实现贪心算法与活动安排问题
2017/12/19 Python
python: 自动安装缺失库文件的方法
2018/10/22 Python
python开发游戏的前期准备
2019/05/05 Python
Python爬虫 urllib2的使用方法详解
2019/09/23 Python
python数据分析工具之 matplotlib详解
2020/04/09 Python
详解Flask前后端分离项目案例
2020/07/24 Python
pycharm-professional-2020.1下载与激活的教程
2020/09/21 Python
法国奢华女性时尚配饰网上商店:Monnier Frères
2016/08/27 全球购物
智能电子秤、手表和健康监测仪:Withings(之前为诺基亚健康)
2018/10/30 全球购物
教育基金募捐倡议书
2014/05/14 职场文书
个人授权委托书样本
2014/09/13 职场文书
高三毕业评语
2014/12/31 职场文书
2014年终个人总结报告
2015/03/09 职场文书
教师节班会开场白
2015/06/01 职场文书
2016公司新年问候语
2015/11/11 职场文书