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的三种$()
Dec 30 Javascript
JavaScript中将一个值转换为字符串的方法分析[译]
Sep 21 Javascript
JavaScript随机生成信用卡卡号的方法
Apr 07 Javascript
浅谈javascript 函数表达式和函数声明的区别
Jan 05 Javascript
基于jQuery实现左侧菜单栏可折叠功能
Dec 27 Javascript
JS简单实现点击按钮或文字显示遮罩层的方法
Apr 27 Javascript
jquery加载单文件vue组件的方法
Jun 20 jQuery
vue父组件向子组件动态传值的两种方法
Nov 11 Javascript
node.js使用免费的阿里云ip查询获取ip所在地【推荐】
Sep 03 Javascript
d3.js实现图形缩放平移
Dec 19 Javascript
uniapp实现可以左右滑动导航栏
Oct 21 Javascript
Typescript类型系统FLOW静态检查基本规范
May 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和ACCESS写聊天室(四)
2006/10/09 PHP
Gregarius中文日期格式问题解决办法
2008/04/22 PHP
Windows平台实现PHP连接SQL Server2008的方法
2017/07/26 PHP
laravel项目利用twemproxy部署redis集群的完整步骤
2018/05/11 PHP
jscript之Read an Excel Spreadsheet
2007/06/13 Javascript
SOSO地图API使用(一)在地图上画圆实现思路与代码
2013/01/15 Javascript
jQuery实现可拖动的浮动层完整代码
2013/05/27 Javascript
原生js ActiveXObject获取execl里面的值
2013/11/01 Javascript
使用JS或jQuery模拟鼠标点击a标签事件代码
2014/03/10 Javascript
Node.js与PHP、Python的字符处理性能对比
2014/07/06 Javascript
js获取新浪天气接口的实现代码
2016/06/06 Javascript
Ionic + Angular.js实现图片轮播的方法示例
2017/05/21 Javascript
微信小程序icon组件使用详解
2018/01/31 Javascript
OpenLayers实现图层切换控件
2020/09/25 Javascript
vue3.0+vue-router+element-plus初实践
2020/12/02 Vue.js
[10:24]郎朗助力完美“圣”典,天籁交织奏响序曲
2016/12/18 DOTA
python Django模板的使用方法(图文)
2013/11/04 Python
Python实现打印螺旋矩阵功能的方法
2017/11/21 Python
Python numpy 常用函数总结
2017/12/07 Python
django用户注册、登录、注销和用户扩展的示例
2018/03/19 Python
使用Python快速制作可视化报表的方法
2019/02/03 Python
Python socket模块实现的udp通信功能示例
2019/04/10 Python
python opencv把一张图片嵌入(叠加)到另一张图片上的实现代码
2020/06/11 Python
Kathmandu澳洲户外商店:新西兰户外运动品牌
2017/11/12 全球购物
加拿大约会网站:EliteSingles.ca
2018/01/12 全球购物
Vans荷兰官方网站:美国南加州的原创极限运动潮牌
2018/01/23 全球购物
美国精品地毯网站:Boutique Rugs
2020/03/04 全球购物
解释一下ruby中的特殊方法与特殊类
2013/02/26 面试题
生日主持词
2014/03/20 职场文书
幼儿园端午节活动方案
2014/08/25 职场文书
端午节寄语2015
2015/03/23 职场文书
教师考核鉴定意见
2015/06/05 职场文书
天那边观后感
2015/06/09 职场文书
《自己去吧》教学反思
2016/02/16 职场文书
一篇文章弄懂Python关键字、标识符和变量
2021/07/15 Python
动作冒险《Hell Is Us》将采用虚幻5 消灭怪物探索王国
2022/04/13 其他游戏