文章首发于我的个人博客
前言
上一篇文章:《Chrome 小恐龙游戏源码探究六 -- 记录游戏分数》实现了游戏分数、最高分数的记录和绘制。这一篇文章中将实现昼夜模式交替的的效果。
夜晚模式
定义夜晚模式类 NightMode:
/** * 夜晚模式 * @param {HTMLCanvasElement} canvas 画布 * @param {Object} spritePos 雪碧图中的坐标信息 * @param {Number} containerWidth 容器宽度 */function NightMode(canvas, spritePos, containerWidth) { this.canvas = canvas; this.ctx = this.canvas.getContext('2d'); this.spritePos = spritePos; this.containerWidth = containerWidth; this.xPos = containerWidth - 50; // 月亮的 x 坐标 this.yPos = 30; // 月亮的 y 坐标 this.currentPhase = 0; // 月亮当前所处的时期 this.opacity = 0; // 星星和月亮的透明度 this.stars = []; // 存储星星 this.drawStars = false; // 是否绘制星星 // 放置星星 this.placeStars();}
相关的配置参数:
NightMode.config = { WIDTH: 20, // 半月的宽度 HEIGHT: 40, // 月亮的高度 FADE_SPEED: 0.035, // 淡入淡出的速度 MOON_SPEED: 0.25, // 月亮的速度 NUM_STARS: 2, // 星星的数量 STAR_SIZE: 9, // 星星的大小 STAR_SPEED: 0.3, // 星星的速度 STAR_MAX_Y: 70, // 星星在画布上的最大 y 坐标};// 月亮所处的时期(不同的时期有不同的位置)NightMode.phases = [140, 120, 100, 60, 40, 20, 0];
补充本篇文章中会用到的一些数据:
function Runner(containerSelector, opt_config) { // ...+ this.inverted = false; // 是否开启夜晚模式+ this.invertTimer = 0; // 夜晚模式的时间}Runner.config = { // ...+ INVERT_FADE_DURATION: 12000, // 夜晚模式的持续时间+ INVERT_DISTANCE: 100, // 触发夜晚模式的距离};Runner.spriteDefinition = { LDPI: { // ...+ MOON: {x: 484, y: 2},+ STAR: {x: 645, y: 2}, },};Runner.classes = { // ...+ INVERTED: 'inverted',};
body { transition: filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1), background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1); will-change: filter, background-color;}.inverted { filter: invert(100%); background-color: #000;}
来看下 NightMode 原型链上的方法:
NightMode.prototype = { draw: function () { // 月期为 3 时,月亮为满月 var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 : NightMode.config.WIDTH; var moonSourceHeight = NightMode.config.HEIGHT; // 月亮在雪碧图中的 x 坐标 var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase]; var moonOutputWidth = moonSourceWidth; // 星星在雪碧图中的 x 坐标 var starSourceX = Runner.spriteDefinition.LDPI.STAR.x; var starSize = NightMode.config.STAR_SIZE; this.ctx.save(); this.ctx.globalAlpha = this.opacity; // 画布的透明度随之变化 // 绘制星星 if (this.drawStars) { for (var i = 0; i < NightMode.config.NUM_STARS; i++) { this.ctx.drawImage( Runner.imageSprite, starSourceX, this.stars[i].sourceY, starSize, starSize, Math.round(this.stars[i].x), this.stars[i].y, NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE, ); } } // 绘制月亮 this.ctx.drawImage( Runner.imageSprite, moonSourceX, this.spritePos.y, moonSourceWidth, moonSourceHeight, Math.round(this.xPos), this.yPos, moonOutputWidth, NightMode.config.HEIGHT ); this.ctx.globalAlpha = 1; this.ctx.restore(); }, /** * 更新星星和月亮的位置,改变月期 * @param {Boolean} activated 是否夜晚模式被激活 */ update: function (activated) { // 改变月期 if (activated && this.opacity === 0) { this.currentPhase++; if (this.currentPhase >= NightMode.phases.length) { this.currentPhase = 0; } } // 淡入 if (activated && (this.opacity < 1 || this.opacity === 0)) { this.opacity += NightMode.config.FADE_SPEED; } else if (this.opacity > 0) { // 淡出 this.opacity -= NightMode.config.FADE_SPEED; } // 设置月亮和星星的位置 if (this.opacity > 0) { // 更新月亮的 x 坐标 this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED); // 更新星星的 x 坐标 if (this.drawStars) { for (var i = 0; i < NightMode.config.NUM_STARS; i++) { this.stars[i].x = this.updateXPos(this.stars[i].x, NightMode.config.STAR_SPEED); } } this.draw(); } else { this.opacity = 0; this.placeStars(); } this.drawStars = true; }, // 更新 x 坐标 updateXPos: function (currentPos, speed) { // 月亮移出画布半个月亮宽度,将其位置移动到画布右边 if (currentPos < -NightMode.config.WIDTH) { currentPos = this.containerWidth; } else { currentPos -= speed; } return currentPos; }, // 随机放置星星 placeStars: function () { // 将画布分为若干组 var segmentSize = Math.round(this.containerWidth / NightMode.config.NUM_STARS); for (var i = 0; i < NightMode.config.NUM_STARS; i++) { this.stars[i] = {}; // 分别随机每组画布中星星的位置 this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1)); this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y); // 星星在雪碧图中的 y 坐标 this.stars[i].sourceY = Runner.spriteDefinition.LDPI.STAR.y + NightMode.config.STAR_SIZE * i; } },};
定义好 NightMode 类以及相关方法后,接下来需要通过 Horizon 来进行调用。
修改 Horizon 类:
function Horizon(canvas, spritePos, dimensions, gapCoefficient) { // ... // 夜晚模式+ this.nightMode = null;}
初始化 NightMode 类:
Horizon.prototype = { init: function () { // ...+ this.nightMode = new NightMode(this.canvas, this.spritePos.MOON,+ this.dimensions.WIDTH); },};
更新夜晚模式:
Horizon.prototype = {- update: function (deltaTime, currentSpeed, updateObstacles) {+ update: function (deltaTime, currentSpeed, updateObstacles, showNightMode) { // ...+ this.nightMode.update(showNightMode); },};
然后修改 Runner 的 update
方法:
Runner.prototype = { update: function () { this.updatePending = false; // 等待更新 if (this.playing) { // ... // 直到开场动画结束再移动地面 if (this.playingIntro) { this.horizon.update(0, this.currentSpeed, hasObstacles); } else { deltaTime = !this.activated ? 0 : deltaTime;- this.horizon.update(deltaTime, this.currentSpeed, hasObstacles);+ this.horizon.update(deltaTime, this.currentSpeed, hasObstacles,+ this.inverted); }+ // 夜晚模式+ if (this.invertTimer > this.config.INVERT_FADE_DURATION) { // 夜晚模式结束+ this.invertTimer = 0;+ this.invertTrigger = false;+ this.invert();+ } else if (this.invertTimer) { // 处于夜晚模式,更新其时间+ this.invertTimer += deltaTime;+ } else { // 还没进入夜晚模式+ // 游戏移动的距离+ var actualDistance =+ this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan));++ if(actualDistance > 0) {+ // 每移动指定距离就触发一次夜晚模式+ this.invertTrigger = !(actualDistance % this.config.INVERT_DISTANCE);++ if (this.invertTrigger && this.invertTimer === 0) {+ this.invertTimer += deltaTime;+ this.invert();+ }+ }+ } } if (this.playing) { // 进行下一次更新 this.scheduleNextUpdate(); } },};
上面用到的 invert
方法定义如下:
Runner.prototype = { /** * 反转当前页面的颜色 * @param {Boolea} reset 是否重置颜色 */ invert: function (reset) { var bodyElem = document.body; if (reset) { bodyElem.classList.toggle(Runner.classes.INVERTED, false); // 删除 className this.invertTimer = 0; // 重置夜晚模式的时间 this.inverted = false; // 关闭夜晚模式 } else { this.inverted = bodyElem.classList.toggle(Runner.classes.INVERTED, this.invertTrigger); } },};
这样就是实现了昼夜交替的效果。原来的游戏中,昼夜交替每 700 米触发一次,这里为了演示,改成了 100 米触发一次。效果如下:
查看添加的代码,戳这里
Demo 体验地址:https://liuyib.github.io/pages/demo/games/google-dino/night-mode/
上一篇 | 下一篇 | Chrome 小恐龙游戏源码探究六 -- 记录游戏分数 | Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙 |