本节内容为实现两名玩家即两条蛇的绘制与人工操作移动功能。
1. 地图优化改进
之前我们设计的地图尺寸是13×13,两名玩家的起点横纵坐标之和均为偶数,因此可能在同一时刻走到同一个格子上,为了避免这种情况,可以将地图改为13×14的大小(即将 GameMap.js
中的 this.cols
改为14),这样两名玩家就不会在同一时刻走到同一个格子上。这样修改完之后就不能用轴对称了,需要改为中心对称:
1 | // 添加地图内部的随机障碍物,需要有对称性因此枚举一半即可,另一半对称生成 |
2. 绘制玩家的起始位置
刚开始玩家占一个格子,我们可以规定一下前七步的每一步将蛇的长度加一,之后改为每三步长度加一。每条蛇其实就是一堆格子的序列,我们可以将一个格子定义成一个 Cell
对象,在 scripts
目录下创建 Cell.js
记录格子的坐标。
我们在每个格子中绘制的是一个圆,若格子的左上角坐标为 (x, y)
则圆的圆心坐标应该是 (x + 0.5, y + 0.5)
,Cell.js
如下:
1 | export class Cell { |
此外每条蛇也可以定义成一个对象 Snake.js
:
1 | import { AcGameObject } from "./AcGameObject"; |
然后我们在 GameMap.js
中创建两条蛇:
1 | import { AcGameObject } from "./AcGameObject"; |
3. 实现玩家移动
为了实现蛇移动的连续性,我们不对每个格子进行更新,只更新头部和尾部,头部创建一个新的点往前动,尾部直接往前动。首先在 Snake
对象中设置一些移动的属性:
1 | import { AcGameObject } from "./AcGameObject"; |
由于游戏是回合制的,因此移动的判定条件应该是获取到了两名玩家的指示后才能移动一次,且该指令既可以由键盘输入也可以由 AI 代码输入,判定两条蛇是否准备好执行下一步不能各自判断,需要由上层也就是 GameMap
判定,判定条件是两条蛇都处于静止状态且都已经获取到了下一步指令:
1 | import { AcGameObject } from "./AcGameObject"; |
现在我们只能从前端获得用户的操作,即获取用户的键盘输入。为了能够让 Canvas 获取键盘输入,需要添加一个 tabindex
属性,在 GameMap.vue
中进行修改:
1 | <template> |
这样我们就能够在 GameMap.js
中绑定键盘的监听事件:
1 | import { AcGameObject } from "./AcGameObject"; |
现在我们即可在 Snake.js
中实现蛇的移动:
1 | import { AcGameObject } from "./AcGameObject"; |
接着我们还需要实现蛇尾的移动,如果蛇的长度增加了一个单位,那么尾部不用动即可,否则尾部需要向前一个节点移动,且当移动完成后需要将尾部节点对象删去:
1 | import { AcGameObject } from "./AcGameObject"; |
4. 优化蛇的身体效果
现在我们蛇的身体还是分开的若干个圆球,没有连续感。我们可以在两个相邻的圆球中间绘制一个矩形覆盖一遍即可。然后我们这边再做个小优化,将蛇的半径缩小一点,不然贴在一起时就会融合在一起不好看:
1 | import { AcGameObject } from "./AcGameObject"; |
5. 碰撞检测实现
我们只要在每一轮判断一下玩家下一步移动的目标格子是不是合法的即可,如果不是两条蛇的身体或障碍物说明是合法的,同理需要在 GameMap
中判断而不能由玩家自己判断,这边需要注意如果在追蛇尾的话需要判断蛇尾是否有移动,如果没有移动则不合法:
1 | import { AcGameObject } from "./AcGameObject"; |
然后在 Snake
中每次进入下一步时进行合法性判断,如果不合法则将状态更新为 die
,且颜色变白:
1 | import { AcGameObject } from "./AcGameObject"; |
6. 绘制蛇的眼睛
绘制眼睛时需要考虑蛇头的朝向,即上一步移动的方向,对于不同方向的眼睛偏移量打个表记录即可,完善后 Snake.js
的完整代码如下:
1 | import { AcGameObject } from "./AcGameObject"; |
我们可以将 next_step()
中的 this.direction = -1;
还有 update_move()
中的 this.status = "idle";
注释掉,并在 update_move()
中的 if (distance < this.eps)
判断中添加一行 this.next_step();
,这样就可以从回合制改为连续移动:
1 | if (distance < this.eps) { // 已经走到目标点 |