Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙

栏目: 后端 · 发布时间: 5年前

内容简介:文章首发于我的上一篇文章:《定义小恐龙类:

文章首发于我的 个人博客

前言

上一篇文章:《 Chrome 小恐龙游戏源码探究七 -- 昼夜模式交替 》实现了游戏昼夜模式的交替,这一篇文章中,将实现:1、小恐龙的绘制 2、键盘对小恐龙的控制 3、页面失焦后,重新聚焦会重置小恐龙的状态

绘制静态的小恐龙

定义小恐龙类:

/**
 * 小恐龙类
 * @param {HTMLCanvasElement} canvas 画布
 * @param {Object} spritePos 图片在雪碧图中的坐标
 */
function Trex(canvas, spritePos) {
  this.canvas = canvas;
  this.ctx = canvas.getContext('2d');
  this.spritePos = spritePos;

  this.xPos = 0;
  this.yPos = 0;
  this.groundYPos = 0;               // 小恐龙在地面上时的 y 坐标

  this.currentFrame = 0;             // 当前的动画帧
  this.currentAnimFrames = [];       // 存储当前状态的动画帧在雪碧图中的 x 坐标
  this.blinkDelay = 0;               // 眨眼间隔的时间(随 机)
  this.blinkCount = 0;               // 眨眼次数
  this.animStartTime = 0;            // 小恐龙眨眼动画开始时间
  this.timer = 0;                    // 计时器
  this.msPerFrame = 1000 / FPS;      // 帧率
  this.status = Trex.status.WAITING; // 当前的状态
  this.config = Trex.config;

  this.jumping = false;              // 是否跳跃
  this.ducking = false;              // 是否闪避(俯身)
  this.jumpVelocity = 0;             // 跳跃的速度
  this.reachedMinHeight = false;     // 是否达到最低高度
  this.speedDrop = false;            // 是否加速下降
  this.jumpCount = 0;                // 跳跃的次数
  this.jumpspotX = 0;                // 跳跃点的 x 坐标

  this.init();
}

相关的配置参数:

Trex.config = {
  GRAVITY: 0.6,               // 引力
  WIDTH: 44,                  // 站立时的宽度
  HEIGHT: 47,
  WIDTH_DUCK: 59,             // 俯身时的宽度
  HEIGHT_DUCK: 25,
  MAX_JUMP_HEIGHT: 30,        // 最大跳跃高度
  MIN_JUMP_HEIGHT: 30,        // 最小跳跃高度
  SPRITE_WIDTH: 262,          // 站立的小恐龙在雪碧图中的总宽度
  DROP_VELOCITY: -5,          // 下落的速度
  INITIAL_JUMP_VELOCITY: -10, // 初始跳跃速度
  SPEED_DROP_COEFFICIENT: 3,  // 下落时的加速系数(越大下落的越快)
  INTRO_DURATION: 1500,       // 开场动画的时间
  START_X_POS: 50,            // 开场动画结束后,小恐龙在 canvas 上的 x 坐标
};

Trex.BLINK_TIMING = 7000;     // 眨眼最大间隔的时间

// 小恐龙的状态
Trex.status = {
  CRASHED: 'CRASHED', // 撞到障碍物
  DUCKING: 'DUCKING', // 正在闪避(俯身)
  JUMPING: 'JUMPING', // 正在跳跃
  RUNNING: 'RUNNING', // 正在奔跑
  WAITING: 'WAITING', // 正在等待(未开始游戏)
};

// 为不同的状态配置不同的动画帧
Trex.animFrames = {
  WAITING: {
    frames: [44, 0],
    msPerFrame: 1000 / 3
  },
  RUNNING: {
    frames: [88, 132],
    msPerFrame: 1000 / 12
  },
  CRASHED: {
    frames: [220],
    msPerFrame: 1000 / 60
  },
  JUMPING: {
    frames: [0],
    msPerFrame: 1000 / 60
  },
  DUCKING: {
    frames: [264, 323],
    msPerFrame: 1000 / 8
  },
};

补充本篇文章中会用到的一些数据:

Runner.config = {
  // ...

  BOTTOM_PAD: 10,     // 小恐龙距 canvas 底部的距离
  MAX_BLINK_COUNT: 3, // 小恐龙的最大眨眼次数
};

Runner.spriteDefinition = {
  LDPI: {
    // ...

    TREX: {x: 848, y: 2}, // 小恐龙
  },
};

然后来看下 Trex 原型链上的方法。我们首先来绘制静态的小恐龙:

Trex.prototype = {
  init: function() {
    // 获取小恐龙站在地面上时的 y 坐标
    this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
        Runner.config.BOTTOM_PAD;
    this.yPos = this.groundYPos; // 小恐龙的 y 坐标初始化

    this.draw(0, 0);             // 绘制小恐龙的第一帧图片
  },
  /**
   * 绘制小恐龙
   * @param {Number} x 当前帧相对于第一帧的 x 坐标
   * @param {Number} y 当前帧相对于第一帧的 y 坐标
   */
  draw: function(x, y) {
    // 在雪碧图中的坐标
    var sourceX = x + this.spritePos.x;
    var sourceY = y + this.spritePos.y;

    // 在雪碧图中的宽高
    var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
        this.config.WIDTH_DUCK : this.config.WIDTH;
    var sourceHeight = this.config.HEIGHT;

    // 绘制到 canvas 上时的高度
    var outputHeight = sourceHeight;

    // 躲避状态.
    if (this.ducking && this.status != Trex.status.CRASHED) {
      this.ctx.drawImage(
        Runner.imageSprite,
        sourceX, sourceY,
        sourceWidth, sourceHeight,
        this.xPos, this.yPos,
        this.config.WIDTH_DUCK, outputHeight
      );
    } else {
      // 躲闪状态下撞到障碍物
      if (this.ducking && this.status == Trex.status.CRASHED) {
        this.xPos++;
      }
      // 奔跑状态
      this.ctx.drawImage(
        Runner.imageSprite,
        sourceX, sourceY,
        sourceWidth, sourceHeight,
        this.xPos, this.yPos,
        this.config.WIDTH, outputHeight
      );
    }

    this.ctx.globalAlpha = 1;
  },
};

前面进入街机模式那一章中,用到了 Trex 类中的数据,临时定义了 Trex 类,别忘了将其删除。

接下来需要通过 Runner 类调用 Trex 类。添加属性用于存储小恐龙类的实例:

function Runner(containerSelector, opt_config) {
  // ...

+ this.tRex = null; // 小恐龙
}

初始化小恐龙类:

Runner.prototype = {
  init: function () {
    // ...

    // 加载小恐龙类
+   this.tRex = new Trex(this.canvas, this.spriteDef.TREX);
  },
};

这样在游戏初始化时就绘制出了静态的小恐龙,如图:

实现眨眼效果

游戏初始化之后,小恐龙会随机眨眼睛。默认的是最多只能眨三次。下面将实现这个效果。

添加更新小恐龙的方法:

Trex.prototype = {
  /**
   * 更新小恐龙
   * @param {Number} deltaTime 间隔时间
   * @param {String} opt_status 小恐龙的状态
   */
  update: function(deltaTime, opt_status) {
    this.timer += deltaTime;

    // 更新状态的参数
    if (opt_status) {
      this.status = opt_status;
      this.currentFrame = 0;
      this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
      this.currentAnimFrames = Trex.animFrames[opt_status].frames;

      if (opt_status == Trex.status.WAITING) {
        this.animStartTime = getTimeStamp(); // 设置眨眼动画开始的时间
        this.setBlinkDelay();                // 设置眨眼间隔的时间
      }
    }

    if (this.status == Trex.status.WAITING) {
      // 小恐龙眨眼
      this.blink(getTimeStamp());
    } else {
      // 绘制动画帧
      this.draw(this.currentAnimFrames[this.currentFrame], 0);
    }

    if (this.timer >= this.msPerFrame) {
      // 更新当前动画帧,如果处于最后一帧就更新为第一帧,否则更新为下一帧
      this.currentFrame = this.currentFrame ==
        this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
      // 重置计时器
      this.timer = 0;
    }
  },
  // 设置眨眼间隔的时间
  setBlinkDelay: function() {
    this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
  },
  // 小恐龙眨眼
  blink: function (time) {
    var deltaTime = time - this.animStartTime;
    
    // 间隔时间大于随机获取的眨眼间隔时间才能眨眼
    if (deltaTime >= this.blinkDelay) {
      this.draw(this.currentAnimFrames[this.currentFrame], 0);
      
      // 正在眨眼
      if (this.currentFrame == 1) {
        console.log('眨眼');
        this.setBlinkDelay();      // 重新设置眨眼间隔的时间
        this.animStartTime = time; // 更新眨眼动画开始的时间
        this.blinkCount++;         // 眨眼次数加一
      }
    }
  },
};

然后将小恐龙初始更新为等待状态:

Trex.prototype = {
  init: function () {
    // ...

    this.update(0, Trex.status.WAITING); // 初始为等待状态
  },
};

最后在 Runner 的 update 方法中调用 Trex 的 update 方法来实现小恐龙眨眼:

Runner.prototype = {
  update: function () {
    // ...

    // 游戏变为开始状态或小恐龙还没有眨三次眼
-   if (this.playing) {
+   if (this.playing || (!this.activated &&
+     this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) {
+     this.tRex.update(deltaTime);
      
      // 进行下一次更新
      this.scheduleNextUpdate();
    }
  },
};

效果如下:

Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙

可以看到,眨眼的代码逻辑触发了 3 次,但是实际小恐龙只眨眼了 1 次。这就是前面说的,小恐龙默认最多只能眨三次眼。具体原因如下:

先来看下 Trex 的 update 方法中的这段代码:

if (this.timer >= this.msPerFrame) {
  // 更新当前动画帧,如果处于最后一帧就更新为第一帧,否则更新为下一帧
  this.currentFrame = this.currentFrame ==
    this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
  // 重置计时器
  this.timer = 0;
}

这段代码会将当前动画帧不断更新为下一帧。对于小恐龙来说就是不断切换 睁眼闭眼 这两帧。如果当前帧为 “睁眼”,那么执行 blink 函数后小恐龙还是睁眼,也就是说实际小恐龙没眨眼;同理,只有当前帧为 “闭眼” 时,执行 blink 函数后,小恐龙才会真正的眨眼。

至于这样做的目的,就是为了防止小恐龙不停的眨眼睛。例如,将 blink 函数修改为:

// 小恐龙眨眼
blink: function () {
  this.draw(this.currentAnimFrames[this.currentFrame], 0);
},

这样小恐龙会不停的眨眼睛。所以需要对其进行限制,这里 Chrome 开发人员的做法就是:设置一个间隔时间,当小恐龙眨眼的间隔时间大于这个设置的间隔时间,并且当前动画帧为 “闭眼” 时,才允许小恐龙眨眼睛。然后每次眨完眼后,重新设置眨眼间隔(默认设置为 0~7 秒),就实现了小恐龙的随机眨眼。

小恐龙的开场动画

下面来实现小恐龙对键盘按键的响应。

首先,当触发游戏彩蛋后,小恐龙会跳跃一次,并向右移动 50 像素(默认设置的是 50 像素)。

添加让小恐龙开始跳跃的方法:

Trex.prototype = {
  // 开始跳跃
  startJump: function(speed) {
    if (!this.jumping) {
      // 更新小恐龙为跳跃状态 
      this.update(0, Trex.status.JUMPING);
      
      // 根据游戏的速度调整跳跃的速度
      this.jumpVelocity = this.config.INITIAL_JUMP_VELOCITY - (speed / 10);
      
      this.jumping = true;
      this.reachedMinHeight = false;
      this.speedDrop = false;
    }
  },
};

进行调用:

Runner.prototype = {
  onKeyDown: function (e) {
    if (!this.crashed && !this.paused) {
      if (Runner.keyCodes.JUMP[e.keyCode]) {
        e.preventDefault();
        
        // ...

        // 开始跳跃
+       if (!this.tRex.jumping && !this.tRex.ducking) {
+         this.tRex.startJump(this.currentSpeed);
+       }
      }
    }
  },
};

这样,按下空格键后,小恐龙仍然会静止在地面上。接下来还需要更新动画帧才能实现小恐龙的奔跑动画。

添加更新小恐龙动画帧的方法:

Trex.prototype = {
  // 更新小恐龙跳跃时的动画帧
  updateJump: function(deltaTime) {
    var msPerFrame = Trex.animFrames[this.status].msPerFrame; // 获取当前状态的帧率
    var framesElapsed = deltaTime / msPerFrame;

    // 加速下落
    if (this.speedDrop) {
      this.yPos += Math.round(this.jumpVelocity *
        this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
    } else {
      this.yPos += Math.round(this.jumpVelocity * framesElapsed);
    }

    // 跳跃的速度受重力的影响,向上逐渐减小,然后反向
    this.jumpVelocity += this.config.GRAVITY * framesElapsed;

    // 达到了最低允许的跳跃高度
    if (this.yPos < this.minJumpHeight || this.speedDrop) {
      this.reachedMinHeight = true;
    }

    // 达到了最高允许的跳跃高度
    if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
      this.endJump(); // 结束跳跃
    }

    // 重新回到地面,跳跃完成
    if (this.yPos > this.groundYPos) {
      this.reset();     // 重置小恐龙的状态
      this.jumpCount++; // 跳跃次数加一
    }
  },
  // 跳跃结束
  endJump: function() {
    if (this.reachedMinHeight &&
        this.jumpVelocity < this.config.DROP_VELOCITY) {
      this.jumpVelocity = this.config.DROP_VELOCITY; // 下落速度重置为默认
    }
  },
  // 重置小恐龙状态
  reset: function() {
    this.yPos = this.groundYPos;
    this.jumpVelocity = 0;
    this.jumping = false;
    this.ducking = false;
    this.update(0, Trex.status.RUNNING);
    this.speedDrop = false;
    this.jumpCount = 0;
  },
};

其中 minJumpHeight 的属性值为:

Trex.prototype = {
  init: function() {
    // 最低跳跃高度
+   this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
    
    // ...
  },
}

然后进行调用:

Runner.prototype = {
  update: function () {
    // ...

    if (this.playing) {
      this.clearCanvas();

+     if (this.tRex.jumping) {
+       this.tRex.updateJump(deltaTime);
+     }

      this.runningTime += deltaTime;
      var hasObstacles = this.runningTime > this.config.CLEAR_TIME;

      // 刚开始 this.playingIntro 未定义 !this.playingIntro 为真
-     if (!this.playingIntro) {
+     if (this.tRex.jumpCount == 1 && !this.playingIntro) {
        this.playIntro(); // 执行开场动画
      }

      // ...
    }

    // ...
  },
};

这样在按下空格键后,小恐龙就会跳跃一次并进行奔跑动画。如图:

Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙

下面来实现效果:小恐龙第一次跳跃后,向右移动 50 像素。

修改 Trex 的 update 方法。当判断到正在执行开场动画时,移动小恐龙:

Trex.prototype = {
  update: function(deltaTime, opt_status) {
    this.timer += deltaTime;

    // 更新状态的参数
    if (opt_status) {
      // ...
    }

    // 正在执行开场动画,将小恐龙向右移动 50 像素
+   if (this.playingIntro && this.xPos < this.config.START_X_POS) {
+     this.xPos += Math.round((this.config.START_X_POS /
+       this.config.INTRO_DURATION) * deltaTime);
+   }

    // ...
  },
};

可以看出当 playingIntro 属性为 true 时,小恐龙就会向右移动。所以需要通过控制这个属性的值来控制小恐龙第一次跳跃后的移动。

修改 Runner 上的 playIntro 方法,将小恐龙标记为正在执行开场动画:

Runner.prototype = {
  playIntro: function () {
    if (!this.activated && !this.crashed) {
+     this.tRex.playingIntro = true; // 小恐龙执行开场动画

      // ...
    }
  },
};

然后需要在开始游戏后也就是执行 startGame 方法时,结束小恐龙的开场动画:

Runner.prototype = {
  startGame: function () {
    this.setArcadeMode();           // 进入街机模式
    
+   this.tRex.playingIntro = false; // 小恐龙的开场动画结束
    
    // ...
  },
};

效果如下:

Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙

可以很明显的看到,小恐龙在第一次跳跃后向右移动了一段距离(默认 50 像素)。

使用键盘控制小恐龙

在这个游戏中,当按下 键后,如果小恐龙正在跳跃,就会快速下落,如果小恐龙在地上,就会进入躲闪状态,下面来实现这些效果。

加速下落:

Trex.prototype = {
  // 设置小恐龙为加速下落,立即取消当前的跳跃
  setSpeedDrop: function() {
    this.speedDrop = true;
    this.jumpVelocity = 1;
  },
};

设置小恐龙是否躲闪:

Trex.prototype = {
  // 设置小恐龙奔跑时是否躲闪
  setDuck: function(isDucking) {
    if (isDucking && this.status != Trex.status.DUCKING) { // 躲闪状态
      this.update(0, Trex.status.DUCKING);
      this.ducking = true;
    } else if (this.status == Trex.status.DUCKING) {       // 奔跑状态
      this.update(0, Trex.status.RUNNING);
      this.ducking = false;
    }
  },
};

onKeyDown 方法中调用:

Runner.prototype = {
  onKeyDown: function () {
    if (!this.crashed && !this.paused) {
      if (Runner.keyCodes.JUMP[e.keyCode]) {
        // ...
+     } else if (this.playing && Runner.keyCodes.DUCK[e.keyCode]) {
+       e.preventDefault();
+
+       if (this.tRex.jumping) {
+         this.tRex.setSpeedDrop(); // 加速下落
+       } else if (!this.tRex.jumping && !this.tRex.ducking) {
+         this.tRex.setDuck(true);  // 进入躲闪状态
+       }
+     }
    }
  },
};

这样就实现了前面所说的效果。但是小恐龙进入躲闪状态后,如果松开按键并不会重新站起来。因为现在还没有定义松开键盘按键时响应的事件。下面来定义:

Runner.prototype = {
  onKeyUp: function(e) {
    var keyCode = String(e.keyCode);

    if (Runner.keyCodes.DUCK[keyCode]) { // 躲避状态
      this.tRex.speedDrop = false;
      this.tRex.setDuck(false);
    }
  },
};

然后调用,修改 handleEvent 方法:

Runner.prototype = {
  handleEvent: function (e) {
    return (function (eType, events) {
      switch (eType) {
        // ...

+       case events.KEYUP:
+         this.onKeyUp(e);
+         break;
        default:
          break;
      }
    }.bind(this))(e.type, Runner.events);
  },
};

效果如下:

Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙

第一次跳是正常下落,第二次跳是加速下落

处理小恐龙的跳跃

小恐龙的跳跃分为大跳和小跳,如图:

Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙

要实现这个效果,只需要在 键被松开时,立即结束小恐龙的跳跃即可。

修改 onKeyUp 方法:

Runner.prototype = {
  onKeyUp: function(e) {
    var keyCode = String(e.keyCode);
+   var isjumpKey = Runner.keyCodes.JUMP[keyCode];

+   if (this.isRunning() && isjumpKey) {        // 跳跃
+     this.tRex.endJump();
    } else if (Runner.keyCodes.DUCK[keyCode]) { // 躲避状态
      this.tRex.speedDrop = false;
      this.tRex.setDuck(false);
    }
  },
};

其中 isRunning 方法定义如下:

Runner.prototype = {
  // 是否游戏正在进行
  isRunning: function() {
    return !!this.raqId;
  },
};

这样就实现了小恐龙的大跳和小跳。

最后是要实现的效果是:如果页面失焦时,小恐龙正在跳跃,就重置小恐龙的状态(也就是会立即回到地面上)。这个效果实现很简单,直接调用前面定义的 reset 方法即可:

Runner.prototype = {
  play: function () {
    if (!this.crashed) {
      // ...

+     this.tRex.reset();
    }
  },
};

效果如下:

Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙

查看添加的代码, 戳这里

Demo 体验地址: https://liuyib.github.io/pages/demo/games/google-dino/dino-gogogo/

上一篇 下一篇 Chrome 小恐龙游戏源码探究七 -- 昼夜模式交替 TODO

以上所述就是小编给大家介绍的《Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

JavaScript从入门到精通

JavaScript从入门到精通

明日科技 / 清华大学出版社 / 2012-9 / 69.80元

《JavaScript从入门到精通》从初学者角度出发,通过通俗易懂的语言、丰富多彩的实例,详细介绍了使用JavaScript语言进行程序开发应该掌握的各方面技术。全书共分24章,包括初识JavaScript、JavaScript基础、流程控制、函数、JavaScript对象与数组、字符串与数值处理对象、正则表达式、程序调试与错误处理、事件处理、处理文档(document对象)、文档对象模型(DOM......一起来看看 《JavaScript从入门到精通》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具