canvas实现贪食蛇的实践
发布时间:2022-02-14 16:40:07 作者:zzzzxcv 我要评论
本文主要介绍了canvas实现贪食蛇的实践,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
贪食蛇
最近 dan 有在油管上直播,播放量最多的就是手写一个贪食蛇。
本来想学一下大佬写代码的姿势,看了几分钟就没了耐性,心想我为什么不能自己写一个呢。
一步一步跟着敲代码,我实践了一段时间但是收效甚微,因为中间少了自己的思考。
初期可能有些作用,可以学到一些技巧和规范。
但是自己实现一个东西带来的成就感,你不断的 debug 和查文档查资料留下的记忆和习惯,这大概就是这个玩意带给我最大的收获吧。
我的 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贪食蛇内容请搜索脚本之家以前的文章或继续浏览下面的相关文章,希望大家以后多多支持脚本之家!
相关文章
- 本文详细介绍了Checkbox的基础属性、样式自定义及其应用场景,Checkbox通过<input>元素实现,样式自定义可以通过隐藏原始Checkbox、使用伪元素、JavaScript来实现,Chec2024-10-16
- 本文介绍了HTML5中表单的自动验证、取消验证、自定义错误信息等功能,通过required、pattern、min、max和step属性,可以实现对表单输入的自动检查,novalidate和formnovalida2024-09-23
- 在HTML5项目中使用NotoSansCJK字体能提升用户体验,本文介绍了通过GoogleFonts在线加载和本地托管两种方法,在线加载无需下载,通过GoogleFonts链接快速引入,适合离线使用或2024-09-23
- HLS是一种基于HTTP协议的流媒体传输协议,它的出现使得视频的传输更加稳定和可靠,本文给大家介绍Html5如何播放hls格式的视频,感兴趣的朋友一起看看吧2024-08-19
- 使用<blockquote>标签可以实现页面文字的段落缩进,这一标签也是每使用一次,段落就缩进一次,并且可以嵌套使用,以达到不同的缩进效果,本文通过实例代码介绍HTML5使2024-07-08
- 这篇文章主要介绍了window.open()各参数示例详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧2024-06-19
- 在现代互联网应用中,有时我们需要让用户在网页上进行签字操作,比如确认文件、填写电子表格或者签署合同,利用 HTML5 的 canvas 画布,我们可以轻松地实现这一功能,为用2024-06-19
uniapp在h5页面实现扫码功能(html5-qrcode)
这篇文章主要介绍了uniapp在h5页面实现扫码功能(html5-qrcode),本文结合实例代码给大家介绍的非常详细,需要的朋友可以参考下2024-06-11- a 元素可通过其href属性创建指向其他网页、文件、同一页面内的位置或其他URL的超链接,这篇文章主要介绍了HTML5中的A 标签,需要的朋友可以参考下2024-05-28
- button 用于显示一个可点击的按钮,可用在表单或文档的其它地方,button元素是内联双标签,不同浏览器下button样式不同,可以通过CSS修改,这篇文章主要介绍了HTML5 中的Bu2024-05-28
最新评论