飞道的博客

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

280人阅读  评论(0)

目录

一、整体框架

二、修改地图为中心对称

三、 画蛇

四、实现蛇的移动

 五、读取键盘的操作

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


一、整体框架

二、修改地图为中心对称

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

修改代码:


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

 

 

 成功后的界面:

 

三、 画蛇

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

蛇里面的一个格子我们定义成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. }

 

 新建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. }

 

 在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. }

 

成功之后的界面:

 

四、实现蛇的移动

移动应该是连贯的。

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

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

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

在 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. }

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

如何实现连贯的移动呢?

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. }

 

 五、读取键盘的操作

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

在 GameMap.vue 中修改:

<canvas ref="canvas" tabindex="0"></canvas>

 这样我们的游戏才拥有了聚焦功能(这里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 维护哦~~

 


转载:https://blog.csdn.net/qq_59539549/article/details/127836997
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场