摘要:例如,將函數(shù)修改為小恐龍眨眼這樣小恐龍會不停的眨眼睛。小恐龍的開場動畫下面來實現(xiàn)小恐龍對鍵盤按鍵的響應(yīng)。接下來還需要更新動畫幀才能實現(xiàn)小恐龍的奔跑動畫。
文章首發(fā)于我的 GitHub 博客前言
上一篇文章:《Chrome 小恐龍游戲源碼探究七 -- 晝夜模式交替》實現(xiàn)了游戲晝夜模式的交替,這一篇文章中,將實現(xiàn):1、小恐龍的繪制 2、鍵盤對小恐龍的控制 3、頁面失焦后,重新聚焦會重置小恐龍的狀態(tài)
繪制靜態(tài)的小恐龍定義小恐龍類 Trex:
/** * 小恐龍類 * @param {HTMLCanvasElement} canvas 畫布 * @param {Object} spritePos 圖片在雪碧圖中的坐標 */ function Trex(canvas, spritePos) { this.canvas = canvas; this.ctx = canvas.getContext("2d"); this.spritePos = spritePos; this.xPos = 0; this.yPos = 0; this.groundYPos = 0; // 小恐龍在地面上時的 y 坐標 this.currentFrame = 0; // 當前的動畫幀 this.currentAnimFrames = []; // 存儲當前狀態(tài)的動畫幀在雪碧圖中的 x 坐標 this.blinkDelay = 0; // 眨眼間隔的時間(隨 機) this.blinkCount = 0; // 眨眼次數(shù) this.animStartTime = 0; // 小恐龍眨眼動畫開始時間 this.timer = 0; // 計時器 this.msPerFrame = 1000 / FPS; // 幀率 this.status = Trex.status.WAITING; // 當前的狀態(tài) this.config = Trex.config; this.jumping = false; // 是否跳躍 this.ducking = false; // 是否閃避(俯身) this.jumpVelocity = 0; // 跳躍的速度 this.reachedMinHeight = false; // 是否達到最低高度 this.speedDrop = false; // 是否加速下降 this.jumpCount = 0; // 跳躍的次數(shù) this.jumpspotX = 0; // 跳躍點的 x 坐標 this.init(); }
相關(guān)的配置參數(shù):
Trex.config = { GRAVITY: 0.6, // 引力 WIDTH: 44, // 站立時的寬度 HEIGHT: 47, WIDTH_DUCK: 59, // 俯身時的寬度 HEIGHT_DUCK: 25, MAX_JUMP_HEIGHT: 30, // 最大跳躍高度 MIN_JUMP_HEIGHT: 30, // 最小跳躍高度 SPRITE_WIDTH: 262, // 站立的小恐龍在雪碧圖中的總寬度 DROP_VELOCITY: -5, // 下落的速度 INITIAL_JUMP_VELOCITY: -10, // 初始跳躍速度 SPEED_DROP_COEFFICIENT: 3, // 下落時的加速系數(shù)(越大下落的越快) INTRO_DURATION: 1500, // 開場動畫的時間 START_X_POS: 50, // 開場動畫結(jié)束后,小恐龍在 canvas 上的 x 坐標 }; Trex.BLINK_TIMING = 7000; // 眨眼最大間隔的時間 // 小恐龍的狀態(tài) Trex.status = { CRASHED: "CRASHED", // 撞到障礙物 DUCKING: "DUCKING", // 正在閃避(俯身) JUMPING: "JUMPING", // 正在跳躍 RUNNING: "RUNNING", // 正在奔跑 WAITING: "WAITING", // 正在等待(未開始游戲) }; // 為不同的狀態(tài)配置不同的動畫幀 Trex.animFrames = { WAITING: { frames: [44, 0], msPerFrame: 1000 / 3 }, RUNNING: { frames: [88, 132], msPerFrame: 1000 / 12 }, CRASHED: { frames: [220], msPerFrame: 1000 / 60 }, JUMPING: { frames: [0], msPerFrame: 1000 / 60 }, DUCKING: { frames: [264, 323], msPerFrame: 1000 / 8 }, };
補充本篇文章中會用到的一些數(shù)據(jù):
Runner.config = { // ... BOTTOM_PAD: 10, // 小恐龍距 canvas 底部的距離 MAX_BLINK_COUNT: 3, // 小恐龍的最大眨眼次數(shù) }; Runner.spriteDefinition = { LDPI: { // ... TREX: {x: 848, y: 2}, // 小恐龍 }, };
然后來看下 Trex 原型鏈上的方法。我們首先來繪制靜態(tài)的小恐龍:
Trex.prototype = { // 初始化小恐龍 init: function() { // 獲取小恐龍站在地面上時的 y 坐標 this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT - Runner.config.BOTTOM_PAD; this.yPos = this.groundYPos; // 小恐龍的 y 坐標初始化 this.draw(0, 0); // 繪制小恐龍的第一幀圖片 }, /** * 繪制小恐龍 * @param {Number} x 當前幀相對于第一幀的 x 坐標 * @param {Number} y 當前幀相對于第一幀的 y 坐標 */ draw: function(x, y) { // 在雪碧圖中的坐標 var sourceX = x + this.spritePos.x; var sourceY = y + this.spritePos.y; // 在雪碧圖中的寬高 var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ? this.config.WIDTH_DUCK : this.config.WIDTH; var sourceHeight = this.config.HEIGHT; // 繪制到 canvas 上時的高度 var outputHeight = sourceHeight; // 躲避狀態(tài). if (this.ducking && this.status != Trex.status.CRASHED) { this.ctx.drawImage( Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight, this.xPos, this.yPos, this.config.WIDTH_DUCK, outputHeight ); } else { // 躲閃狀態(tài)下撞到障礙物 if (this.ducking && this.status == Trex.status.CRASHED) { this.xPos++; } // 奔跑狀態(tài) this.ctx.drawImage( Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight, this.xPos, this.yPos, this.config.WIDTH, outputHeight ); } this.ctx.globalAlpha = 1; }, };
前面進入街機模式那一章中,用到了 Trex 類中的數(shù)據(jù),臨時定義了 Trex 類,別忘了將其刪除。
接下來需要通過 Runner 類調(diào)用 Trex 類。添加屬性用于存儲小恐龍類的實例:
function Runner(containerSelector, opt_config) { // ... + this.tRex = null; // 小恐龍 }
初始化小恐龍類:
Runner.prototype = { init: function () { // ... + // 加載小恐龍類 + this.tRex = new Trex(this.canvas, this.spriteDef.TREX); }, };
這樣在游戲初始化時就繪制出了靜態(tài)的小恐龍,如圖:
實現(xiàn)眨眼效果游戲初始化之后,小恐龍會隨機眨眼睛。默認的是最多只能眨三次。下面將實現(xiàn)這個效果。
添加更新小恐龍的方法:
Trex.prototype = { /** * 更新小恐龍 * @param {Number} deltaTime 間隔時間 * @param {String} opt_status 小恐龍的狀態(tài) */ update: function(deltaTime, opt_status) { this.timer += deltaTime; // 更新狀態(tài)的參數(shù) if (opt_status) { this.status = opt_status; this.currentFrame = 0; this.msPerFrame = Trex.animFrames[opt_status].msPerFrame; this.currentAnimFrames = Trex.animFrames[opt_status].frames; if (opt_status == Trex.status.WAITING) { this.animStartTime = getTimeStamp(); // 設(shè)置眨眼動畫開始的時間 this.setBlinkDelay(); // 設(shè)置眨眼間隔的時間 } } if (this.status == Trex.status.WAITING) { // 小恐龍眨眼 this.blink(getTimeStamp()); } else { // 繪制動畫幀 this.draw(this.currentAnimFrames[this.currentFrame], 0); } if (this.timer >= this.msPerFrame) { // 更新當前動畫幀,如果處于最后一幀就更新為第一幀,否則更新為下一幀 this.currentFrame = this.currentFrame == this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1; // 重置計時器 this.timer = 0; } }, // 設(shè)置眨眼間隔的時間 setBlinkDelay: function() { this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING); }, // 小恐龍眨眼 blink: function (time) { var deltaTime = time - this.animStartTime; // 間隔時間大于隨機獲取的眨眼間隔時間才能眨眼 if (deltaTime >= this.blinkDelay) { this.draw(this.currentAnimFrames[this.currentFrame], 0); // 正在眨眼 if (this.currentFrame == 1) { console.log("眨眼"); this.setBlinkDelay(); // 重新設(shè)置眨眼間隔的時間 this.animStartTime = time; // 更新眨眼動畫開始的時間 this.blinkCount++; // 眨眼次數(shù)加一 } } }, };
然后將小恐龍初始更新為等待狀態(tài):
Trex.prototype = { init: function () { // ... this.update(0, Trex.status.WAITING); // 初始為等待狀態(tài) }, };
最后在 Runner 的 update 方法中調(diào)用 Trex 的 update 方法來實現(xiàn)小恐龍眨眼:
Runner.prototype = { update: function () { // ... // 游戲變?yōu)殚_始狀態(tài)或小恐龍還沒有眨三次眼 - if (this.playing) { + if (this.playing || (!this.activated && + this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) { + this.tRex.update(deltaTime); // 進行下一次更新 this.scheduleNextUpdate(); } }, };
效果如下:
可以看到,眨眼的代碼邏輯觸發(fā)了 3 次,但是實際小恐龍只眨眼了 1 次。這就是前面說的,小恐龍默認最多只能眨三次眼。具體原因如下:
先來看下 Trex 的 update 方法中的這段代碼:
if (this.timer >= this.msPerFrame) { // 更新當前動畫幀,如果處于最后一幀就更新為第一幀,否則更新為下一幀 this.currentFrame = this.currentFrame == this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1; // 重置計時器 this.timer = 0; }
這段代碼會將當前動畫幀不斷更新為下一幀。對于小恐龍來說就是不斷切換睜眼和閉眼這兩幀。如果當前幀為 “睜眼”,那么執(zhí)行 blink 函數(shù)后小恐龍還是睜眼,也就是說實際小恐龍沒眨眼;同理,只有當前幀為 “閉眼” 時,執(zhí)行 blink 函數(shù)后,小恐龍才會真正的眨眼。
至于這樣做的目的,就是為了防止小恐龍不停的眨眼睛。例如,將 blink 函數(shù)修改為:
// 小恐龍眨眼 blink: function () { this.draw(this.currentAnimFrames[this.currentFrame], 0); },
這樣小恐龍會不停的眨眼睛。所以需要對其進行限制,這里 Chrome 開發(fā)人員的做法就是:設(shè)置一個間隔時間,當小恐龍眨眼的間隔時間大于這個設(shè)置的間隔時間,并且當前動畫幀為 “閉眼” 時,才允許小恐龍眨眼睛。然后每次眨完眼后,重新設(shè)置眨眼間隔(默認設(shè)置為 0~7 秒),就實現(xiàn)了小恐龍的隨機眨眼。
小恐龍的開場動畫下面來實現(xiàn)小恐龍對鍵盤按鍵的響應(yīng)。
首先,當觸發(fā)游戲彩蛋后,小恐龍會跳躍一次,并向右移動 50 像素(默認設(shè)置的是 50 像素)。
添加讓小恐龍開始跳躍的方法:
Trex.prototype = { // 開始跳躍 startJump: function(speed) { if (!this.jumping) { // 更新小恐龍為跳躍狀態(tài) this.update(0, Trex.status.JUMPING); // 根據(jù)游戲的速度調(diào)整跳躍的速度 this.jumpVelocity = this.config.INITIAL_JUMP_VELOCITY - (speed / 10); this.jumping = true; this.reachedMinHeight = false; this.speedDrop = false; } }, };
進行調(diào)用:
Runner.prototype = { onKeyDown: function (e) { if (!this.crashed && !this.paused) { if (Runner.keyCodes.JUMP[e.keyCode]) { e.preventDefault(); // ... + // 開始跳躍 + if (!this.tRex.jumping && !this.tRex.ducking) { + this.tRex.startJump(this.currentSpeed); + } } } }, };
這樣,按下空格鍵后,小恐龍仍然會靜止在地面上。接下來還需要更新動畫幀才能實現(xiàn)小恐龍的奔跑動畫。
添加更新小恐龍動畫幀的方法:
Trex.prototype = { // 更新小恐龍?zhí)S時的動畫幀 updateJump: function(deltaTime) { var msPerFrame = Trex.animFrames[this.status].msPerFrame; // 獲取當前狀態(tài)的幀率 var framesElapsed = deltaTime / msPerFrame; // 加速下落 if (this.speedDrop) { this.yPos += Math.round(this.jumpVelocity * this.config.SPEED_DROP_COEFFICIENT * framesElapsed); } else { this.yPos += Math.round(this.jumpVelocity * framesElapsed); } // 跳躍的速度受重力的影響,向上逐漸減小,然后反向 this.jumpVelocity += this.config.GRAVITY * framesElapsed; // 達到了最低允許的跳躍高度 if (this.yPos < this.minJumpHeight || this.speedDrop) { this.reachedMinHeight = true; } // 達到了最高允許的跳躍高度 if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) { this.endJump(); // 結(jié)束跳躍 } // 重新回到地面,跳躍完成 if (this.yPos > this.groundYPos) { this.reset(); // 重置小恐龍的狀態(tài) this.jumpCount++; // 跳躍次數(shù)加一 } }, // 跳躍結(jié)束 endJump: function() { if (this.reachedMinHeight && this.jumpVelocity < this.config.DROP_VELOCITY) { this.jumpVelocity = this.config.DROP_VELOCITY; // 下落速度重置為默認 } }, // 重置小恐龍狀態(tài) reset: function() { this.yPos = this.groundYPos; this.jumpVelocity = 0; this.jumping = false; this.ducking = false; this.update(0, Trex.status.RUNNING); this.speedDrop = false; this.jumpCount = 0; }, };
其中 minJumpHeight 的屬性值為:
Trex.prototype = { init: function() { + // 最低跳躍高度 + this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT; // ... }, }
然后進行調(diào)用:
Runner.prototype = { update: function () { // ... if (this.playing) { this.clearCanvas(); + if (this.tRex.jumping) { + this.tRex.updateJump(deltaTime); + } this.runningTime += deltaTime; var hasObstacles = this.runningTime > this.config.CLEAR_TIME; // 剛開始 this.playingIntro 未定義 !this.playingIntro 為真 - if (!this.playingIntro) { + if (this.tRex.jumpCount == 1 && !this.playingIntro) { this.playIntro(); // 執(zhí)行開場動畫 } // ... } // ... }, };
這樣在按下空格鍵后,小恐龍就會跳躍一次并進行奔跑動畫。如圖:
下面來實現(xiàn)效果:小恐龍第一次跳躍后,向右移動 50 像素。
修改 Trex 的 update 方法。當判斷到正在執(zhí)行開場動畫時,移動小恐龍:
Trex.prototype = { update: function(deltaTime, opt_status) { this.timer += deltaTime; // 更新狀態(tài)的參數(shù) if (opt_status) { // ... } // 正在執(zhí)行開場動畫,將小恐龍向右移動 50 像素 + if (this.playingIntro && this.xPos < this.config.START_X_POS) { + this.xPos += Math.round((this.config.START_X_POS / + this.config.INTRO_DURATION) * deltaTime); + } // ... }, };
可以看出當 playingIntro 屬性為 true 時,小恐龍就會向右移動。所以需要通過控制這個屬性的值來控制小恐龍第一次跳躍后的移動。
修改 Runner 上的 playIntro 方法,將小恐龍標記為正在執(zhí)行開場動畫:
Runner.prototype = { playIntro: function () { if (!this.activated && !this.crashed) { + this.tRex.playingIntro = true; // 小恐龍執(zhí)行開場動畫 // ... } }, };
然后需要在開始游戲后也就是執(zhí)行 startGame 方法時,結(jié)束小恐龍的開場動畫:
Runner.prototype = { startGame: function () { this.setArcadeMode(); // 進入街機模式 + this.tRex.playingIntro = false; // 小恐龍的開場動畫結(jié)束 // ... }, };
效果如下:
可以很明顯的看到,小恐龍在第一次跳躍后向右移動了一段距離(默認 50 像素)。
使用鍵盤控制小恐龍在這個游戲中,當按下 ↓ 鍵后,如果小恐龍正在跳躍,就會快速下落,如果小恐龍在地上,就會進入躲閃狀態(tài),下面來實現(xiàn)這些效果。
加速下落:
Trex.prototype = { // 設(shè)置小恐龍為加速下落,立即取消當前的跳躍 setSpeedDrop: function() { this.speedDrop = true; this.jumpVelocity = 1; }, };
設(shè)置小恐龍是否躲閃:
Trex.prototype = { // 設(shè)置小恐龍奔跑時是否躲閃 setDuck: function(isDucking) { if (isDucking && this.status != Trex.status.DUCKING) { // 躲閃狀態(tài) this.update(0, Trex.status.DUCKING); this.ducking = true; } else if (this.status == Trex.status.DUCKING) { // 奔跑狀態(tài) this.update(0, Trex.status.RUNNING); this.ducking = false; } }, };
在 onKeyDown 方法中調(diào)用:
Runner.prototype = { onKeyDown: function () { if (!this.crashed && !this.paused) { if (Runner.keyCodes.JUMP[e.keyCode]) { // ... + } else if (this.playing && Runner.keyCodes.DUCK[e.keyCode]) { + e.preventDefault(); + + if (this.tRex.jumping) { + this.tRex.setSpeedDrop(); // 加速下落 + } else if (!this.tRex.jumping && !this.tRex.ducking) { + this.tRex.setDuck(true); // 進入躲閃狀態(tài) + } + } } }, };
這樣就實現(xiàn)了前面所說的效果。但是小恐龍進入躲閃狀態(tài)后,如果松開按鍵并不會重新站起來。因為現(xiàn)在還沒有定義松開鍵盤按鍵時響應(yīng)的事件。下面來定義:
Runner.prototype = { onKeyUp: function(e) { var keyCode = String(e.keyCode); if (Runner.keyCodes.DUCK[keyCode]) { // 躲避狀態(tài) this.tRex.speedDrop = false; this.tRex.setDuck(false); } }, };
然后調(diào)用,修改 handleEvent 方法:
Runner.prototype = { handleEvent: function (e) { return (function (eType, events) { switch (eType) { // ... + case events.KEYUP: + this.onKeyUp(e); + break; default: break; } }.bind(this))(e.type, Runner.events); }, };
效果如下:
第一次跳是正常下落,第二次跳是加速下落處理小恐龍的跳躍
小恐龍的跳躍分為大跳和小跳,如圖:
要實現(xiàn)這個效果,只需要在 ↑ 鍵被松開時,立即結(jié)束小恐龍的跳躍即可。
修改 onKeyUp 方法:
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]) { // 躲避狀態(tài) this.tRex.speedDrop = false; this.tRex.setDuck(false); } }, };
其中 isRunning 方法定義如下:
Runner.prototype = { // 是否游戲正在進行 isRunning: function() { return !!this.raqId; }, };
這樣就實現(xiàn)了小恐龍的大跳和小跳。
最后是要實現(xiàn)的效果是:如果頁面失焦時,小恐龍正在跳躍,就重置小恐龍的狀態(tài)(也就是會立即回到地面上)。這個效果實現(xiàn)很簡單,直接調(diào)用前面定義的 reset 方法即可:
Runner.prototype = { play: function () { if (!this.crashed) { // ... + this.tRex.reset(); } }, };
效果如下:
查看添加或修改的代碼,戳這里
Demo 體驗地址:https://liuyib.github.io/blog/demo/game/google-dino/dino-gogogo/
上一篇 | 下一篇 | Chrome 小恐龍游戲源碼探究七 -- 晝夜模式交替 | Chrome 小恐龍游戲源碼探究九 -- 游戲碰撞檢測 |
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/103899.html
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究八奔跑的小恐龍實現(xiàn)了小恐龍的繪制以及鍵盤對小恐龍的控制,這一篇文章中將實現(xiàn)游戲的碰撞檢測。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究八 -- 奔跑的小恐龍》實現(xiàn)了小恐龍的繪制以及鍵盤對小恐龍的控制,這一篇文章中將實現(xiàn)游戲的碰撞檢測。 碰撞檢測原理 這個游戲采用的檢測方法是盒子碰撞,這種檢...
摘要:首先是繪制靜態(tài)的地面。上一篇下一篇無小恐龍游戲源碼探究二讓地面動起來 文章首發(fā)于我的 GitHub 博客 目錄 Chrome 小恐龍游戲源碼探究一 -- 繪制靜態(tài)地面 Chrome 小恐龍游戲源碼探究二 -- 讓地面動起來 Chrome 小恐龍游戲源碼探究三 -- 進入街機模式 Chrome 小恐龍游戲源碼探究四 -- 隨機繪制云朵 Chrome 小恐龍游戲源碼探究五 -- 隨機繪...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究六記錄游戲分數(shù)實現(xiàn)了游戲分數(shù)最高分數(shù)的記錄和繪制。這一篇文章中將實現(xiàn)晝夜模式交替的的效果。原來的游戲中,晝夜交替每米觸發(fā)一次,這里為了演示,改成了米觸發(fā)一次。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究六 -- 記錄游戲分數(shù)》實現(xiàn)了游戲分數(shù)、最高分數(shù)的記錄和繪制。這一篇文章中將實現(xiàn)晝夜模式...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究二讓地面動起來實現(xiàn)了地面的移動。街機模式的效果就是游戲開始后,進入全屏模式。例如可以看到,進入街機模式之前,有一段開場動畫。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究二 -- 讓地面動起來》 實現(xiàn)了地面的移動。這一篇文章中,將實現(xiàn)效果:1、瀏覽器失焦時游戲暫停,聚焦游戲繼續(xù)。 2、開場動...
摘要:文章首發(fā)于我的博客前言上一篇文章小恐龍游戲源碼探究三進入街機模式實現(xiàn)了開場動畫和街機模式。 文章首發(fā)于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龍游戲源碼探究三 -- 進入街機模式》 實現(xiàn)了開場動畫和街機模式。這一篇文章中,將實現(xiàn)云朵的隨機繪制。 云朵類 Cloud 定義云朵類 Cloud: /** * 云朵類 * @param {HTMLCanvasEle...
閱讀 2329·2021-09-30 09:47
閱讀 2949·2019-08-30 11:05
閱讀 2526·2019-08-29 17:20
閱讀 1912·2019-08-29 13:01
閱讀 1721·2019-08-26 13:39
閱讀 1221·2019-08-26 13:26
閱讀 3204·2019-08-23 18:40
閱讀 1810·2019-08-23 17:09