首先声明,代码不是我写的,是我从网上看到大佬写的(侵权私我),感到有兴趣,所以拿来研究了下。。。
原帖点这里啊
变量说明
- snake
存储蛇的数组,之后可用于判断前进方向、是否撞到身体等。。 - bean
用于存储豆子的位置 - direction
控制蛇的方向 - head
蛇头当前的位置
思路
- canvas 绘制蛇行走的面板(网格思路)
- 使用数组存储蛇
- draw 函数用于绘制蛇、豆子
- 向 direction 所指方向绘制蛇头
- 未吃到豆子则删除最后一个身体,让其变为黑色
理解面板
首先绘制 400*400 大小的 canvas 面板,蛇的身体每一节、果实都分别占 20*20 大小的区域(即每个大小为 20*20),所以整体网格可以分为 20*20个小的网格,这样就可以抽象出以下网格。
![](https://img.haomeiwen.com/i9696872/96062c7f936db101.png)
面板左上角从 0 开始编号,第一行为 0~19 第二行为 20~39,以此类推,最后一行为 380~399。至此,面板上的每一个格都代表一个位置(蛇、果实的位置)。
理解豆子、蛇的位置表示
首先贴出代码。。。。
// 创建蛇数组,存放蛇身的绘图位置
var snake = [42, 41];
// 创建豆子,存放豆子初始出现的位置
var bean = 43;
snake 数组表示蛇身体所占的网格位置,bean 表示豆子出现的位置,上面理解了网格面板,现在可以找找蛇和豆子的位置大概在哪里了,密集恐惧的话就算了,问题不大。。。
理解 draw 函数
首先贴出代码。。。。
function draw(location, color) {
ctx.fillStyle = color;
// location % 20 * 20 + 1 取位置的个位乘格的宽度
ctx.fillRect(location % 20 * 20 + 1, parseInt(location / 20) * 20 + 1, 18, 18);
}
要理解 draw,先要知道 fillStyle、fillRect 是怎么个意思,不用百度了,我给你大概说下。。。
fillStyle 设置画笔的填充颜色
fillRect(x, y, width, height) 从 (x, y) 点开始画个矩形宽 width 高 height 的矩形
好了接下来就可以理解 draw 函数在干嘛了,就是设置填充颜色,然后画个矩形。
fillRect 函数里参数一大堆可能觉得很蒙蔽,其实挺简单的哈,跟着我理解下。
location 表示要在哪个编号上画矩形,例如:43 号,画矩形的起点为 (x, y)。编号除 20 取余可知在第几列,然后乘宽度就是横坐标了(x = location % 20 * 20),纵坐标的求法类似,除 20 取整,可知在第几行,然后乘 20(y = parseInt(location / 20) * 20)
至于为什么加一,还要设置宽高为 18(说好的 20),是为了让蛇身体间有点空隙,看下图,绿色是蛇占的区域,外层红框是一个 20*20 区域,这样可留出 1 的空白。
![](https://img.haomeiwen.com/i9696872/50f265f7abd6e1b2.png)
理解键盘监视 onkeydown
简单来说,就是当你键盘按下去的时候,浏览器会接受一个 keyCode,可以利用 keyCode 区别按下了哪个键。
上代码。。。。
document.onkeydown = function (e) {
e = e || event;
/**
* 1.获取方向键
* -1 向左退一格
* 1 向右前进一格
* 20 向下走一格
* -20 向上走一格
* 左、上、右、下的 keycode 分别对应了 37 38 39 40
* 2.(snake[1] - snake[0]) 可以获取蛇当前前进的反方向
* 当前进方向一致或相反时,direction 不变
*/
if(e.keyCode < 37 || e.keyCode > 40 || (snake[1] - snake[0] == [-1, -20, 1, 20][e.keyCode - 37])){
direction = direction;
}
else {
direction = [-1, -20, 1, 20][e.keyCode - 37];
}
// direction = (snake[1] - snake[0] == (head = [-1, -20, 1, 20][(e || event).keyCode - 37] || direction)) ? direction : head;
};
-1、1、20、-20 所代表方向,按网格理解(一行 20 个),不理解自己画画,其他注释挺清楚,不做解释了。
理解碰撞检测
代码。。。
if (snake.indexOf(head, 1) > 0 || head < 0 || head > 399 || (direction == -1 && head % 20 == 19) || (direction == 1 && head % 20 == 0)){
return alert("GAME OVER");
}
一下一下来啊。
1.snake.indexOf(head, 1)> 0
说明除首位,还有别的身体部位和占了头一样的位置,前面说了 snake 用数组存储身体所在位置,若 head 的位置在除第一个以外的存在,说明头撞到身体了,game over。。
2.head < 0 || head > 399
头不能冲出网格的界限,否则 game over。。。
3.(direction == -1 && head % 20 == 19)
方向向左时,头部能整除 20,说明头部在最左边了,除 20 取余为 19 说明在最右边了。-1 代表左边,但是和右边界搭配,为啥呢,你试试就知道了,不这样到不了最边边就撞了,试试吧,不做解释了。。。。
理解蛇的爬行
代码不来了,简单说下就是,先让头部加上方向的值,就可以控制上下移动,然后推入 snake 身体数组,之后判断是否碰撞了,撞了就 over,没撞继续爬。。。没吃豆豆的情况下,每一次爬,末尾的身体被销毁,变成黑色(因为没吃豆豆蛇长度不变,头部推入,尾部销毁)。吃了豆豆的画,尾部就不销毁了,表示身体长了一节。
理解自执行
代码来。。。。
!function () {
// 绘制蛇头
head = snake[0] + direction;
snake.unshift(head);
if (snake.indexOf(head, 1) > 0 || head < 0 || head > 399 || (direction == -1 && head % 20 == 19) || (direction == 1 && head % 20 == 0)){
// console.log("direction = " + direction + "head = " + head);
return alert("GAME OVER");
}
draw(head, "Lime");
// 绘制豆子
if (head == bean) {
while (snake.indexOf(bean = parseInt(Math.random() * 400)) >= 0);
draw(bean, "Yellow");
} else {
// 没有吃到豆子,则将蛇尾绘制为背景色(黑色)
draw(snake.pop(), "Black");
}
// arguments.callee 指向参数的所属函数,即回调当前函数
// setTimeout 设置绘图的时间,可改变蛇的速度(游戏难度)
setTimeout(arguments.callee, 150);
}();
!function () {}();
匿名函数自动执行。
setTimeout(arguments.callee, 150);
延迟执行函数,过 150 ms 执行一次第一个参数代表的函数。arguments.callee
表示参数所属函数,就是这个函数本身,俗称回调。
结语
基本就这些了。。。。奉上代码,与原版有所改动。。。。。
<!doctype html>
<html>
<body style='overflow:hidden'>
<canvas id="can" width="400" height="400" style="background:Black;display: block;margin:20px auto;"></canvas>
<script>
// 创建蛇数组,存放蛇身的绘图位置
var snake = [42, 41];
// 创建豆子,存放豆子初始出现的位置
var bean = 43;
// 控制蛇的方向
var direction = 1;
var head;
// 获取画笔
var ctx = document.getElementById("can").getContext("2d");
function draw(location, color) {
ctx.fillStyle = color;
// location % 20 * 20 + 1 取位置的个位乘格的宽度
ctx.fillRect(location % 20 * 20 + 1, parseInt(location / 20) * 20 + 1, 18, 18);
}
document.onkeydown = function (e) {
e = e || event;
/**
* 1.获取方向键
* -1 向左退一格
* 1 向右前进一格
* 20 向下走一格
* -20 向上走一格
* 左、上、右、下的 keycode 分别对应了 37 38 39 40
* 2.(snake[1] - snake[0]) 可以获取蛇当前前进的反方向
* 当前进方向一致或相反时,direction 不变
*/
if(e.keyCode < 37 || e.keyCode > 40 || (snake[1] - snake[0] == [-1, -20, 1, 20][e.keyCode - 37])){
direction = direction;
}
else {
direction = [-1, -20, 1, 20][e.keyCode - 37];
}
// direction = (snake[1] - snake[0] == (head = [-1, -20, 1, 20][(e || event).keyCode - 37] || direction)) ? direction : head;
};
// 自执行匿名函数
!function () {
// 绘制蛇头
head = snake[0] + direction;
snake.unshift(head);
if (snake.indexOf(head, 1) > 0 || head < 0 || head > 399 || (direction == -1 && head % 20 == 19) || (direction == 1 && head % 20 == 0)){
// console.log("direction = " + direction + "head = " + head);
return alert("GAME OVER");
}
draw(head, "Lime");
// 绘制豆子
if (head == bean) {
while (snake.indexOf(bean = parseInt(Math.random() * 400)) >= 0);
draw(bean, "Yellow");
} else {
// 没有吃到豆子,则将蛇尾绘制为背景色(黑色)
draw(snake.pop(), "Black");
}
// arguments.callee 指向参数的所属函数,即回调当前函数
// setTimeout 设置绘图的时间,可改变蛇的速度(游戏难度)
setTimeout(arguments.callee, 150);
}();
</script>
</body>
</html>
网友评论