Chrome 小恐龍遊戲源碼探究二 -- 讓地面動起來

文章首發於個人 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();
}

最後在 Runnerinit 方法中調用它的 update 方法:函數

Runner.prototype = {
  init: function () {
    // ...

+   // 更新 canvas
+   this.update();
  },
};

因爲類層層抽象的緣由,方法的也須要層層調用。this

如今地面就能夠進行無限滾動了,效果以下:google

clipboard.png

你能夠經過查看個人 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',
};

而後在 Runnerinit 方法中調用 startListening 方法,來監聽鍵盤的 keydownkeyup 事件:

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 事件的解釋。

到這裏,就實現了地面對空格鍵的響應,效果以下:

clipboard.png

查看添加或修改的代碼, 戳這裏

Demo 體驗地址:https://liuyib.github.io/blog/demo/game/google-dino/horizonline-move/

上一篇 下一篇
Chrome 小恐龍遊戲源碼探究一 -- 繪製靜態地面 Chrome 小恐龍遊戲源碼探究三 -- 進入街機模式
相關文章
相關標籤/搜索