Canvas之贪吃蛇

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。

同时,增加一个点击事件,当点击时清空画布,随机食物和初始化网格。还可以设置分数,每次吃到食物增加分数。

当然还可以再设置一块画布,将食物单独放置到第三块画布中。

相关推荐
Andy_GF10 分钟前
纯血鸿蒙HarmonyOS Next 远程测试包分发
前端·ios·harmonyos
嗑药狂写9W行代码24 分钟前
cesium修改源码支持4490坐标系
前端
小山不高27 分钟前
react实现leaferjs编辑器之形状裁剪功能点
前端
202630 分钟前
13.2 ssr基本原理,构建步骤
前端·vue.js
cpp加油站36 分钟前
打脸来的太快了,又发现一个Trae的宝藏功能--内置浏览器可以指定机型来显示前端界面
前端·ai编程·trae
Web极客码1 小时前
如何为WordPress启用LiteSpeed缓存
前端·缓存
咕噜分发企业签名APP加固彭于晏1 小时前
白嫖价值千元的EO
前端·javascript
前端开发爱好者1 小时前
首个「完整级」WebSocket 调试神器来了!
前端·javascript·vue.js
前端Hardy1 小时前
HTML&CSS&JS:高颜值登录注册页面—建议收藏
前端·javascript·css
白雾茫茫丶1 小时前
如何动态执行 JS 脚本
javascript