Chrome Dinosaur Game Source Exploration III - Enter the Arcade Mode

Keywords: Javascript github Google

Preface

Last article:《 Chrome Dinosaur Game Source Exploration II - Let the Ground Move 》 The movement of the ground is realized. In this article, the effect will be achieved: 1. When the browser is out of focus, the game will be paused and the focused game will continue. 2. Opening animation. 3. Enter arcade mode.

The effect of arcade mode is to enter full screen mode after the game starts. For example:

As you can see, there is an opening animation before entering arcade mode. Let's first implement this opening animation.

Here, we only realize the opening animation of the ground, and then the small dinosaur.

Realizing Opening Animation

First, modify the CSS style:

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

Let canvas initially display only 44px width.

Then add the method on the prototype chain of Runner:

Runner.prototype = {
  /**
   * Opening animation when the game is activated
   * Maximize the width of canvas
   */
  playIntro: function () {
    if (!this.activated && !this.crashed) {
      this.playingIntro = true; // Executing opening animation

      // Define CSS animation key frames
      var keyframes = '@-webkit-keyframes intro { ' +
          'from { width:' + Trex.config.WIDTH + 'px }' +
          'to { width: ' + this.dimensions.WIDTH + 'px }' +
        '}';
      // Insert animation key frames into the first style sheet on the page
      document.styleSheets[0].insertRule(keyframes, 0);

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

      // Monitor animation. When the end event is triggered, the game is set to the start state
      this.containerEl.addEventListener(Runner.events.ANIMATION_END,
        this.startGame.bind(this));

      this.setPlayStatus(true); // Set the game as in progress
      this.activated = true;    // Game Eggs Activated
    } else if (this.crashed) {
      // The logic of this restart method is not implemented here
      this.restart();
    }
  },
  /**
   * Update the game to start state
   */
  startGame: function () {
    this.playingIntro = false; // Opening Animation End
    this.containerEl.style.webkitAnimation = '';
  },
};

Some additional data are needed:

Runner.events = {
  // ...

+ ANIMATION_END: 'webkitAnimationEnd',
};

Here we use the data from the small dinosaurs. Let's define it temporarily.

function Trex() {}

Trex.config = {
  WIDTH: 44,
};

Then the playIntro method defined above is called in Runner's update method:

Runner.prototype = {
  /**
   * Update Game Frames and Next Update
   */
  update: function () {
    // ...

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

      // At first, this. playing Intro is undefined! This. playing Intro is true
+     if (!this.playingIntro) {
+       this.playIntro(); // Execute opening animation
+     }

      // Move the ground until the opening animation is over
+     if (this.playingIntro) {
+       this.horizon.update(0, this.currentSpeed);
+     } else {
+       deltaTime = !this.activated ? 0 : deltaTime;
        this.horizon.update(deltaTime, this.currentSpeed);
+     }
    }

    // ...
  },
};

Explain that in the code above:

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

When the program goes if logic, the first parameter received by this.horizon.update is 0, so the displacement calculated within this method is 0. So as long as the opening animation is still being performed, the ground will not move. When the program takes the else logic, the opening animation is finished, and the playIntro function is finished, the value of this. activate is true, the value of deltaTime is greater than zero, and the displacement of the ground is also there.

So far, the ground opening animation has been realized:

Look at the added code. Poke here

Listen for window blur, focus events

The next effect is to pause the game when the browser window is out of focus and continue the game when it is focused.

Add a method to the Runner prototype chain:

Runner.prototype = {
  /**
   * Pause the game when the page is out of focus
   */
  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;
  },
};

In the startGame method, we add the monitoring of blur and focus events:

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

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

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

Supplementary data:

Runner.events = {
  // ...

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

The results are as follows:

Look at the added code. Poke here

Realizing Arcade Mode

Add a method to the Runner prototype chain:

Runner.prototype = {
  /**
   * Set the scaling ratio of canvas containers when entering arcade mode
   */
  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;

    // Fill the screen horizontally with canvas at 10% of the top window height
    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)';
  },
  /**
   * Open arcade mode (full screen)
   */
  setArcadeMode: function () {
    document.body.classList.add(Runner.classes.ARCADE_MODE);
    this.setArcadeModeContainerScale();
  },
};

Supplementary data:

Runner.config = {
  // ...
  
+ ARCADE_MODE_INITIAL_TOP_POSITION: 35,  // In arcade mode, the initial distance between canvas and the top
+ ARCADE_MODE_TOP_POSITION_PERCENT: 0.1, // In arcade mode, the distance between canvas and the top of the page as a percentage of the height of the screen
};

Runner.classes = {
  // ...

+ ARCADE_MODE: 'arcade-mode',
};

Define the style in the CSS class 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;
}

Modify the startGame method on Runner to call the setArcadeMode method:

Runner.prototype = {
  startGame: function () {
+   this.setArcadeMode();      // Enter arcade mode

    // ...
  },
};

Arriving here, the arcade mode is realized, and the effect is as follows:

Look at the added code. Poke here

Demo Experience Address: https://liuyib.github.io/pages/demo/games/google-dino/arcade-mode/

Last article Next article
Chrome Dinosaur Game Source Exploration II - Let the Ground Move Chrome Dinosaur Game Source Exploration IV - Random Drawing Clouds

Posted by bad_gui on Sat, 27 Apr 2019 13:54:36 -0700