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复选框CHECKBOX全选、反选
Aug 30 Javascript
jquery中动态效果小结
Dec 16 Javascript
js向上无缝滚动,网站公告效果 具体代码
Nov 18 Javascript
jquery用offset()方法获得元素的xy坐标
Sep 06 Javascript
jQuery显示和隐藏 常用的状态判断方法
Jan 29 Javascript
Vue 短信验证码组件开发详解
Feb 14 Javascript
Bootstrap 设置datetimepicker在屏幕上面弹出设置方法
Mar 21 Javascript
JSON在Javascript中的使用(eval和JSON.parse的区别)详细解析
Sep 05 Javascript
jQuery EasyUI window窗口使用实例代码
Dec 25 jQuery
vue使用自定义icon图标的方法
May 14 Javascript
JS实现可切换图片的幻灯切换效果示例
May 24 Javascript
vue大型项目之分模块运行/打包的实现
Sep 21 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
解决163/sohu/sina不能够收到PHP MAIL函数发出邮件的问题
2009/03/13 PHP
PHP编码转换函数 自动转换字符集支持数组转换
2012/12/16 PHP
PHP使用Redis实现防止大并发下二次写入的方法
2017/10/09 PHP
js静态方法与实例方法分析
2011/07/04 Javascript
用Javascript评估用户输入密码的强度(Knockout版)
2011/11/30 Javascript
js获取页面传来参数的方法
2014/09/06 Javascript
借助FileReader实现将文件编码为Base64后通过AJAX上传
2015/12/24 Javascript
JavaScript简单实现鼠标移动切换图片的方法
2016/02/23 Javascript
Nodejs如何复制文件
2016/03/09 NodeJs
JQuery为元素添加样式的实现方法
2016/07/20 Javascript
js放大镜放大购物图片效果
2017/01/18 Javascript
javascript实现下雨效果
2017/03/27 Javascript
简单实现js上传文件功能
2017/08/21 Javascript
Vue-cli创建项目从单页面到多页面的方法
2017/09/20 Javascript
vue通过路由实现页面刷新的方法
2018/01/25 Javascript
详解webpack 打包文件体积过大解决方案(code splitting)
2018/04/10 Javascript
Redux实现组合计数器的示例代码
2018/07/04 Javascript
在Vue项目中,防止页面被缩放和放大示例
2019/10/28 Javascript
VUE+Element实现增删改查的示例源码
2020/11/23 Vue.js
[50:21]Liquid vs Winstrike 2018国际邀请赛小组赛BO2 第二场
2018/08/19 DOTA
python网络编程之数据传输UDP实例分析
2015/05/20 Python
Python实现的双色球生成功能示例
2017/12/18 Python
PyQt5实现拖放功能
2018/04/25 Python
django ajax json的实例代码
2018/05/29 Python
PyQt Qt Designer工具的布局管理详解
2019/08/07 Python
利用Python的sympy包求解一元三次方程示例
2019/11/22 Python
Python 中的pygame安装与配置教程详解
2020/02/10 Python
Python基于stuck实现scoket文件传输
2020/04/02 Python
英国在线珠宝店:The Jewel Hut
2017/03/20 全球购物
美国最古老的精致书写工具制造商:A.T. Cross(高仕)
2018/01/30 全球购物
什么是动态端口(Dynamic Ports)?动态端口的范围是多少?
2014/12/12 面试题
读书心得体会
2013/12/28 职场文书
求职信的最佳写作思路
2014/02/01 职场文书
Mysql数据库值的添加、修改、删除及清空操作实例
2021/06/20 MySQL
python开发飞机大战游戏
2021/07/15 Python
「回转企鹅罐」10周年纪念展「輪るピングドラム展」海报公开
2022/03/22 日漫