文章首发于我的个人博客

前言

上一篇文章:《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 小恐龙游戏源码探究九 -- 游戏碰撞检测无