1.创建蛇
先写Cell.js,用来表示蛇的每一格。
export class Cell {
constructor(r,c) {
this.r = r;
this.c = c;
//圆心的坐标 canvas的坐标和常规的不一样
this.x = this.c + 0.5;
this.y = this.r + 0.5;
}
}
Snake.js的信息,render函数渲染出蛇的每一个格子
import { AcGameObject } from "./AcGameObject";
import { Cell } from "./Cell";
export class Snake extends AcGameObject {
constructor(info,GameMap) { //传递蛇的信息和地图信息
super();
this.id = info.id;
this.color = info.color;
this.gamemap = GameMap;
this.cells = [new Cell(info.r,info.c)]; //存放蛇的身体 cell[0]存放蛇头
}
start() {
}
update() {
this.render();
}
render() {
const L = this.gamemap.L;
const ctx = this.gamemap.ctx;
//画蛇的每一个格子 即画圆
ctx.fillStyle = this.color;
for(const cell of this.cells) {
//画圆
ctx.beginPath();
ctx.arc(cell.x * L,cell.y * L,L / 2,0,Math.PI * 2);
ctx.fill();
}
}
}
蛇如何去动?
创建一个新头往前面移动,尾巴向前移动,其余的点都不动。
接受键盘的操作?
先让canvs聚焦,加上tabindex属性
<template>
<div ref="parent" class="gamemap">
<canvas ref="canvas" tabindex="0"></canvas>
</div>
</template>
add_listening_events() {
this.ctx.canvas.focus();
const [snake0,snake1] = this.snakes;
this.ctx.canvas.addEventListener("keydown",e => {
if(e.key === 'w') snake0.set_direction(0);
else if(e.key === 'd') snake0.set_direction(1);
else if(e.key === 's') snake0.set_direction(2);
else if(e.key === 'a') snake0.set_direction(3);
else if(e.key === 'ArrowUp') snake1.set_direction(0);
else if(e.key === 'ArrowRight') snake1.set_direction(1);
else if(e.key === 'ArrowDown') snake1.set_direction(2);
else if(e.key === 'ArrowLeft') snake1.set_direction(3);
});
}
start() {
for(let i = 0;i < 1000;i ++) {
if(this.create_walls())
break;
}
this.add_listening_events();
}
蛇边长的规则:前十步每一步蛇都变长,后面每三步蛇变长一格。
check_tail_increasing() {
if(this.step <= 10) return true;
if(this.step % 3 === 1) return true;
return false;
}
next_step() { //将蛇的状态改为走下一步
const d = this.direction;
this.next_cell = new Cell(this.cells[0].r + this.dr[d],this.cells[0].c + this.dc[d]);
this.direction = -1; //清空操作
this.status = "move"; //可以移动了
this.step ++;
const k = this.cells.length;
for(let i = k;i > 0;i --) { //除了头节点的其余所有点都往后移动,并复制一份头节点
this.cells[i] = JSON.parse(JSON.stringify(this.cells[i - 1]));
}
}
update_move() { //蛇的移动
const dx = this.next_cell.x - this.cells[0].x;
const dy = this.next_cell.y - this.cells[0].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if(distance < this.eps) { //移动到目标值了
this.cells[0] = this.next_cell;
this.next_cell = null;
this.status = "idle"; //走完了 停止
if(!this.check_tail_increasing()) {
this.cells.pop();
}
} else {
const move_distance = this.speed * this.timedelta / 1000;
this.cells[0].x += move_distance * dx / distance;
this.cells[0].y += move_distance * dy / distance;
if(!this.check_tail_increasing()) { //蛇尾不增加长度,就让倒数第一个点移动到倒数第二个点上去。蛇尾长度增加的话,蛇尾就不动
const k = this.cells.length;
const tail = this.cells[k - 1],tail_target = this.cells[k - 2];
const tail_dx = tail_target.x - tail.x;
const tail_dy = tail_target.y - tail.y;
tail.x += move_distance * tail_dx / distance;
tail.y += move_distance * tail_dy / distance;
}
}
}
update() {
if(this.status === 'move') {
this.update_move();
}
this.render();
}
优化蛇的形状
render() {
const L = this.gamemap.L;
const ctx = this.gamemap.ctx;
//画蛇的每一个格子 即画圆
ctx.fillStyle = this.color;
for(const cell of this.cells) {
//画圆
ctx.beginPath();
ctx.arc(cell.x * L,cell.y * L,L / 2 * 0.8,0,Math.PI * 2);
ctx.fill();
}
for(let i = 1;i < this.cells.length;i ++) {
const a = this.cells[i - 1],b = this.cells[i];
if(Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps)
continue;
else if(Math.abs(a.x - b.x) < this.eps) { //竖方向
ctx.fillRect((a.x - 0.5 + 0.1) * L , Math.min(a.y,b.y) * L , L * 0.8 , Math.abs(a.y - b.y) * L);
} else { //横方向
ctx.fillRect(Math.min(a.x,b.x) * L , (a.y - 0.5 + 0.1) * L , Math.abs(a.x - b.x) * L , L * 0.8);
}
}
}
2.蛇碰撞的检测
就是检测下一个格子是否合法?
墙的检测:
check_vaild(cell) { //检测下一步是否合法
for(const wall of this.walls) { //撞墙与否
if(cell.r === wall.r && cell.c === wall.c)
return false;
}
//是否撞到蛇的身体
for(const snake of this.snakes) {
let k = snake.cells.length; //蛇的长度
if(!snake.check_tail_increasing()) { //蛇的长度不再增加,蛇尾移动,蛇尾不要在判断
k --;
}
for(let i = 0;i < k;i ++) {
if(cell.r === snake.cells[i].r && cell.c === snake.cells[i].c)
return false;
}
}
return true;
}
3.画眼睛
ctx.fillStyle = "black";
for(let i = 0;i < 2;i ++) {
const eye_x = (this.cells[0].x + this.eye_dx[this.eye_direction][i] * 0.15) * L;
const eye_y = (this.cells[0].y + this.eye_dy[this.eye_direction][i] * 0.15) * L;
ctx.beginPath();
ctx.arc(eye_x,eye_y,L * 0.05,0,Math.PI * 2);
ctx.fill();
}