最近参加了baidu_ife_2017,里面有个任务是用canvas写贪吃蛇游戏,游戏有三种玩法,不过现在我也只是完成了前两种(普通模式和过关模式),想记录一下遇到的几个问题和解决方法,各位走过路过也可以给些建议。可以到此试玩一下:Snake Online
1.如何让蛇动起来
用canvas来写游戏,无非就是设置个定时器,不断清空画布然后重绘。而让蛇动起来,让其身体每一截都往前移动,这显然是非常麻烦的一件事,比如蛇身现在弯弯曲曲,而其每一截都要向爬行方向移动一格,还需要给每一截重新计算下一步的位置。在我看来是十分麻烦的一件事,于是思索一番,发现蛇每次只是移动一格,那么如果我只改变最后一截,让其位置变为爬行方向的下一格不就解决了吗。想到这,我用一个数组来维护每一截的位置对象:
function Snake(args) {
this.bodyArr = [];
// 每次移动蛇头x,y值改变量
this.dx = 0;
this.dy = 0;
//...
}
Snake.prototype.setBodyArr = function () {
var newX = this.bodyArr[0].x + this.dx;
var newY = this.bodyArr[0].y + this.dy;
// 移除最后一截
var temp = this.bodyArr.pop();
// 将下一次要移动的位置插入到位置数组中
this.bodyArr.unshift({
x: newX,
y: newY
});
// ...
};
2.食物的随机位置如何设置
或许大家觉得这个很简单,不就是产生个随机数,然后避开蛇身的位置就完事了么。我也是这么想的,但是想动手时却犯难了,我觉得应该是可以优化的。毕竟每产生一次随机数,就要和蛇身每一截去比较一次,重叠了又要重新产生随机数再全部比较一次… 虽然废不了多少时间,但总觉得可以优化,于是在github上找别人的解决方案,发现很多人都是使用一个二维数组来维护地图上每个位置的状态的,觉得不错于是将其用上了:
function Snake(args) {
// position[x][y] = 0 为空,
// position[x][y] = 1 为蛇身,
// position[x][y] = 2 为食物
this.position = creat2dArr(width, height);
//...
}
// 产生一个值全为0的二维数组
function creat2dArr(width, height) {
var arr = new Array(width);
for(var j = 0; j < width; j++) {
arr[j] = new Array(height);
for(var k = 0; k < height; k++) {
arr[j][k] = 0;
}
}
return arr;
}
每次只需比较 position[x][y] 的值即可知道该位置的状态。
3.蛇爬行方向的改变
游戏是通过方向键来控制的,那么如果玩家在两次重绘之间,按下了多次不同的方向键,那么蛇下一次爬行的方向该定为什么?怎么解决这个冲突?我想到的是用一个队列来保存玩家的输入,每次重绘前取出队列的首项,如果该方向合理(即不是与当前方向相同或相反),则修改下一步的方向,通过这种方式,玩家的每一次输入都可以得到响应,只是快与慢的问题罢了。
function Snake(args) {
// 爬行方向("up": 上,"down": 下,"left": 左,"right": 右)
this.directionQueue = [];
this.nextDirection = "up"; // 定义初始爬行方向为上
//...
}
// 设置下一次爬行的方向
Snake.prototype.setNextDirection = function () {
if(this.directionQueue.length === 0) { // 如果用户没有输入下一个方向则返回
return;
}
var temp = "";
while(this.directionQueue.length !== 0) {
if((temp = this.directionQueue.shift()) !== this.nextDirection) {
// 判断方向合法性
if(this.judgeLegality(temp)) {
this.nextDirection = temp;
// 设置dx与dy的值
if(temp === "up") {
this.dx = 0;
this.dy = -1;
} else if(temp === "down") {
this.dx = 0;
this.dy = 1;
} else if(temp === "left") {
this.dx = -1;
this.dy = 0;
} else {
this.dx = 1;
this.dy = 0;
}
break;
}
}
}
};
// 页面加载完成后
window.onload = function () {
var snake = new Snake(args);
// 注册键盘事件
onEvent(document, "keydown", function (e) {
e = e || window.event;
if(e.keyCode === 37) { // left
snake.directionQueue.push("left");
} else if(e.keyCode === 38) { // up
snake.directionQueue.push("up");
} else if(e.keyCode === 39) { // right
snake.directionQueue.push("right");
} else if(e.keyCode === 40) { // down
snake.directionQueue.push("down");
}
});
};
结语
这些是我做贪吃蛇时碰到的几个问题,写出来分享下。如果觉得太白痴了请略过… 如果觉得还可以改进,欢迎在下方留言。