使用Canvas和javaScript实现贪吃蛇
复刻经典:贪吃蛇
首先我们先将Canvas画布绘画出来
HTML
代码如下
<canvas id="myCanvas" width="600" height="600"></canvas>
Css
代码如下
<style>
#myCanvas{
background:#9d9d9d;
box-shadow:0 1px 10px #3e3e3e ;
}
</style>
Js
代码如下
var canv = document.getElementById('myCanvas') //获取前端的Dom
var ctx = canv.getContext('2d') //在2d平面上进行绘画
首先思考到我们经典的贪吃蛇是一个方块一个方块组成的,食物也是一个方块一个方块组成的,我们的画布也是一个600px×600px的方块,这样我们这个画布可以分解成400个 30px×30px的方块 他们的id分别为0~399
如图所示
基本思路
我们是用键盘事件keyCode
来实现的
我们学习过
左键:keyCode:37 上键:keyCode:38 右键:keyCode:39 下键:keyCode:40
接下来我们会用到这些 keyCode
然后我们如何根据解析id
来获取每一个方块的坐标呢
我们思考第一行的那些方块 它们的id
都是小于20
的 而我们的一贯思路认为这一行的坐标是 (x , 0
)
而这个坐标0
,是如何得到的呢?
????????
这一行的每一个元素对20
取整 得到的整数部分都是 0
而 0
就是这一行的纵坐标
同理第二行 对20
取整 得到的整数部分都是1
第三行,第四行 ......... 都是如此
那么横坐标是什么呢?
??????
那么横坐标应该是将他的id 取余
得到的 余数
即为他的横坐标
那么综上所述我们解析id
来获取坐标 行号 row
:~~(id/20) 列号:col
:id%20
坐标 x y 就应该是
x = col×块宽 y = row×块高
实现移动
根据移动方向 生成新的头部块next = snake[0] + 方向
将新生成的next 添加到snake数组的头部 我们用到unshift()
方法
并且我们删除掉最后一个尾部元素 这样我们保证在不吃到食物的情况下 蛇的身体长度不变
这里我们使用pop()
方法
如果吃到食物的话,我们就不删除尾部元素,使得snake体长+1
并且在别的地方生成新的食物
键盘事件
之前我们说到 KeyCode
:左:37 上:38 右:39 下:40
对应到贪吃蛇的 左:-1 上:-20 右:+1 下:+20
贪吃蛇的方向数组为[-1 , -20, 1 , 20] 每个数组的index
为 0 , 1 , 2 , 3
那么这个 index
怎么和这个KeyCode
取得联系呢
我们可以想到 index
为[KeyCode-37]
整明白以上的基础内容
我们开始正式的代码编写
首先 声明我们用到的一些变量
var g_block = 30
var g_row = 600 / g_block
var g_col = 600 / g_block
var g_margin = 1 //为了方块之间有空隙这样会更美观
var snake = [45, 44, 43] //蛇是由53,54,55游戏块组成的
var food = 55 //自定义初始食物为58号游戏块
var dirc = 1 //定义方向是向右移动1个单位
var colors = ['#f5b6b3', '#ffebb5', '#89cff0'] //声明蛇的颜色 食物的颜色 和 背景颜色
然后我们自定义一个函数 setBlock
function setBlock(id, color) {
//我们声明setBlock函数
let [row, col] = [~~(id / g_col), id % g_col]//为他的row和col赋值
let [x, y] = [col * g_block, row * g_block]//对他的坐标赋值
ctx.save()
ctx.fillStyle = colors[color]
ctx.fillRect(x, y, g_block - g_margin, g_block - g_margin)
ctx.restore()
}
~~ 取整的意思
在上面
function setBlock(id, color)
我们在 setBlock
中传了两个参数 一个是 id
一个是 color
接下来我们会用到这两个参数
首先根据这个 setBlock
方法 我们先将我们的蛇体绘画出来
snake.forEach(n => setBlock(n, 0))
这样我将snake
里面三个方块都绘制成了淡粉色 也就是用到了第二个参数 color
值为'#f5b6b3'
效果如下
这样完成了初始三个游戏块的绘制
接下来就是绘制一个食物并让它下一步进行移动了
我们让他向右移动一格
也就是 在头部添加一个淡粉色的游戏块
在尾部删除最后一个游戏块
代码如下
setBlock(food,1)
//id为 food color:第一个元素
//将food绘画出来
//贪吃蛇的增添
let next = snake[0]+dirc
snake.unshift(next)
setBlock(next,0)
//id 为next color: 为第零个元素
let tail = snake.pop()
setBlock(tail)
//id 为tail color 为第二个元素
这样的话,我们会得到这样一个效果
很明显,我们在尾部,删除的元素,他默认给绘制成了黑色
这样我们修改代码如下,让他删除,并将最后一个元素绘制成和背景一样的颜色
setBlock(tail,2)
这个基本的移动规律是这样的
我们该怎么让他动起来呢
我们想到应该使用 定时器 setInterval
修改代码如下
setInterval(() => {
//贪吃蛇的增添
let next = snake[0] + dirc
snake.unshift(next)
setBlock(next, 0)
//id 为next color: 为第零个元素
let tail = snake.pop()
setBlock(tail, 2)
//id 为tail color 为第二个元素
// 但是我们该怎么让他动起来呢
//我们想到用定时器
}, 500)
这样我们就实现了贪吃蛇的基本移动
但是我们这样的话贪吃蛇移动了,但是他吃掉食物的时候
第一:他的身体没有增长
第二:没有生成新的食物
如图所示
我们做食物碰撞判断
修改代码如下
setInterval(() => {
//贪吃蛇的增添
let next = snake[0] + dirc
snake.unshift(next)
setBlock(next, 0)
//id 为next color: 为第零个元素
if (next == food){
//在其他地方生成新的食物,并绘制游戏块:食物
food=~~(Math.random()*400)
setBlock(food,1)
}else {
let tail = snake.pop()
setBlock(tail, 2)
//id 为tail color 为第二个元素
}
// 但是我们该怎么让他动起来呢
//我们想到用定时器
}, 500)
效果如下
这里我们存在一个小BUG
是我们随机生成食物的时候
food=~~(Math.random()*400)
因为我们是随机生成的,所以我们不知道我们是否是生成到蛇的身体上,这样就不符合实际了
我们先存在这个疑问
我们先实现根据上下左右键实现移动
实现自定义移动,我们用键盘事件 keyCode
来实现
首先我们做补充
键盘事件的一个监听事件
代码如下
document.addEventListener('keydown', (event) => {
console.log(event.code, event.keyCode)
})
这样我们监听到键盘按下它的keyCode
我们检查一下控制台输出 keyCode
让他根据键盘的 keyCode
和方向数组为[-1 , -20, 1 , 20] 取得联系
上面我们有讲到 index
为[KeyCode-37]
完善代码如下
document.addEventListener('keydown', (event) => {
dirc = [-1, -20, 1, 20][event.keyCode - 37]
console.log(dirc)
})
我们控制台输出 dirc
应该就是 左:-1 上:-20 右:+1 下:+20
如图所示
这样我们就基本实现了贪吃蛇的基本移动效果
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?