共计 5114 个字符,预计需要花费 13 分钟才能阅读完成。
文章首发于我的个人博客
前言
上一篇文章:《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
方法已经定义过了。DistanceMeter
、Horizon
类上的 reset
方法定义如下:
DistanceMeter.prototype = {
// 重置当前分数为 '00000'
reset: function() {this.update(0); // 更新分数
this.achievement = false; // 分数不进行闪动特效
}
};
Horizon.prototype = {
// 重置背景类
reset: function() {this.obstacles = []; // 清空障碍物
this.horizonLine.reset(); // 重置地面
this.nightMode.reset(); // 重置夜晚模式},
};
同样的,HorizonLine
、NightMode
类上的 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 小恐龙游戏源码探究九 — 游戏碰撞检测 | 无 |