文章首發於個人 GitHub 博客
上一篇文章:《Chrome 小恐龍遊戲源碼探究五 -- 隨機繪製障礙》 實現了障礙物仙人掌和翼龍的繪製。這一篇將實現當前分數、最高分數的記錄和繪製。git
在遊戲中,小恐龍移動的距離就是遊戲的分數。分數每達 100
,就會有閃動的特效。首次進行遊戲的時候,因爲沒有記錄過遊戲的歷史得分,因此不會顯示最高分,只有當第一次 game over
後才能顯示歷史最高分。github
定義分數類 DistanceMeter
:web
/** * 記錄移動的距離(分數等於移動距離) * @param {HTMLCanvasElement} canvas 畫布 * @param {Object} spritePos 圖片在雪碧圖中的位置 * @param {Number} canvasWidth 畫布的寬度 */ function DistanceMeter(canvas, spritePos, canvasWidth) { this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.config = DistanceMeter.config; this.spritePos = spritePos; this.x = 0; // 分數顯示在 canvas 中的 x 座標 this.y = 5; this.maxScore = 0; // 遊戲分數上限 this.highScore = []; // 存儲最高分數的每一位數字 this.digits = []; // 存儲分數的每一位數字 this.achievement = false; // 是否進行閃動特效 this.defaultString = ''; // 遊戲的默認距離(00000) this.flashTimer = 0; // 動畫計時器 this.flashIterations = 0; // 特效閃動的次數 this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS; // 分數的最大位數 this.init(canvasWidth); }
有關的配置參數:canvas
DistanceMeter.config = { MAX_DISTANCE_UNITS: 5, // 分數的最大位數 ACHIEVEMENT_DISTANCE: 100, // 每 100 米觸發一次閃動特效 COEFFICIENT: 0.025, // 將像素距離轉換爲比例單位的係數 FLASH_DURATION: 1000 / 4, // 一閃的時間(一次閃動分別兩閃:從有到無,從無到有) FLASH_ITERATIONS: 3, // 閃動的次數 }; DistanceMeter.dimensions = { WIDTH: 10, HEIGHT: 13, DEST_WIDTH: 11, // 加上間隔後每一個數字的寬度 };
補充本篇文章中會用到的一些數據:segmentfault
function Runner(containerSelector, opt_config) { // ... + this.msPerFrame = 1000 / FPS; // 每幀的時間 }, Runner.spriteDefinition = { LDPI: { //... + TEXT_SPRITE: {x: 655, y: 2}, // 文字 }, };
在 DistanceMeter
上添加方法:數組
DistanceMeter.prototype = { // 初始化分數 init: function (width) { var maxDistanceStr = ''; // 遊戲的最大距離 this.calcXPos(width); // 計算分數顯示在 canvas 中的 x 座標 for (var i = 0; i < this.maxScoreUnits; i++) { this.draw(i, 0); // 第一次遊戲,不繪製最高分 this.defaultString += '0'; // 默認初始分數 00000 maxDistanceStr += '9'; // 默認最大分數 99999 } this.maxScore = parseInt(maxDistanceStr); }, // 計算 x 座標 calcXPos: function (canvasWidth) { this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH * (this.maxScoreUnits + 1)); }, /** * 將分數繪製到 canvas 上 * @param {Number} digitPos 數字在分數中的位置 * @param {Number} value 數字的具體值(0-9) * @param {Boolean} opt_highScore 是否顯示最高分 */ draw: function (digitPos, value, opt_highScore) { // 在雪碧圖中的座標 var sourceX = this.spritePos.x + DistanceMeter.dimensions.WIDTH * value; var sourceY = this.spritePos.y + 0; var sourceWidth = DistanceMeter.dimensions.WIDTH; var sourceHeight = DistanceMeter.dimensions.HEIGHT; // 繪製到 canvas 時的座標 var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH; var targetY = this.y; var targetWidth = DistanceMeter.dimensions.WIDTH; var targetHeight = DistanceMeter.dimensions.HEIGHT; this.ctx.save(); if (opt_highScore) { // 顯示最高分 var hightScoreX = this.x - (this.maxScoreUnits * 2) * DistanceMeter.dimensions.WIDTH; this.ctx.translate(hightScoreX, this.y); } else { // 不顯示最高分 this.ctx.translate(this.x, this.y); } this.ctx.drawImage( Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight, targetX, targetY, targetWidth, targetHeight ); this.ctx.restore(); }, /** * 將遊戲移動的像素距離轉換爲真實的距離 * @param {Number} distance 遊戲移動的像素距離 */ getActualDistance: function (distance) { return distance ? Math.round(distance * this.config.COEFFICIENT) : 0; }, // 更新分數 update: function (deltaTime, distance) { var paint = true; // 是否繪製分數 var playSound = false; // 是否播放音效 // 沒有進行閃動特效 if (!this.achievement) { distance = this.getActualDistance(distance); // 分數超出上限時,上限增長一位數。超出上限兩位數時,分數置零 if (distance > this.maxScore && this.maxScoreUnits === this.config.MAX_DISTANCE_UNITS) { this.maxScoreUnits++; this.maxScore = parseInt(this.maxScore + '9'); } else { this.distance = 0; } if (distance > 0) { // 觸發閃動特效 if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) { this.achievement = true; this.flashTimer = 0; playSound = true; } // 分數前面補零來湊位數 var distanceStr = (this.defaultString + distance).substr(-this.maxScoreUnits); this.digits = distanceStr.split(''); } else { // 將默認分數 00000 中的每一位數字存到數組中 this.digits = this.defaultString.split(''); } } else { // 控制特效的閃動次數 if (this.flashIterations <= this.config.FLASH_ITERATIONS) { this.flashTimer += deltaTime; // 第一閃不繪製數字 if (this.flashTimer < this.config.FLASH_DURATION) { paint = false; } // 進行了兩閃,閃動次數加一 else if (this.flashTimer > this.config.FLASH_DURATION * 2) { this.flashTimer = 0; this.flashIterations++; } } else { // 閃動特效結束 this.achievement = false; this.flashIterations = 0; this.flashTimer = 0; } } // 繪製當前分 if (paint) { for (var i = this.digits.length - 1; i >= 0; i--) { this.draw(i, parseInt(this.digits[i])); } } // 繪製最高分 this.drawHighScore(); return playSound; }, // 繪製最高分 drawHighScore: function () { this.ctx.save(); this.ctx.globalAlpha = 0.8; for (var i = this.highScore.length - 1; i >= 0; i--) { this.draw(i, parseInt(this.highScore[i], 10), true); } this.ctx.restore(); }, /** * 將遊戲的最高分數存入數組 * @param {Number} distance 遊戲移動的像素距離 */ setHighScore: function (distance) { distance = this.getActualDistance(distance); var highScoreStr = (this.defaultString + distance).substr(-this.maxScoreUnits); // 分數前面字母 H、I 在雪碧圖中位於數字後面,也就是第 十、11 位置 this.highScore = ['10', '11', ''].concat(highScoreStr.split('')); }, };
下面是對分數類的調用。動畫
首先給 Runner
類添加屬性:this
function Runner(containerSelector, opt_config) { // ... + this.distanceMeter = null; // 距離計數類 + this.distanceRan = 0; // 遊戲移動距離 + this.highestScore = 0; // 最高分 }
而後初始化分數類 DistanceMeter
:google
Runner.prototype = { init: function () { // ... + // 加載距離計數器類 DistanceMeter + this.distanceMeter = new DistanceMeter(this.canvas, + this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH); }, };
更新遊戲分數(移動距離):spa
Runner.prototype = { update: function () { this.updatePending = false; // 等待更新 if (this.playing) { // ... + this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame; if (this.currentSpeed < this.config.MAX_SPEED) { this.currentSpeed += this.config.ACCELERATION; } + var playAchievementSound = this.distanceMeter.update(deltaTime, + Math.ceil(this.distanceRan)); } // ... }, };
這樣就實現了分數的繪製,效果以下:
查看添加或修改的代碼, 戳這裏
上面定義了保存、繪製遊戲最高分的方法,但尚未調用,如今只要在遊戲結束時,將分數保存下來,就能實現最高分的繪製。添加 gameOver
方法:
Runner.prototype = { // 遊戲結束 gameOver: function () { this.stop(); if (this.distanceRan > this.highestScore) { this.highestScore = Math.ceil(this.distanceRan); this.distanceMeter.setHighScore(this.highestScore); // 保存最高分 } // 重置時間 this.time = getTimeStamp(); }, };
這裏爲了演示,當頁面失焦時,結束遊戲(等後面講到實現遊戲結束時,須要刪除這裏的臨時代碼):
Runner.prototype = { onVisibilityChange: function (e) { if (document.hidden || document.webkitHidden || e.type == 'blur' || document.visibilityState != 'visible') { this.stop(); + this.gameOver(); } else if (!this.crashed) { this.play(); } }, };
效果以下:
查看添加或修改的代碼, 戳這裏
Demo 體驗地址:https://liuyib.github.io/blog/demo/game/google-dino/show-score/
上一篇 | 下一篇 |
Chrome 小恐龍遊戲源碼探究五 -- 隨機繪製障礙 | Chrome 小恐龍遊戲源碼探究七 -- 晝夜模式交替 |