翼度科技»论坛 编程开发 html5 查看内容

canvas实现贪食蛇的实践

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
贪食蛇

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

  3. const width = 400
  4. const height = 400
  5. const cellLength = 20

  6. let foodPosition

  7. let initSnake = [
  8.   [0, 0],
  9.   [1, 0],
  10.   [2, 0],
  11. ]

  12. let snake = [...initSnake]

  13. let direction = "right"

  14. let canChangeDirection = true
复制代码
canvas 绘制页面
  1. //  背景
  2. function drawBackground() {
  3.   ctx.strokeStyle = "#bfbfbf"
  4.   for (let i = 0; i <= height / cellLength; i++) {
  5.     ctx.beginPath()
  6.     ctx.moveTo(0, cellLength * i)
  7.     ctx.lineTo(width, cellLength * i)
  8.     ctx.stroke()
  9.   }

  10.   for (let i = 0; i <= width / cellLength; i++) {
  11.     ctx.beginPath()
  12.     ctx.moveTo(cellLength * i, 0)
  13.     ctx.lineTo(cellLength * i, height)
  14.     ctx.stroke()
  15.   }
  16. }

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

  24.     ctx.fillRect(
  25.       snake[i][0] * cellLength,
  26.       snake[i][1] * cellLength,
  27.       cellLength,
  28.       cellLength
  29.     )
  30.   }
  31. }

  32. // 绘制食物

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

  49. // 绘制
  50. function drawFood() {
  51.   ctx.fillStyle = "#ff7875"
  52.   ctx.fillRect(
  53.     foodPosition[0] * cellLength,
  54.     foodPosition[1] * cellLength,
  55.     cellLength,
  56.     cellLength
  57.   )
  58. }
复制代码
蛇的移动
  1. // 蛇的移动
  2. // 确定下一次移动的位置,将这个点push到数组末尾(头的位置),
  3. // 将数组第一项shift出来(尾的位置)

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

  6. function snakeMove() {
  7.   let next
  8.   let last = snake[snake.length - 1]
  9.   // 根据方向确定下一个蛇头的位置
  10.   switch (direction) {
  11.     case "up": {
  12.       next = [last[0], last[1] - 1]
  13.       break
  14.     }
  15.     case "down": {
  16.       next = [last[0], last[1] + 1]
  17.       break
  18.     }
  19.     case "left": {
  20.       next = [last[0] - 1, last[1]]
  21.       break
  22.     }
  23.     case "right": {
  24.       next = [last[0] + 1, last[1]]
  25.       break
  26.     }
  27.   }

  28.   // 边缘碰撞
  29.   const boundary =
  30.     next[0] < 0 ||
  31.     next[0] >= width / cellLength ||
  32.     next[1] < 0 ||
  33.     next[1] >= height / cellLength

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

  36.   // 碰撞重新开始游戏
  37.   if (boundary || selfCollision) {
  38.     return restart()
  39.   }

  40.   snake.push(next)

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

  47.   canChangeDirection = true
  48. }
复制代码
事件监听
  1. document.addEventListener("keydown", (e) => {
  2.   switch (e.key) {
  3.     case "ArrowUp":
  4.       if (direction === "down" || !canChangeDirection) return
  5.       direction = "up"
  6.       canChangeDirection = false
  7.       break
  8.     case "ArrowDown":
  9.       if (direction === "up" || !canChangeDirection) return
  10.       direction = "down"
  11.       canChangeDirection = false
  12.       break
  13.     case "ArrowLeft":
  14.       if (direction === "right" || !canChangeDirection) return
  15.       direction = "left"
  16.       canChangeDirection = false
  17.       break
  18.     case "ArrowRight":
  19.       if (direction === "left" || !canChangeDirection) return
  20.       direction = "right"
  21.       canChangeDirection = false
  22.       break
  23.   }
  24. })
复制代码
requestAnimationFrame 实现动画
  1. // 默认的requestAnimationFrame循环应该是60帧,对于这个游戏来说太快了。
  2. // 所以做了限制,5次loop才渲染(蛇移动一格)一次
  3. function animate() {
  4.   let count = 0
  5.   function loop() {
  6.     if (++count > 5) {
  7.       draw()
  8.       count = 0
  9.     }
  10.     requestAnimationFrame(loop)
  11.   }
  12.   requestAnimationFrame(loop)
  13. }
复制代码
BUG 解决

游戏规则中,蛇是不能反向移动的。但是在事件回调中,如果改变方向过快,(5 次 loop 才执行一次重绘),就会出现方向变了,但是蛇的位置没变(比如蛇向右移动,我们先按上再按左),他就和自身碰撞了解决方案:我加了一个 canChangeDirection 变量,当你改变方向之后,必须等待蛇移动了才能再次改变方向
  1. // 事件回调
  2. case "ArrowUp":
  3.   if (direction === "down" |!canChangeDirection) return
  4.   direction = "up"
  5.   canChangeDirection = false
  6.   break
复制代码
到此这篇关于canvas实现贪食蛇的实践的文章就介绍到这了,更多相关 canvas贪食蛇内容请搜索脚本之家以前的文章或继续浏览下面的相关文章,希望大家以后多多支持脚本之家!

来源:https://www.jb51.net/html5/810952.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具