文章首發於個人 GitHub 博客
上一篇文章:《Chrome 小恐龍遊戲源碼探究一 -- 繪製靜態地面》 中定義了遊戲的主體類 Runner,並實現了靜態地面的繪製。這一篇文章中,將實現效果:一、地面無限滾動。二、剛開始地面不動,按下空格後地面滾動。git
要實現地面的移動就要不斷更新地面的 x
座標。定義以下方法:github
HorizonLine.prototype = { /** * 更新地面的 x 座標 * @param {Number} pos 地面的位置 * @param {Number} incre 移動距離 */ updateXPos: function (pos, incre) { var line1 = pos; var line2 = pos === 0 ? 1 : 0; // 第一段地面向左移動,第二段地面隨之 this.xPos[line1] -= incre; this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH; // 第一段地面移出了 canvas if (this.xPos[line1] <= -this.dimensions.WIDTH) { // 將第一段地面放到 canvas 右側 this.xPos[line1] += this.dimensions.WIDTH * 2; // 此時第二段地面的 x 座標恰好和 canvas 的 x 座標對齊 this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH; // 給放到 canvas 後面的地面隨機地形 this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x; } }, // 獲取隨機的地形 getRandomType: function () { return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0; }, };
其中 updateXPos
實現了地面 x
座標的更新。當第一段地面移出 canvas
時,會將第一段地面 x
座標乘 2
放到 canvas
後面,而後爲其隨機地形。這時,原來的第二段地面就變成了第一段地面繼續向前移動,以此類推。這樣就實現了地面的不斷移動和更新。canvas
上面的函數只是實現了地面移動相關的邏輯,真正讓地面動起來還須要調用這個函數。添加方法:segmentfault
HorizonLine.prototype = { /** * 更新地面 * @param {Number} deltaTime 間隔時間 * @param {Number} speed 速度 */ update: function (deltaTime, speed) { // 計算地面每次移動的距離(距離 = 速度 x 時間)時間由幀率和間隔時間共同決定 var incre = Math.floor(speed * (FPS / 1000) * deltaTime); if (this.xPos[0] <= 0) { this.updateXPos(0, incre); } else { this.updateXPos(1, incre); } this.draw(); }, };
而後須要經過 Horizon
上的 update
方法來調用 HorizonLine
上的 update
方法:瀏覽器
Horizon.prototype = { // 更新背景 update: function (deltaTime, currentSpeed) { this.horizonLine.update(deltaTime, currentSpeed); }, };
同理,按照上面的邏輯,如今應該在 Runner
上定義 update
方法來調用 Horizon
上的 update
方法:dom
Runner.prototype = { // 更新遊戲幀並進行下一次更新 update: function () { this.updatePending = false; // 等待更新 var now = getTimeStamp(); var deltaTime = now - (this.time || now); this.time = now; this.clearCanvas(); this.horizon.update(deltaTime, this.currentSpeed); // 進行下一次更新 this.scheduleNextUpdate(); }, // 清空 canvas clearCanvas: function () { this.ctx.clearRect(0, 0, this.dimensions.WIDTH, this.dimensions.HEIGHT); }, // 進行下一次更新 scheduleNextUpdate: function () { if (!this.updatePending) { this.updatePending = true; this.raqId = requestAnimationFrame(this.update.bind(this)); } }, }; // 獲取時間戳 function getTimeStamp() { return performance.now(); }
最後在 Runner
的 init
方法中調用它的 update
方法:函數
Runner.prototype = { init: function () { // ... + // 更新 canvas + this.update(); }, };
因爲類層層抽象的緣由,方法的也須要層層調用。this
如今地面就能夠進行無限滾動了,效果以下:google
你能夠經過查看個人
commit
信息,來查看添加或修改的代碼:
戳這裏
下面來實現地面對空格鍵的響應,具體效果就是,初始地面不動,按下空格鍵後地面移動。spa
修改 Runner
原型鏈中的 update
方法:
Runner.prototype = { update: function () { var now = getTimeStamp(); var deltaTime = now - (this.time || now); this.time = now; + if (this.playing) { this.clearCanvas(); this.horizon.update(deltaTime, this.currentSpeed); + } + if (this.playing) { // 進行下一次更新 this.scheduleNextUpdate(); + } }, };
監聽鍵盤事件:
Runner.prototype = { startListening: function () { document.addEventListener(Runner.events.KEYDOWN, this); document.addEventListener(Runner.events.KEYUP, this); }, stopListening: function () { document.removeEventListener(Runner.events.KEYDOWN, this); document.removeEventListener(Runner.events.KEYUP, this); }, };
添加數據:
Runner.events = { // ... + KEYDOWN: 'keydown', + KEYUP: 'keyup', };
而後在 Runner
的 init
方法中調用 startListening
方法,來監聽鍵盤的 keydown
和 keyup
事件:
Runner.prototype = { init: function () { // ... + // 開始監聽用戶動做 + this.startListening(); }, };
當瀏覽器監聽到用戶按下鍵盤時,會執行 handleEvent
方法來處理鍵盤事件:
Runner.prototype = { // 用來處理 EventTarget(這裏就是 Runner 類) 上發生的事件 // 當事件被髮送到 EventListener 時,瀏覽器就會自動調用這個方法 handleEvent: function (e) { return (function (eType, events) { switch (eType) { case events.KEYDOWN: this.onKeyDown(e); break; default: break; } }.bind(this))(e.type, Runner.events); }, };
上面用到的 onKeyDown
方法定義以下:
Runner.prototype = { onKeyDown: function (e) { if (!this.crashed && !this.paused) { if (Runner.keyCodes.JUMP[e.keyCode]) { e.preventDefault(); if (!this.playing) { this.setPlayStatus(true); this.update(); } } } }, // 設置遊戲是否爲進行狀態 setPlayStatus: function (isPlaying) { this.playing = isPlaying; }, };
這裏須要提一下,當按下空格鍵後, 爲何瀏覽器會執行 handleEvent 事件:緣由是當使用 addEventListener
監聽某個對象上的事件時,只要被監聽的事件觸發了,就會執行該對象上名字爲 handleEvent
的方法(若是有)。MDN 上有對 handleEvent
事件的解釋。
到這裏,就實現了地面對空格鍵的響應,效果以下:
查看添加或修改的代碼, 戳這裏
Demo 體驗地址:https://liuyib.github.io/blog/demo/game/google-dino/horizonline-move/
上一篇 | 下一篇 |
Chrome 小恐龍遊戲源碼探究一 -- 繪製靜態地面 | Chrome 小恐龍遊戲源碼探究三 -- 進入街機模式 |