• 项目实战——创建菜单与游戏页面(下)


    目录

    一、整体框架

    二、修改地图为中心对称

    三、 画蛇

    四、实现蛇的移动

     五、读取键盘的操作

     六、美化蛇和检测非法逻辑


    一、整体框架

    逻辑框架图

    二、修改地图为中心对称

    如果两条蛇,同时走到相同的格子,会造成平局,这种情况会对优势者不利!
    需要把地图变为 偶数 乘 奇数

    修改代码:

    1. x = rows - 1 - x;
    2. y = cols - 1 - y;

     修改地图1

     

     成功后的界面:

    地图修改sucess

     

    三、 画蛇

    如何画蛇 -> 本质上蛇是由一堆格子组成的序列。

    蛇里面的一个格子我们定义成Cell
    中心的坐标r+0.5, c +0.5
    先调用基类的构造函数,
    再把蛇的信息取出来

    新建Cell.js 存储一堆格子的序列 (蛇的身体):

    1. export class Cell {
    2. constructor(r, c) {
    3. this.r = r;
    4. this.c = c;
    5. // 转换为 canvas 的坐标
    6. this.x = c + 0.5;
    7. this.y = r + 0.5;
    8. }
    9. }

     蛇头1

     新建Snake.js 对象,方便我们进行操作~

    1. import { AcGameObject } from "./AcGameObject";
    2. import { Cell } from "./Cell";
    3. export class Snake extends AcGameObject {
    4. constructor(info, gamemap) {
    5. super();
    6. // 取出基本的id
    7. this.id = info.id;
    8. this.color = info.color;
    9. this.gamemap = gamemap; // 方便调用函数和参数
    10. //存放蛇的身体;
    11. this.cells = [new Cell(info.r, info.c)];
    12. }
    13. start() {
    14. }
    15. update() {
    16. this.render();
    17. }
    18. render() {
    19. // 画出基本的蛇头
    20. const L = this.gamemap.L;
    21. const ctx = this.gamemap.ctx;
    22. ctx.fillStyle = this.color;
    23. for (const cell of this.cells) {
    24. ctx.beginPath();
    25. ctx.arc(cell.x * L, cell.y * L, L / 2, 0, Math.PI * 2);
    26. ctx.fill();
    27. }
    28. }
    29. }

     蛇头2

     在GameMap.js 中创建两条蛇的对象:

    1. this.Snakes = {
    2. new Snake({id : 0, color : "#4876ec", r : this.rows - 2, c : 1}, this),
    3. new Snake({id : 1, color : "#f94848", r : 1, c : this.cols - 2}, this),
    4. }

     蛇头3

    成功之后的界面:

     蛇头sucess

    四、实现蛇的移动

    移动应该是连贯的。

    身体有多个部分,如何让保持连贯?

    中间保持不动,头和尾动,在头部创建一个新的节点,朝着目的地移动。尾巴朝着目的地动

    蛇只有在获取到 两个人 或 机器的指令后才能动~~

    在 Snake.js 中添加代码,实现蛇头的移动:

    1. import { AcGameObject } from "./AcGameObject";
    2. import { Cell } from "./Cell";
    3. export class Snake extends AcGameObject {
    4. constructor(info, gamemap) {
    5. super(); // 继承AcGameObject的方法
    6. this.id = info.id;
    7. this.color = info.color;
    8. this.gamemap = gamemap;
    9. this.cells = [new Cell(info.r, info.c)]; // 存放蛇的身体, cell[0] 存放蛇头
    10. // new add
    11. this.speed = 5;
    12. }
    13. update_move() {
    14. // 向右移动
    15. this.cells[0].x += this.speed * this.timedelta / 1000;
    16. //向上移动
    17. //this.cells[0].y -= this.speed * this.timedelta / 1000;
    18. }
    19. update() {
    20. this.update_move();
    21. this.render();
    22. }

    在这里插入图片描述

     蓝色的球会一直向右移动:

    向右移动sucess

    如何实现连贯的移动呢?

    1、 由于可能会产生一些问题, 也就是中间某个状态,没有完全移出去,蛇的身子会出现问题。
    2、中间不动,首尾动!创建的虚拟节点朝着目的地移动。只有两个点动。
    3、考虑蛇什么时候动? 回合制游戏,两个人都有输入的时候,才可以移动。

    修改 Snake.js:

    1. import { AcGameObject } from "./AcGameObject";
    2. import { Cell } from "./Cell";
    3. export class Snake extends AcGameObject {
    4. constructor(info, gamemap) {
    5. super();
    6. this.id = info.id;
    7. this.color = info.color;
    8. this.gamemap = gamemap;
    9. //存放蛇的身体;
    10. this.cells = [new Cell(info.r, info.c)];
    11. this.speed = 5; // 蛇每秒走5格
    12. // new add
    13. this.direction = -1; // -1表示没有指令 0 1 2 3
    14. this.status = "idle"; // 静止, move 移动 die 死亡
    15. }
    16. }

    基本移动 

    首先我们需要一个裁判来判断两条蛇的移动,我们抽取放在 GameMap.js 中

    1. check_ready() { // 判断两条蛇是否准备下一回合了
    2. for (const snake of this.snakes) {
    3. if (snake.status !== "idle") return false;
    4. if (snake.direction === -1) return false;
    5. }
    6. return true;
    7. }

     裁判逻辑

     在 Snake.js 中更新蛇的状态:

    1. this.next_cell = null; //下一步的目标位置
    2. this.dr = [-1, 0, 1, 0]; // 行
    3. this.dc = [0, 1, 0, -1]; //列
    4. this.step = 0;
    5. next_step() {
    6. const d = this.direction;
    7. this.next_cell = new Cell(this.cells[0].r + this.dr[d], this.cells[0].c + this.dc[d]);
    8. this.direction = -1;
    9. this.status = "move";
    10. this.step ++ ;
    11. }

     在这里插入图片描述

     在 GameMap.js 中更新:

    1. next_step() {
    2. for (const snake of this.snake) {
    3. snake.next_step();
    4. }
    5. }
    6. update() {
    7. this.update_size();
    8. if (this.check_ready()) {
    9. this.next_step();
    10. }
    11. this.render();
    12. }

     基本移动4

     五、读取键盘的操作

    从键盘获取操作 w a s d 和 ↑ ↓ ← → 来控制两条蛇。

    在 GameMap.vue 中修改:

    "canvas" tabindex="0">

     这样我们的游戏才拥有了聚焦功能(这里Edge会有一个Bug就是上下左右一次后它会聚焦导航栏,但不用担心,因为我们后期实施多人对战,会让每个用户用 w a s d 来操作~)

    在这里插入图片描述

    绑定事件:

    在 Snake.js 中加入辅助函数,用来获取方向

    1. //辅助函数
    2. set_direction(d) {
    3. this.direction = d;
    4. }

     在这里插入图片描述

    在 GameMap.js 中修改,添加事件:

     

    1. add_listening_events() {
    2. this.ctx.canvas.focus();
    3. const [snake0, snake1] = this.snakes;
    4. this.ctx.canvas.addEventListener("keydown", e => {
    5. if (e.key === 'w') snake0.set_direction(0);
    6. else if (e.key === 'd') snake0.set_direction(1);
    7. else if (e.key === 's') snake0.set_direction(2);
    8. else if (e.key === 'a') snake0.set_direction(3);
    9. else if (e.key === 'ArrowUp') snake1.set_direction(0);
    10. else if (e.key === 'ArrowRight') snake1.set_direction(1);
    11. else if (e.key === 'ArrowDown') snake1.set_direction(2);
    12. else if (e.key === 'ArrowLeft') snake1.set_direction(3);
    13. });
    14. }

     在这里插入图片描述

    在 Snake.js 中更新状态:

    1. update() { // 每一帧执行一次
    2. if (this.status === 'move') {
    3. this.uppdate_move()
    4. }
    5. this.render();
    6. }

     在这里插入图片描述

     此时我们就可以真正让蛇动起来了~

    在 Snake.js 中修改:

    1. import { AcGameObject } from "./AcGameObject";
    2. import { Cell } from "./Cell";
    3. export class Snake extends AcGameObject {
    4. constructor(info, gamemap) {
    5. super();
    6. this.id = info.id;
    7. this.color = info.color;
    8. this.gamemap = gamemap;
    9. this.cells = [new Cell(info.r, info.c)]; // 存放蛇的身体,cells[0]存放蛇头
    10. this.next_cell = null; // 下一步的目标位置
    11. this.speed = 5; // 蛇每秒走5个格子
    12. this.direction = -1; // -1表示没有指令,0、1、2、3表示上右下左
    13. this.status = "idle"; // idle表示静止,move表示正在移动,die表示死亡
    14. this.dr = [-1, 0, 1, 0]; // 4个方向行的偏移量
    15. this.dc = [0, 1, 0, -1]; // 4个方向列的偏移量
    16. this.step = 0; // 表示回合数
    17. this.eps = 1e-2; // 允许的误差
    18. }
    19. start() {
    20. }
    21. set_direction(d) {
    22. this.direction = d;
    23. }
    24. next_step() { //蛇的状态变为走下一步
    25. const d = this.direction;
    26. this.next_cell = new Cell(this.cells[0].r + this.dr[d], this.cells[0].c + this.dc[d]);
    27. this.direction = -1;
    28. this.status = "move";
    29. this.step ++ ;
    30. // 求长度
    31. const k = this.cells.length;
    32. for (let i = k; i > 0; i -- ) { // 初始元素不变 每一个元素往后移动一位
    33. this.cells[i] = JSON.parse(JSON.stringify(this.cells[i - 1]));
    34. }
    35. }
    36. update_move() {
    37. const dx = this.next_cell.x - this.cells[0].x;
    38. const dy = this.next_cell.y - this.cells[0].y;
    39. const distance = Math.sqrt(dx * dx + dy * dy);
    40. if (distance < this.eps) { // 走到目标点了
    41. this.cells[0] = this.next_cell; // 添加一个新蛇头
    42. this.next_cell = null;
    43. this.status = "idle"; // 走完了,停下来
    44. } else {
    45. const move_distance = this.speed * this.timedelta / 1000;
    46. this.cells[0].x += move_distance * dx / distance;
    47. this.cells[0].y += move_distance * dy / distance;
    48. }
    49. }
    50. update() { // 每一帧执行一次
    51. if (this.status === 'move') {
    52. this.update_move();
    53. }
    54. this.render();
    55. }
    56. render() {
    57. const L = this.gamemap.L;
    58. const ctx = this.gamemap.ctx;
    59. ctx.fillStyle = this.color;
    60. for (const cell of this.cells) {
    61. ctx.beginPath();
    62. ctx.arc(cell.x * L, cell.y * L, L / 2, 0, Math.PI * 2);
    63. ctx.fill();
    64. }
    65. }
    66. }

    实现蛇尾的移动 :

    在 Snake.js 中添加代码,判断蛇尾是否增长

    1. check_tail_increasing() {
    2. if (step <= 10) return true;
    3. if (step % 3 === 1) return true;
    4. return false;
    5. }

     在这里插入图片描述

     修改 Snake.js , 判断蛇尾是否在下一步是否增长

    1. this.next_cell = null; //下一步的目标位置
    2. this.dr = [-1, 0, 1, 0]; // 行
    3. this.dc = [0, 1, 0, -1]; //列
    4. this.step = 0;
    5. this.eps = 1e-2 // 允许的误差
    6. next_step() {
    7. const d = this.direction;
    8. this.next_cell = new Cell(this.cells[0].r + this.dr[d], this.cells[0].c + this.dc[d]);
    9. this.direction = -1;
    10. this.status = "move";
    11. this.step ++ ;
    12. // 求长度
    13. const k = this.cells.length;
    14. for (let i = k; i > 0; i -- ) { // 初始元素不变 每一个元素往后移动一位
    15. this.cells[i] = JSON.parse(JSON.stringify(this.cells[i - 1]));
    16. }
    17. }
    18. update_move() {
    19. const dx = this.next_cell.x - this.cells[0].x;
    20. const dy = this.next_cell.y - this.cells[0].y;
    21. const distance = Math.sqrt(dx * dx + dy * dy);
    22. if (distance < this.eps) { // 走到目标点了
    23. this.cells[0] = this.next_cell; // 添加一个新蛇头
    24. this.next_cell = null;
    25. this.status = "idle"; // 走完了,停下来
    26. if (!this.check_tail_increasing()) { // 蛇不变长。
    27. this.cells.pop();
    28. }
    29. } else {
    30. const move_distance = this.speed * this.timedelta / 1000;
    31. this.cells[0].x += move_distance * dx / distance;
    32. this.cells[0].y += move_distance * dy / distance;
    33. if (!this.check_tail_increasing()) {
    34. const k = this.cells.length;
    35. const tail = this.cells[k - 1], tail_target = this.cells[k - 2];
    36. const tail_dx = tail_target.x - tail.x;
    37. const tail_dy = tail_target.y - tail.y;
    38. tail.x += move_distance * tail_dx / distance;
    39. tail.y += move_distance * tail_dy / distance;
    40. }
    41. }
    42. }

     在这里插入图片描述

    效果图:

     在这里插入图片描述

     六、美化蛇和检测非法逻辑

    修改 Snake.js ,让蛇变得连贯、缩小一点。添加下列代码:

    1. render() {
    2. const L = this.gamemap.L;
    3. const ctx = this.gamemap.ctx;
    4. ctx.fillStyle = this.color;
    5. for (const cell of this.cells) {
    6. ctx.beginPath();
    7. ctx.arc(cell.x * L, cell.y * L, L / 2 * 0.8, 0, Math.PI * 2);
    8. ctx.fill();
    9. }
    10. for (let i = 1; i < this.cells.length; i ++ ) {
    11. const a = this.cells[i - 1], b = this.cells[i];
    12. if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y - b.y) < this.eps)
    13. continue;
    14. if (Math.abs(a.x - b.x) < this.eps) {
    15. ctx.fillRect((a.x - 0.4) * L, Math.min(a.y, b.y) * L, L * 0.8, Math.abs(a.y - b.y) * L);
    16. } else {
    17. ctx.fillRect(Math.min(a.x, b.x) * L, (a.y - 0.4) * L, Math.abs(a.x - b.x) * L, L * 0.8);
    18. }
    19. }
    20. }

    效果图:

     在这里插入图片描述

     检测非法逻辑:

    1)此时目标位置是否合法(墙体、蛇身、障碍物)

    2)蛇尾前进时,蛇尾要不要判断

    在 GameMap.js 中更新:

    1. check_valid(cell) { // 检测目标位置是否合法:没有撞到两条蛇的身体和障碍物
    2. for (const wall of this.walls) {
    3. if (wall.r === cell.r && wall.c === cell.c)
    4. return false;
    5. }
    6. for (const snake of this.snakes) {
    7. let k = snake.cells.length;
    8. if (!snake.check_tail_increasing()) { // 当蛇尾会前进的时候,蛇尾不要判断
    9. k -- ;
    10. }
    11. for (let i = 0; i < k; i ++ ) {
    12. if (snake.cells[i].r === cell.r && snake.cells[i].c === cell.c)
    13. return false;
    14. }
    15. }
    16. return true;
    17. }

    在这里插入图片描述

     在 Snake.js 中更新:

    1. next_step() {
    2. if (!this.gamemap.check_valid(this.next_cell)) {
    3. this.status = "die";
    4. }
    5. }
    6. render() {
    7. if (this.status === "die") {
    8. ctx.fillStyle = "white";
    9. }
    10. }

     在这里插入图片描述

     在这里插入图片描述

     效果图:

    在这里插入图片描述

    画蛇点睛!!!!

    修改 Snake.js

    1. this.eye_direction = 0;
    2. if (this.id === 1) this.eye_direction = 2;
    3. this.eye_dx = [
    4. [-1, 1];
    5. [1, 1];
    6. [1, -1];
    7. [-1, -1];
    8. ];
    9. this.eye_dy = [
    10. [-1, -1];
    11. [-1, 1];
    12. [1, 1];
    13. [1, -1];
    14. ];
    15. next_step() {
    16. this.eye_direction = d;
    17. }
    18. render() {
    19. ctx.fillStyle = "black";
    20. for (let i = 0; i < 2; i ++ ) {
    21. const eye_x = (this.cells[0].x + this.eye_dx[this.eye_direction][i] * 0.15) * L;
    22. const eye_y = (this.cells[0].y + this.eye_dy[this.eye_direction][i] * 0.15) * L;
    23. ctx.beginPath();
    24. ctx.arc(eye_x, eye_y, L * 0.05, 0, Math.PI * 2);
    25. ctx.fill();
    26. }
    27. }

     在这里插入图片描述

     在这里插入图片描述

     效果图:

    在这里插入图片描述

     最后记得用 git 维护哦~~

     

  • 相关阅读:
    hdfs读流程
    建一座设计模式大厦
    ElementUl-布局+分页+表格+导航栏
    List类使用
    程序设计原则
    STM32单片机LCD1602智能药盒定时药盒开盒检测拿起检测
    uvm中transaction的response和id的解读
    【endnote】如何将参考文献放到想放的位置
    JavaWeb AJAX请求
    计算器的简化版
  • 原文地址:https://blog.csdn.net/qq_59539549/article/details/127836997