Chrome 小恐龍遊戲源碼探究三 -- 進入街機模式

文章首發於個人 GitHub 博客

前言

上一篇文章:《Chrome 小恐龍遊戲源碼探究二 -- 讓地面動起來》 實現了地面的移動。這一篇文章中,將實現效果:一、瀏覽器失焦時遊戲暫停,聚焦遊戲繼續。 二、開場動畫。 三、進入街機模式。css

街機模式的效果就是:遊戲開始後,進入全屏模式。例如:git

clipboard.png

能夠看到,進入街機模式以前,有一段開場動畫。咱們先來實現一下這個開場動畫。github

這裏先只實現地面的開場動畫,小恐龍的後續再去實現。

實現開場動畫

首先修改 CSS 樣式:web

.offline .runner-container {
  position: absolute;
  top: 35px;
- width: 100%;
+ width: 44px;
  max-width: 600px;
  height: 150px;
  overflow: hidden;
}

canvas 初始只顯示 44px 的寬度。canvas

而後在 Runner 的原型鏈上添加方法:segmentfault

Runner.prototype = {
  // 遊戲被激活時的開場動畫
  // 將 canvas 的寬度調整到最大
  playIntro: function () {
    if (!this.activated && !this.crashed) {
      this.playingIntro = true; // 正在執行開場動畫

      // 定義 CSS 動畫關鍵幀
      var keyframes = '@-webkit-keyframes intro { ' +
          'from { width:' + Trex.config.WIDTH + 'px }' +
          'to { width: ' + this.dimensions.WIDTH + 'px }' +
        '}';
      // 將動畫關鍵幀插入頁面中的第一個樣式表
      document.styleSheets[0].insertRule(keyframes, 0);

      this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
      this.containerEl.style.width = this.dimensions.WIDTH + 'px';

      // 監聽動畫。當觸發結束事件時,設置遊戲爲開始狀態
      this.containerEl.addEventListener(Runner.events.ANIMATION_END,
        this.startGame.bind(this));

      this.setPlayStatus(true); // 設置遊戲爲進行狀態
      this.activated = true;    // 遊戲彩蛋被激活
    } else if (this.crashed) {
      // 這個 restart 方法的邏輯這裏先不實現
      this.restart();
    }
  },
  // 設置遊戲爲開始狀態
  startGame: function () {
    this.playingIntro = false; // 開場動畫結束
    this.containerEl.style.webkitAnimation = '';
  },
};

補充數據:瀏覽器

Runner.events = {
  // ...

+ ANIMATION_END: 'webkitAnimationEnd',
};

這裏用到了小恐龍類裏的數據,咱們先臨時定義一下(後面講到小恐龍那一章時,須要把這段臨時代碼刪除):函數

function Trex() {}

Trex.config = {
  WIDTH: 44,
};

而後在 Runnerupdate 方法中調用上面定義的 playIntro 方法:動畫

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

    if (this.playing) {
      this.clearCanvas();

+     // 剛開始 this.playingIntro 未定義 !this.playingIntro 爲真
+     if (!this.playingIntro) {
+       this.playIntro(); // 執行開場動畫
+     }

+     // 直到開場動畫結束再移動地面
+     if (this.playingIntro) {
+       this.horizon.update(0, this.currentSpeed);
+     } else {
+       deltaTime = !this.activated ? 0 : deltaTime;
        this.horizon.update(deltaTime, this.currentSpeed);
+     }
    }

    // ...
  },
};

解釋一下這段代碼:this

if (this.playingIntro) {
  this.horizon.update(0, this.currentSpeed);
} else {
  deltaTime = !this.activated ? 0 : deltaTime;
  this.horizon.update(deltaTime, this.currentSpeed);
}

當程序走 if 邏輯的時候,this.horizon.update 接收到的第一個參數爲 0,這樣在這個方法內部計算出來的位移也是 0。因此只要還在執行開場動畫,地面就不會移動。當程序走 else 邏輯的時候,開場動畫執行完畢,此時 playIntro 函數已經執行結束,this.activated 值爲 truedeltaTime 值大於零,計算出的地面位移就再也不爲 0

這樣,就實現了地面的開場動畫:

clipboard.png

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

監聽窗口 blur、focus 事件

接下來要實現的效果是:瀏覽器窗口失焦時遊戲暫停,聚焦時遊戲繼續。

Runner 原型鏈上添加方法,來判斷瀏覽器窗口是否失焦:

Runner.prototype = {
  // 當頁面失焦時,暫停遊戲,不然進行遊戲
  onVisibilityChange: function (e) {
    if (document.hidden || document.webkitHidden || e.type == 'blur' ||
      document.visibilityState != 'visible') {
      this.stop();
    } else if (!this.crashed) {
      this.play();
    }
  },
  play: function () {
    if (!this.crashed) {
      this.setPlayStatus(true);
      this.paused = false;
      this.time = getTimeStamp();
      this.update();
    }
  },
  stop: function () {
    this.setPlayStatus(false);
    this.paused = true;
    cancelAnimationFrame(this.raqId);
    this.raqId = 0;
  },
};

startGame 方法中添加對 blur、focus 事件的監聽:

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

+   window.addEventListener(Runner.events.BLUR,
+     this.onVisibilityChange.bind(this));

+   window.addEventListener(Runner.events.FOCUS,
+     this.onVisibilityChange.bind(this));
  },
};

補充數據:

Runner.events = {
  // ...

+ BLUR: "blur",
+ FOCUS: "focus"
};

效果以下:

clipboard.png

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

實現街機模式

Runner 原型鏈上添加方法:

Runner.prototype = {
  // 設置進入街機模式時 canvas 容器的縮放比例
  setArcadeModeContainerScale: function () {
    var windowHeight = window.innerHeight;
    var scaleHeight = windowHeight / this.dimensions.HEIGHT;
    var scaleWidth = window.innerWidth / this.dimensions.WIDTH;
    var scale = Math.max(1, Math.min(scaleHeight, scaleWidth));
    var scaledCanvasHeight = this.dimensions.HEIGHT * scale;

    // 將 canvas 橫向佔滿屏幕,縱向距離頂部 10% 窗口高度處
    var translateY = Math.ceil(Math.max(0, (windowHeight - scaledCanvasHeight -
        Runner.config.ARCADE_MODE_INITIAL_TOP_POSITION) *
        Runner.config.ARCADE_MODE_TOP_POSITION_PERCENT)) *
        window.devicePixelRatio;
    this.containerEl.style.transform = 'scale(' + scale + ') translateY(' +
        translateY + 'px)';
  },
  // 開啓街機模式(全屏)
  setArcadeMode: function () {
    document.body.classList.add(Runner.classes.ARCADE_MODE);
    this.setArcadeModeContainerScale();
  },
};

補充數據:

Runner.config = {
  // ...
  
+ ARCADE_MODE_INITIAL_TOP_POSITION: 35,  // 街機模式時,canvas 距頂部的初始距離
+ ARCADE_MODE_TOP_POSITION_PERCENT: 0.1, // 街機模式時,canvas 距頁面頂部的距離,佔屏幕高度的百分比
};

Runner.classes = {
  // ...

+ ARCADE_MODE: 'arcade-mode',
};

定義 CSS 類 arcade-mode 裏的樣式:

.arcade-mode,
.arcade-mode .runner-container,
.arcade-mode .runner-canvas {
  image-rendering: pixelated;
  max-width: 100%;
  overflow: hidden;
}

.arcade-mode .runner-container {
  left: 0;
  right: 0;
  margin: auto;
  transform-origin: top center;
  transition: transform 250ms cubic-bezier(0.4, 0.0, 1, 1) .4s;
  z-index: 2;
}

最後調用 setArcadeMode 方法,就能夠進入街機模式:

Runner.prototype = {
  startGame: function () {
+   this.setArcadeMode();      // 進入街機模式

    // ...
  },
};

效果以下:

clipboard.png

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

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

上一篇 下一篇
Chrome 小恐龍遊戲源碼探究二 -- 讓地面動起來 Chrome 小恐龍遊戲源碼探究四 -- 隨機繪製雲朵
相關文章
相關標籤/搜索