文章首發於個人 GitHub 博客
上一篇文章:《Chrome 小恐龍遊戲源碼探究六 -- 記錄遊戲分數》實現了遊戲分數、最高分數的記錄和繪製。這一篇文章中將實現晝夜模式交替的的效果。css
定義夜晚模式類 NightMode
:git
/** * 夜晚模式 * @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(); }
相關的配置參數:github
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];
補充本篇文章中會用到的一些數據:canvas
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
原型鏈上的方法:segmentfault
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
來進行調用。dom
修改 Horizon
類:動畫
function Horizon(canvas, spritePos, dimensions, gapCoefficient) { // ... + // 夜晚模式 + this.nightMode = null; }
初始化 NightMode
類:this
Horizon.prototype = { init: function () { // ... + this.nightMode = new NightMode(this.canvas, this.spritePos.MOON, + this.dimensions.WIDTH); }, };
更新夜晚模式:google
Horizon.prototype = { - update: function (deltaTime, currentSpeed, updateObstacles) { + update: function (deltaTime, currentSpeed, updateObstacles, showNightMode) { // ... + this.nightMode.update(showNightMode); }, };
而後修改 Runner
的 update
方法:spa
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/blog/demo/game/google-dino/night-mode/
上一篇 | 下一篇 |
Chrome 小恐龍遊戲源碼探究六 -- 記錄遊戲分數 | Chrome 小恐龍遊戲源碼探究八 -- 奔跑的小恐龍 |