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。

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

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

相关推荐
MediaTea2 小时前
Python 第三方库:lxml(高性能 XML/HTML 解析与处理)
xml·开发语言·前端·python·html
西陵2 小时前
Nx带来极致的前端开发体验——使用MF进行增量构建
前端·javascript·架构
Nicholas682 小时前
flutter滚动视图之ProxyWidget、ProxyElement、NotifiableElementMixin源码解析(九)
前端
JackieDYH3 小时前
vue3中reactive和ref如何使用和区别
前端·javascript·vue.js
伍哥的传说3 小时前
解密 Vue 3 shallowRef:浅层响应式 vs 深度响应式的性能对决
javascript·vue.js·ecmascript·vue3.js·大数据处理·响应式系统·shallowref
ZZHow10244 小时前
React前端开发_Day4
前端·笔记·react.js·前端框架·web
前端开发爱好者4 小时前
弃用 html2canvas!快 93 倍的截图神器
前端·javascript·vue.js
ss2734 小时前
手写MyBatis第39弹:深入MyBatis BatchExecutor实现原理与最佳实践
前端·javascript·html
leon_teacher4 小时前
HarmonyOS权限管理应用
android·服务器·前端·javascript·华为·harmonyos
lumi.5 小时前
HarmonyOS image组件深度解析:多场景应用与性能优化指南(2.4详细解析,完整见uniapp官网)
前端·javascript·小程序·uni-app·html·css3