Canvas之贪吃蛇
js
const canvas1 = document.createElement('canvas');
const canvas2 = document.createElement('canvas');
function init(canvas) {
document.body.append(canvas);
canvas.width = 400;
canvas.height = 400;
}
init(canvas1);
init(canvas2);
const cell = 20;//每一个格子的大小
const ctx1 = canvas1.getContext('2d');
//绘制棋盘
ctx1.save()
ctx1.strokeStyle = '#ccc'
for (let i = 0; i < canvas1.width / cell; i++) {
ctx1.beginPath()
ctx1.moveTo(i * cell, 0);
ctx1.lineTo(i * cell, canvas1.height)
ctx1.stroke()
console.log(i)
ctx1.beginPath()
ctx1.moveTo(0, i * cell);
ctx1.lineTo(canvas1.width, i * cell)
ctx1.stroke()
}
ctx1.restore()
贪吃蛇包括棋盘,蛇(蛇头和身体),食物。棋盘作为静态的,其他设置为动态。定义每个格子大小,绘制竖线和横线。
js
const ctx2=canvas2.getContext('2d')
//绘制矩形
class Rect{
constructor(x,y,w,h,color){
this.x = x
this.y = y
this.w = w
this.h = h
this.color = color
}
draw(){
ctx2.save()
ctx2.beginPath()
ctx2.fillStyle = this.color
ctx2.fillRect(this.x,this.y,this.w,this.h)
ctx2.restore()
}
}
//绘制蛇
class Snake{
constructor(x,y,dir='ArrowRight'){
this.x=x*cell;
this.y=y*cell;
this.dir=dir;
this.head=new Rect(this.x,this.y,cell,cell,'#f00');
this.body=[];
}
draw(){
this.head.draw();
this.body.forEach(rect=>rect.draw())
}
}
const snake = new Snake(5,5);
snake.draw();
然后开始绘制组成蛇和食物的矩形,给定绘制一个矩形的参数,然后是绘制蛇头和身体,都使用面向对象的方式。
js
//绘制矩形
class Rect {
draw() {
ctx2.clearRect(this.oldX, this.oldY, this.w, this.h);
this.oldX = this.x
this.oldY = this.y
ctx2.save()
ctx2.beginPath()
ctx2.fillStyle = this.color
ctx2.fillRect(this.x, this.y, this.w, this.h)
ctx2.restore()
}
}
//绘制蛇
class Snake {
move() {
switch (this.dir) {
case 'ArrowRight':
this.head.x += cell;
break;
case 'ArrowLeft':
this.head.x -= cell;
break;
case 'ArrowUp':
this.head.y -= cell;
break;
case 'ArrowDown':
this.head.y += cell;
break;
}
if(this.isOver()){
alert('游戏结束');
return;
}
this.draw();
setTimeout(() => {
this.move()
}, 200);
}
isOver(){
return this.head.x < 0 || this.head.y < 0 || this.head.x > canvas2.width || this.head.y > canvas2.height;
}
}
const snake = new Snake(5, 5);
snake.draw();
snake.move();
document.onkeydown = function(e){
//左37 上38 右39 下40
if(e.code == 'ArrowUp' || e.code == 'ArrowRight' || e.code == 'ArrowDown' || e.code == 'ArrowLeft'){
if(snake.dir == 'ArrowUp' && e.code == 'ArrowDown'
||snake.dir == 'ArrowDown' && e.code == 'ArrowUp'
||snake.dir == 'ArrowRight' && e.code == 'ArrowLeft'
||snake.dir == 'ArrowLeft' && e.code == 'ArrowRight'
) return ;
snake.dir = e.code ;
}
}
蛇要移动就得知道方向。通过 switch 设置各种情况,选择蛇头的位置,然后调用计时器函数每隔一段时间执行一次。注意,当下一个矩形出现的时候,上一个矩形需要清空。
设置键盘事件,当相反方向时不能移动。还要设置一个边界条件。
js
let grid = {};//为每一个坐标生成一个网格
function initGrid() {
for (let i = 0; i < canvas1.width / cell; i++) {
for (let j = 0; j < canvas1.height / cell; j++) {
grid[`${i * cell}-${j * cell}`] = 0;
}
}
}
initGrid();
class Rect {
draw() {
grid[`${this.oldX}-${this.oldY}`] = 0
grid[`${this.x}-${this.y}`] = 1
ctx2.clearRect(this.oldX, this.oldY, this.w, this.h);
this.oldX = this.x
this.oldY = this.y
ctx2.save()
ctx2.beginPath()
ctx2.fillStyle = this.color
ctx2.fillRect(this.x, this.y, this.w, this.h)
ctx2.restore()
}
}
let food;
function randomFood() {
while (true) {
const x = Math.floor(Math.random() * canvas1.width / cell) * cell;
const y = Math.floor(Math.random() * canvas1.height / cell) * cell;
console.log(grid[`${x}-${y}`])
if (grid[`${x}-${y}`] == 0) {
console.log('随机位置空闲的')
food = new Rect(x, y, cell, cell, '#00f');
food.draw();
break;
}
}
}
randomFood();
要想添加食物,首先需要添加一系列网格,把这些网格的坐标都设置为0。当蛇头移动到下一个坐标时,前一个坐标设置为0。创建随机坐标,保证随机坐标的网格为0,如果不为0,则一直创建。然后创建一个食物的矩形,绘制出来,结束循环。
js
draw() {
this.head.draw();
if (this.body.length > 0) {
this.body[0].draw();//只需要对第一个元素进行绘制
}
}
//判读是否吃到食物
if (this.isEat()) {
//身体要加长,创建一个矩形,代替原来蛇头的位置
const rect = new Rect(this.head.oldX, this.head.oldY);
this.body.unshift(rect);
randomFood();
} else {
//没有吃到食物,蛇移动时,只需要将最后一个矩形放到原来蛇头的位置
if (this.body.length > 0) {
const last = this.body.pop()
last.x = this.head.oldX;
last.y = this.head.oldY;
this.body.unshift(last);
}
}
//吃到食物
isEat() {
return food && this.head.x == food.x && this.head.y == food.y;
}
吃到食物就是蛇头的位置和食物的位置重合,当吃到食物时,创建一个矩形,加到身体开始的位置,这样不用所有位置都进行移动。没有吃到食物,蛇的移动就是身体最后的元素放到蛇头原来的位置,这样同样可以避免重复绘制。
注意,这时只需要对身体的第一个元素进行绘制。并且保证在绘制和蛇移动的时候身体的长度大于0。
js
isOver() {
return this.head.x < 0 || this.head.y < 0 || this.head.x >= canvas2.width || this.head.y >= canvas2.height || grid[`${this.head.x}-${this.head.y}`] == 1;
}
this.type = type
this.head = new Rect(this.x, this.y, 1, '#f00');
const rect = new Rect(this.head.oldX, this.head.oldY, 1);
food = new Rect(x, y, 2, '#00f');
canvas2.onclick = function () {
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
randomFood();
initGrid();
const snake = new Snake(5, 5);
snake.draw();
snake.move();
document.onkeydown = function (e) {
//左37 上38 右39 下40
if (e.code == 'ArrowUp' || e.code == 'ArrowRight' || e.code == 'ArrowDown' || e.code == 'ArrowLeft') {
if (snake.dir == 'ArrowUp' && e.code == 'ArrowDown'
|| snake.dir == 'ArrowDown' && e.code == 'ArrowUp'
|| snake.dir == 'ArrowRight' && e.code == 'ArrowLeft'
|| snake.dir == 'ArrowLeft' && e.code == 'ArrowRight'
) return;
snake.dir = e.code;
}
}
}
为了使得蛇在移动时不撞到自己的身体,在 Rect 中加入 type 属性。如果属性为1,则结束游戏,属性为2,不影响游戏。将蛇的身体设置为1,食物设置为2。
同时,增加一个点击事件,当点击时清空画布,随机食物和初始化网格。还可以设置分数,每次吃到食物增加分数。
当然还可以再设置一块画布,将食物单独放置到第三块画布中。