版权声明:本文为博主原创文章,允许转载,但转载必须注明出处并附带首发链接 https://blog.csdn.net/Nana_9457/article/details/86570234
源码见github: https://github.com/NanaYang007/Canvas__DoodleSnake
喜欢请给个star,谢谢~
一起来实现一个贪吃蛇吧~
0. 创建canvas获取绘制环境
首先在网页中引入canvas标签,注意有些浏览器不支持canvas,可以给出提示。
<html>
<meta charset="utf8">
<style>
body {
background-color: black;
}
canvas {
background-color: white;
}
</style>
<body>
<canvas id="mySnake" width="400" height="300">
<span>该浏览器不支持canvas</span>
</canvas>
<script>
var cv = document.getElementById("mysnake");
var ctx = cv.getContext("2d");
</script>
</body>
</html>
1. 绘制方块
// 小方块的大小设置为常量,便于修改
const STEP = 10;
// 四个参数分别为(距canvas左边缘距离、距上边缘距离、矩形宽、矩形高)
ctx.fillRect(10, 10, STEP, STEP);
2. 计时器
贪吃蛇作为一个动态游戏,需要一个定时器
⚠️设置高度时不能使用var top = 0
,top是window上的一个对象,会出现canvas不显示动态方块的bug(控制台无报错)
var blockLeft = STEP;
// 变量名不能设置为“top”
var blockTop = STEP;
var stepLength = STEP;
setInterval(function(){
// 每次重画前需要清画布
ctx.clearRect(cv.offsetLeft, cv.offsetTop, cv.offsetWidth, cv.offsetHeight);
blockLeft += stepLength;
blockTop += stepLength;
console.log(top);
ctx.fillRect( blockLeft, blockTop, STEP, STEP);
}, 300);
现在我们就得到一个从左上角不断向右下方跳的小黑方块了~
3. 获取鼠标事件
蛇的运动方向是用户通过操作键盘的“上、下、左、右”按键实现的,所以需要获取到键盘点击事件,
另外,每一步向左(右)、向上(下)运动的步距也是点击一次就改变并维持到下一次点击键盘的。
所以首先需要将stepLength变成有两个方向(包含正负属性)的变量
// var stepLength = STEP;
var stepLeft = STEP;
var stepTop = STEP;
然后获取键盘点击事件:
blockLeft += stepLeft;
blockTop += stepTop;
document.onkeyup = function(e){
var e = e || widnow.event;
// 左 37 上 38 右 39 下 40(自行补齐)
if( e.keyCode == 37){
stepLeft = - STEP;
stepTop = STEP;
}
}
4. 设置撞墙情况
canvas除了可以绘制图形,还可以绘制文字
ctx.font = 'bold 25px Arial';
ctx.fillText("Game Over", 100, 100, 300);
定时器在撞墙后需要关闭,所以需要定义一下定时器名字
var timer = setInterval(function(){...}, 300);
为了让撞墙的小方块停在那里,需要将清除画布的逻辑放到判断撞墙这里
// 撞墙
if( blockLeft <= cv.offsetLeft || blockLeft >= cv.offsetLeft + cv.offsetWidth - STEP ||
blockTop <= cv.offsetTop || blockTop >= cv.offsetTop + cv.offsetHeight - STEP){
clearInterval(timer);
ctx.font = 'bold 25px Arial';
ctx.fillText("Game Over", 100, 100, 300);
} else {
// 每次重画前需要清画布
ctx.clearRect(cv.offsetLeft, cv.offsetTop, cv.offsetWidth, cv.offsetHeight);
}
5. 单个方块变成list
首先,需要建立一个list,并初始化它的位置
const SNAKE_LENGTH = 4; // 蛇的长度(方块数量)
var snake = [];
// 初始化list
for(var i = SNAKE_LENGTH - 1; i >= 0; i--){
snake.push({
left: STEP,
top: i * STEP
});
}
每次绘制方块变成绘制list
// ctx.fillRect( blockLeft, blockTop, STEP, STEP);
snake.forEach(function(item){
ctx.fillRect(item.left, item.top, STEP, STEP);
})
初始化位移方向
snake.forEach(function(item){
item.left += stepLeft;
item.top += stepTop;
})
判断撞墙情况需要通过snake的头部节点的left/top判断
// 撞墙
if( snake[0].left <= cv.offsetLeft || snake[0].left >= cv.offsetLeft + cv.offsetWidth - STEP ||
snake[0].top <= cv.offsetTop || snake[0].top >= cv.offsetTop + cv.offsetHeight - STEP){}
6. 处理蛇的多次转弯
贪吃蛇的行为是:键盘点击后,蛇头的运动方向改变了,但是蛇身的运动方向还是原来的,直到蛇身的某一点到达鼠标点击时蛇头的位置(设为转折点),运动方向才切换为蛇头的运动方向。
用户多次点击键盘蛇身随时间推移会表现出多次转弯效果。
算法有多种实现,如考虑到转折点、将方向存成列表计算区间等,可以设计出不同时空复杂度的方法,此处不考虑空间复杂度,实现时间复杂度为o(1)的方案:蛇头判断是否改变方向,蛇尾擦除一个方块。
var snakeHead = {
left: snake[0].left,
top: snake[0].top
};
snake.unshift({
left: snakeHead.left + stepLeft,
top: snakeHead.top + stepTop
});
snake.pop();
7. 加入食物?
首先障碍物的位置随机,需要js的Math.random()
,生成0.0~1.0之间的随机数。
// timer外部:
// 添加食物
var foodX = Math.ceil(Math.random() * (cv.offsetWidth / 10)) * 10;
var foodY = Math.ceil(Math.random() * (cv.offsetHeight / 10)) * 10;
var foodColor = "#" + Math.ceil(Math.random() * 255).toString(16) + Math.ceil(Math.random() * 255).toString(16) + Math.ceil(Math.random() * 255).toString(16);
var snakeColor = "black";
// timer内部:
//吃到食物
if(snake[0].left == foodX && snake[0].top == foodY){
snakeColor = foodColor;
snake.push({
left: snake[snake.length - 1].left,
top: snake[snake.length - 1].top
});
foodX = Math.ceil(Math.random() * ( (cv.offsetWidth - 100) / 10)) * 10 + 50;
foodY = Math.ceil(Math.random() * ( (cv.offsetHeight - 100 ) / 10)) * 10 + 50;
foodColor = "#" + Math.ceil(Math.random() * 255).toString(16) + Math.ceil(Math.random() * 255).toString(16) + Math.ceil(Math.random() * 255).toString(16);
}
snake.forEach(function(item, index){
ctx.save();
ctx.fillStyle = snakeColor;
ctx.fillRect(item.left, item.top, STEP, STEP);
ctx.restore();
});
// 添加食物
ctx.save();
ctx.fillStyle = foodColor;
ctx.fillRect(foodX, foodY, STEP, STEP);
ctx.restore();
实现效果如下: