乐趣区

Chrome-小恐龙游戏源码探究完-游戏结束和其他要素

文章首发于我的个人博客

前言

上一篇文章:《Chrome 小恐龙游戏源码探究九 — 游戏碰撞检测》实现了游戏的碰撞检测,这一篇文章中将利用碰撞检测的结果,实现游戏的结束。

绘制 Game Over 面板

这个游戏结束时的 Game Over 面板是通过 GameOverPanel 类绘制的。

定义 GameOverPanel 类:

/**
 * 游戏结束面板类
 * @param {!HTMLCanvasElement} 画布元素
 * @param {Object} textImgPos 文字 "Game Over" 在雪碧图中的位置
 * @param {Object} restartImgPos 重置按钮在雪碧图中的位置
 * @param {!Object} dimensions 游戏画布的尺寸
 */
function GameOverPanel(canvas, textImgPos, restartImgPos, dimensions) {
  this.canvas = canvas;
  this.ctx = canvas.getContext('2d');
  this.canvasDimensions = dimensions;
  this.textImgPos = textImgPos;
  this.restartImgPos = restartImgPos;

  this.draw();};

相关的配置参数,以及原型链上的方法:

// 配置参数
GameOverPanel.dimensions = {
  TEXT_X: 0,          // 文字 "Game Over" 的 x 坐标
  TEXT_Y: 13,
  TEXT_WIDTH: 191,    // 文字 "Game Over" 的宽度
  TEXT_HEIGHT: 11,
  RESTART_WIDTH: 36,  // 重置按钮的宽度
  RESTART_HEIGHT: 32,
};

GameOverPanel.prototype = {draw: function() {
    var dimensions = GameOverPanel.dimensions;
    var centerX = this.canvasDimensions.WIDTH / 2;

    // 文字 "Game Over"
    var textSourceX = dimensions.TEXT_X;
    var textSourceY = dimensions.TEXT_Y;
    var textSourceWidth = dimensions.TEXT_WIDTH;
    var textSourceHeight = dimensions.TEXT_HEIGHT;

    var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
    var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
    var textTargetWidth = dimensions.TEXT_WIDTH;
    var textTargetHeight = dimensions.TEXT_HEIGHT;

    // 重置按钮
    var restartSourceWidth = dimensions.RESTART_WIDTH;
    var restartSourceHeight = dimensions.RESTART_HEIGHT;
    var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2);
    var restartTargetY = this.canvasDimensions.HEIGHT / 2;

    textSourceX += this.textImgPos.x;
    textSourceY += this.textImgPos.y;

    // 文字 "Game over"
    this.ctx.drawImage(Runner.imageSprite,
      textSourceX, textSourceY, textSourceWidth, textSourceHeight,
      textTargetX, textTargetY, textTargetWidth, textTargetHeight);

    // 重置按钮
    this.ctx.drawImage(Runner.imageSprite,
      this.restartImgPos.x, this.restartImgPos.y,
      restartSourceWidth, restartSourceHeight,
      restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
      dimensions.RESTART_HEIGHT);
  }
};

然后需要在游戏结束时,调用 GameOverPanel 类。

gameOver 方法中调用 GameOverPanel 类,并更新小恐龙的状态和一些标志位:

Runner.prototype = {
  // 游戏结束
  gameOver: function () {this.stop();
+   this.crashed = true;                    // 小恐龙撞到了障碍物
+   this.distanceMeter.achievement = false; // 结束分数闪动特效

+   // 更新小恐龙为碰撞状态
+   this.tRex.update(100, Trex.status.CRASHED);

+   // 绘制游戏结束面板
+   if (!this.gameOverPanel) {
+     this.gameOverPanel = new GameOverPanel(this.canvas,
+       this.spriteDef.TEXT_SPRITE, this.spriteDef.RESTART,
+       this.dimensions);
+   } else {+     this.gameOverPanel.draw();
+   }

    if (this.distanceRan > this.highestScore) {this.highestScore = Math.ceil(this.distanceRan);
      this.distanceMeter.setHighScore(this.highestScore); // 保存最高分
    }

    // 重置时间
    this.time = getTimeStamp();},
};

补充数据:

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

+   RESTART: {x: 2, y: 2}, // 重置游戏按钮
  },
};

前面的文章中,为了演示效果,我们把 gameOver 方法的调用临时放在了判断页面是否失焦的 onVisibilityChange 方法中。现在我们就不需要这个临时调用了,删除即可。gameOver 实际的调用如下:

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

    if (this.playing) {
      // ...

      // 碰撞检测
      var collision = hasObstacles &&
-       checkForCollision(this.horizon.obstacles[0], this.tRex, this.ctx);
+       checkForCollision(this.horizon.obstacles[0], this.tRex);

+     if (!collision) {
        this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;

        if (this.currentSpeed < this.config.MAX_SPEED) {this.currentSpeed += this.config.ACCELERATION;}
+     } else {+       this.gameOver();
+     }

      var playAchievementSound = this.distanceMeter.update(deltaTime,
        Math.ceil(this.distanceRan));

      // ...
    }

    // ...
  },
};

这样就实现了结束游戏后,Game Over 面板的绘制,效果如下:

查看添加和修改的代码,戳这里

重新开始游戏

想要重新开始游戏,无非就是把保存的数据清零以及把标志位给重置:

Runner.prototype = {
  // 重新开始游戏
  restart: function() {if (!this.raqId) {
      this.runningTime = 0;                  // 重置游戏运行时间
      this.setPlayStatus(true);              // 游戏重置为进行状态
      this.paused = false;                   // 游戏没有暂停
      this.crashed = false;                  // 小恐龙没有撞到障碍物
      this.distanceRan = 0;                  // 重置游戏移动距离(分数)this.currentSpeed = this.config.SPEED; // 重置游戏当前的速度
      this.time = getTimeStamp();            // 重置计时器
      this.clearCanvas();                    // 清空画布
      this.distanceMeter.reset();            // 重置分数类
      this.horizon.reset();                  // 重置背景类
      this.tRex.reset();                     // 重置小恐龙类
      this.invert(true);                     // 重置页面为没有进行颜色反转
      this.update();                         // 重置后更新游戏}
  },
};

其中 Trex 类上的 reset 方法已经定义过了。DistanceMeterHorizon 类上的 reset 方法定义如下:

DistanceMeter.prototype = {
  // 重置当前分数为 '00000'
  reset: function() {this.update(0);           // 更新分数
    this.achievement = false; // 分数不进行闪动特效
  }
};

Horizon.prototype = {
  // 重置背景类
  reset: function() {this.obstacles = [];      // 清空障碍物
    this.horizonLine.reset(); // 重置地面
    this.nightMode.reset();   // 重置夜晚模式},
};

同样的,HorizonLineNightMode 类上的 reset 方法定义如下:

HorizonLine.prototype = {reset: function() {
    // 重置两段地面的坐标
    this.xPos[0] = 0;
    this.xPos[1] = HorizonLine.dimensions.WIDTH;
  },
};

NightMode.prototype = {reset: function() {
    this.currentPhase = 0; // 重置月期
    this.opacity = 0;      // 重置夜晚模式中的物体透明度
    this.update(false);    // 重置夜晚模式位未激活状态
  },
};

最后,调用 restart 方法:

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);
+   } else if (this.crashed) {+     var deltaTime = getTimeStamp() - this.time;
+
+     // 按下回车键或者等待 750 毫秒后,按下空格键,重新开始游戏
+     if (Runner.keyCodes.RESTART[keyCode] ||
+         (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&
+         Runner.keyCodes.JUMP[keyCode])) {+       this.restart();
+     }
+   }
  },
};

效果如下:

查看添加和修改的代码,戳这里

加载游戏音效

TODO

Demo 体验地址:[]() TODO

游戏的其他要素

到这里,我们已经把游戏的必要功能都实现了。原游戏中,还实现了移动端适配,高分屏的适配,是否需要进入街机模式的处理,游戏画布对浏览器窗口大小的自适应。

这些功能我们就不再实现,感兴趣的可以自行尝试。

结语

利用十多天的业余时间探究了小恐龙游戏的源码,收获很多,也为自己以后开发 H5 游戏提供了一定的思路。

由于水平有限,如果文章存在有歧义或错误的地方,欢迎评论指出。

(完)

上一篇 下一篇
Chrome 小恐龙游戏源码探究九 — 游戏碰撞检测
          
          
退出移动版