目录
一、整体框架
二、修改地图为中心对称
如果两条蛇,同时走到相同的格子,会造成平局,这种情况会对优势者不利!
需要把地图变为 偶数 乘 奇数
修改代码:
-
x = rows -
1 - x;
-
-
y = cols -
1 - y;
成功后的界面:
三、 画蛇
如何画蛇 -> 本质上蛇是由一堆格子组成的序列。
蛇里面的一个格子我们定义成Cell
中心的坐标r+0.5, c +0.5
先调用基类的构造函数,
再把蛇的信息取出来
新建Cell.js 存储一堆格子的序列 (蛇的身体):
-
export
class
Cell {
-
constructor(
r, c) {
-
this.
r = r;
-
this.
c = c;
-
// 转换为 canvas 的坐标
-
this.
x = c +
0.5;
-
this.
y = r +
0.5;
-
}
-
}
新建Snake.js 对象,方便我们进行操作~
-
import {
AcGameObject }
from
"./AcGameObject";
-
import {
Cell }
from
"./Cell";
-
-
-
export
class
Snake
extends
AcGameObject {
-
constructor(
info, gamemap) {
-
super();
-
-
// 取出基本的id
-
this.
id = info.
id;
-
this.
color = info.
color;
-
this.
gamemap = gamemap;
// 方便调用函数和参数
-
-
//存放蛇的身体;
-
this.
cells = [
new
Cell(info.
r, info.
c)];
-
-
}
-
-
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();
-
}
-
}
-
}
在GameMap.js 中创建两条蛇的对象:
-
this.
Snakes = {
-
new
Snake({id :
0, color :
"#4876ec", r :
this.
rows -
2, c :
1},
this),
-
new
Snake({id :
1, color :
"#f94848", r :
1, c :
this.
cols -
2},
this),
-
}
成功之后的界面:
四、实现蛇的移动
移动应该是连贯的。
身体有多个部分,如何让保持连贯?
中间保持不动,头和尾动,在头部创建一个新的节点,朝着目的地移动。尾巴朝着目的地动
蛇只有在获取到 两个人 或 机器的指令后才能动~~
在 Snake.js 中添加代码,实现蛇头的移动:
-
import {
AcGameObject }
from
"./AcGameObject";
-
import {
Cell }
from
"./Cell";
-
-
export
class
Snake
extends
AcGameObject {
-
constructor(
info, gamemap) {
-
super();
// 继承AcGameObject的方法
-
-
this.
id = info.
id;
-
this.
color = info.
color;
-
this.
gamemap = gamemap;
-
-
this.
cells = [
new
Cell(info.
r, info.
c)];
// 存放蛇的身体, cell[0] 存放蛇头
-
// new add
-
this.
speed =
5;
-
}
-
-
-
update_move(
) {
-
// 向右移动
-
this.
cells[
0].
x +=
this.
speed *
this.
timedelta /
1000;
-
//向上移动
-
//this.cells[0].y -= this.speed * this.timedelta / 1000;
-
}
-
-
update(
) {
-
this.
update_move();
-
this.
render();
-
}
蓝色的球会一直向右移动:
如何实现连贯的移动呢?
1、 由于可能会产生一些问题, 也就是中间某个状态,没有完全移出去,蛇的身子会出现问题。
2、中间不动,首尾动!创建的虚拟节点朝着目的地移动。只有两个点动。
3、考虑蛇什么时候动? 回合制游戏,两个人都有输入的时候,才可以移动。
修改 Snake.js:
-
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)];
-
-
this.
speed =
5;
// 蛇每秒走5格
-
// new add
-
this.
direction = -
1;
// -1表示没有指令 0 1 2 3
-
this.
status =
"idle";
// 静止, move 移动 die 死亡
-
}
-
}
首先我们需要一个裁判来判断两条蛇的移动,我们抽取放在 GameMap.js 中
-
check_ready(
) {
// 判断两条蛇是否准备下一回合了
-
for (
const snake
of
this.
snakes) {
-
if (snake.
status !==
"idle")
return
false;
-
if (snake.
direction === -
1)
return
false;
-
}
-
return
true;
-
}
在 Snake.js 中更新蛇的状态:
-
this.
next_cell =
null;
//下一步的目标位置
-
-
this.
dr = [-
1,
0,
1,
0];
// 行
-
this.
dc = [
0,
1,
0, -
1];
//列
-
-
this.
step =
0;
-
-
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 ++ ;
-
}
在 GameMap.js 中更新:
-
next_step(
) {
-
for (
const snake
of
this.
snake) {
-
snake.
next_step();
-
}
-
}
-
-
update(
) {
-
this.
update_size();
-
if (
this.
check_ready()) {
-
this.
next_step();
-
}
-
this.
render();
-
}
五、读取键盘的操作
从键盘获取操作 w a s d 和 ↑ ↓ ← → 来控制两条蛇。
在 GameMap.vue 中修改:
<canvas ref="canvas" tabindex="0"></canvas>
这样我们的游戏才拥有了聚焦功能(这里Edge会有一个Bug就是上下左右一次后它会聚焦导航栏,但不用担心,因为我们后期实施多人对战,会让每个用户用 w a s d 来操作~)
绑定事件:
在 Snake.js 中加入辅助函数,用来获取方向
-
//辅助函数
-
set_direction(
d) {
-
this.
direction = d;
-
}
在 GameMap.js 中修改,添加事件:
-
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);
-
});
-
}
在 Snake.js 中更新状态:
-
update(
) {
// 每一帧执行一次
-
if (
this.
status ===
'move') {
-
this.
uppdate_move()
-
}
-
-
this.
render();
-
}
此时我们就可以真正让蛇动起来了~
在 Snake.js 中修改:
-
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)];
// 存放蛇的身体,cells[0]存放蛇头
-
this.
next_cell =
null;
// 下一步的目标位置
-
-
this.
speed =
5;
// 蛇每秒走5个格子
-
this.
direction = -
1;
// -1表示没有指令,0、1、2、3表示上右下左
-
this.
status =
"idle";
// idle表示静止,move表示正在移动,die表示死亡
-
-
this.
dr = [-
1,
0,
1,
0];
// 4个方向行的偏移量
-
this.
dc = [
0,
1,
0, -
1];
// 4个方向列的偏移量
-
-
this.
step =
0;
// 表示回合数
-
this.
eps =
1e-2;
// 允许的误差
-
-
}
-
-
-
start(
) {
-
-
}
-
-
set_direction(
d) {
-
this.
direction = d;
-
}
-
-
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";
// 走完了,停下来
-
-
}
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;
-
}
-
}
-
-
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,
Math.
PI *
2);
-
ctx.
fill();
-
}
-
}
-
}
实现蛇尾的移动 :
在 Snake.js 中添加代码,判断蛇尾是否增长
-
check_tail_increasing(
) {
-
if (step <=
10)
return
true;
-
if (step %
3 ===
1)
return
true;
-
return
false;
-
}
修改 Snake.js , 判断蛇尾是否在下一步是否增长
-
this.
next_cell =
null;
//下一步的目标位置
-
-
this.
dr = [-
1,
0,
1,
0];
// 行
-
this.
dc = [
0,
1,
0, -
1];
//列
-
-
this.
step =
0;
-
-
this.
eps =
1e-2
// 允许的误差
-
-
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;
-
}
-
}
-
}
效果图:
六、美化蛇和检测非法逻辑
修改 Snake.js ,让蛇变得连贯、缩小一点。添加下列代码:
-
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;
-
if (
Math.
abs(a.
x - b.
x) <
this.
eps) {
-
ctx.
fillRect((a.
x -
0.4) * 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.4) * L,
Math.
abs(a.
x - b.
x) * L, L *
0.8);
-
}
-
}
-
}
效果图:
检测非法逻辑:
1)此时目标位置是否合法(墙体、蛇身、障碍物)
2)蛇尾前进时,蛇尾要不要判断
在 GameMap.js 中更新:
-
check_valid(
cell) {
// 检测目标位置是否合法:没有撞到两条蛇的身体和障碍物
-
for (
const wall
of
this.
walls) {
-
if (wall.
r === cell.
r && wall.
c === cell.
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 (snake.
cells[i].
r === cell.
r && snake.
cells[i].
c === cell.
c)
-
return
false;
-
}
-
}
-
-
return
true;
-
}
-
在 Snake.js 中更新:
-
next_step(
) {
-
if (!
this.
gamemap.
check_valid(
this.
next_cell)) {
-
this.
status =
"die";
-
}
-
}
-
-
-
render(
) {
-
if (
this.
status ===
"die") {
-
ctx.
fillStyle =
"white";
-
}
-
}
效果图:
画蛇点睛!!!!
修改 Snake.js
-
this.
eye_direction =
0;
-
if (
this.
id ===
1)
this.
eye_direction =
2;
-
-
this.
eye_dx = [
-
[-
1,
1];
-
[
1,
1];
-
[
1, -
1];
-
[-
1, -
1];
-
];
-
this.
eye_dy = [
-
[-
1, -
1];
-
[-
1,
1];
-
[
1,
1];
-
[
1, -
1];
-
];
-
-
-
next_step(
) {
-
this.
eye_direction = d;
-
-
}
-
-
render(
) {
-
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();
-
}
-
}
效果图:
最后记得用 git 维护哦~~
转载:https://blog.csdn.net/qq_59539549/article/details/127836997