Exploration of the Game Source of Chrome Little Dinosaurs Eight--Running Little Dinosaurs

Keywords: Javascript Attribute github Google

The article started with me Personal Blog

Preface

Last article: ' Chrome Dinosaur Game Source Exploration Seven--Alternating Day and Night Modes In this article, the following will be achieved: 1. Drawing of small dinosaurs 2. Keyboard control of small dinosaurs 3. Page defocusing will reset the state of small dinosaurs

Draw static Dinosaurs

Define small dinosaurs:

/**
 * Small Dinosaurs
 * @param {HTMLCanvasElement} canvas canvas
 * @param {Object} spritePos Picture coordinates in Sprite
 */
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-coordinate of a small dinosaur on the ground

  this.currentFrame = 0;             // Current animation frame
  this.currentAnimFrames = [];       // Stores the x-coordinate of the animated frame in the current state on the Sprite chart
  this.blinkDelay = 0;               // Time between blinks (random)
  this.blinkCount = 0;               // blink response
  this.animStartTime = 0;            // Start time of the little dinosaur blink animation
  this.timer = 0;                    // timer
  this.msPerFrame = 1000 / FPS;      // frame rate
  this.status = Trex.status.WAITING; // Current status
  this.config = Trex.config;

  this.jumping = false;              // Whether to skip
  this.ducking = false;              // Whether to dodge (lean down)
  this.jumpVelocity = 0;             // The speed of the jump
  this.reachedMinHeight = false;     // Is minimum height reached
  this.speedDrop = false;            // Whether to accelerate the decline
  this.jumpCount = 0;                // Number of skips
  this.jumpspotX = 0;                // x-coordinate of jump point

  this.init();
}

Related configuration parameters:

Trex.config = {
  GRAVITY: 0.6,               // gravitation
  WIDTH: 44,                  // Width when standing
  HEIGHT: 47,
  WIDTH_DUCK: 59,             // Width at bend
  HEIGHT_DUCK: 25,
  MAX_JUMP_HEIGHT: 30,        // Maximum Jump Height
  MIN_JUMP_HEIGHT: 30,        // Minimum Jump Height
  SPRITE_WIDTH: 262,          // Total width of a standing dinosaur in a sprite map
  DROP_VELOCITY: -5,          // Falling speed
  INITIAL_JUMP_VELOCITY: -10, // Initial Jump Speed
  SPEED_DROP_COEFFICIENT: 3,  // Acceleration factor when falling (the larger the falling the faster)
  INTRO_DURATION: 1500,       // Time of opening animation
  START_X_POS: 50,            // The x-coordinate of the dinosaur on canvas after the opening animation
};

Trex.BLINK_TIMING = 7000;     // Maximum time between blinks

// The state of small dinosaurs
Trex.status = {
  CRASHED: 'CRASHED', // Hit an obstacle
  DUCKING: 'DUCKING', // Avoiding (leaning down)
  JUMPING: 'JUMPING', // Jumping
  RUNNING: 'RUNNING', // Running
  WAITING: 'WAITING', // Waiting (no game started)
};

// Configure different animation frames for different states
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
  },
};

To supplement some of the data used in this article:

Runner.config = {
  // ...

  BOTTOM_PAD: 10,     // Distance of small dinosaurs from the bottom of canvas
  MAX_BLINK_COUNT: 3, // Maximum Number of Blinks of Little Dinosaurs
};

Runner.spriteDefinition = {
  LDPI: {
    // ...

    TREX: {x: 848, y: 2}, // Small Dinosaurs
  },
};

Then look at the methods on the Trex prototype chain.Let's start by drawing small, static dinosaurs:

Trex.prototype = {
  init: function() {
    // Getting y-coordinates of a small dinosaur standing on the ground
    this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
        Runner.config.BOTTOM_PAD;
    this.yPos = this.groundYPos; // y-coordinate initialization of small dinosaurs

    this.draw(0, 0);             // Draw the first picture of a small dinosaur
  },
  /**
   * Draw Little Dinosaurs
   * @param {Number} x The x-coordinate of the current frame relative to the first frame
   * @param {Number} y y-coordinate of current frame relative to first frame
   */
  draw: function(x, y) {
    // Coordinates in Sprite
    var sourceX = x + this.spritePos.x;
    var sourceY = y + this.spritePos.y;

    // Width and height in Sprite
    var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
        this.config.WIDTH_DUCK : this.config.WIDTH;
    var sourceHeight = this.config.HEIGHT;

    // Height when drawn on canvas
    var outputHeight = sourceHeight;

    // Avoidance state.
    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 {
      // Hit an obstacle while dodging
      if (this.ducking && this.status == Trex.status.CRASHED) {
        this.xPos++;
      }
      // Running Status
      this.ctx.drawImage(
        Runner.imageSprite,
        sourceX, sourceY,
        sourceWidth, sourceHeight,
        this.xPos, this.yPos,
        this.config.WIDTH, outputHeight
      );
    }

    this.ctx.globalAlpha = 1;
  },
};

In the previous chapter into arcade mode, you used data from the Trex class and temporarily defined the Trex class, so don't forget to delete it.

Next, you need to call the Trex class through the Runner class.Add attributes to store instances of small dinosaurs:

function Runner(containerSelector, opt_config) {
  // ...

+ this.tRex = null; // Small Dinosaurs
}

Initialize small dinosaurs:

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

    // Loading small dinosaurs
+   this.tRex = new Trex(this.canvas, this.spriteDef.TREX);
  },
};

This draws a static dinosaur when the game is initialized, as shown in the following figure:

To achieve blinking effect

When the game is initialized, the little dinosaurs blink randomly.The default is to blink at most three times.This effect will be achieved below.

Add a way to update the dinosaurs:

Trex.prototype = {
  /**
   * Update Little Dinosaurs
   * @param {Number} deltaTime Interval Time
   * @param {String} opt_status The state of small dinosaurs
   */
  update: function(deltaTime, opt_status) {
    this.timer += deltaTime;

    // Parameters to update status
    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(); // Set the time at which the blink animation begins
        this.setBlinkDelay();                // Set the time between blinks
      }
    }

    if (this.status == Trex.status.WAITING) {
      // Little Dinosaur Blinking
      this.blink(getTimeStamp());
    } else {
      // Draw animation frames
      this.draw(this.currentAnimFrames[this.currentFrame], 0);
    }

    if (this.timer >= this.msPerFrame) {
      // Update the current animation frame, if at the last frame, to the first frame, otherwise to the next frame
      this.currentFrame = this.currentFrame ==
        this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
      // Reset timer
      this.timer = 0;
    }
  },
  // Set the time between blinks
  setBlinkDelay: function() {
    this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
  },
  // Little Dinosaur Blinking
  blink: function (time) {
    var deltaTime = time - this.animStartTime;
    
    // The interval is longer than the randomly obtained blink interval to blink
    if (deltaTime >= this.blinkDelay) {
      this.draw(this.currentAnimFrames[this.currentFrame], 0);
      
      // Blinking
      if (this.currentFrame == 1) {
        console.log('Blink');
        this.setBlinkDelay();      // Reset blink interval time
        this.animStartTime = time; // Update the start time of blink animation
        this.blinkCount++;         // Blinks plus one
      }
    }
  },
};

Then the baby dinosaurs are initially updated to wait:

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

    this.update(0, Trex.status.WAITING); // Initially in a waiting state
  },
};

Finally, in Runner's update method, Trex's update method is called to blink the little dinosaur:

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

    // The game starts or the little dinosaur hasn't blinked three times
-   if (this.playing) {
+   if (this.playing || (!this.activated &&
+     this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) {
+     this.tRex.update(deltaTime);
      
      // Make the next update
      this.scheduleNextUpdate();
    }
  },
};

The results are as follows:

You can see that the code logic for blinking triggered three times, but the actual dinosaur blinked only once.That's what we said before. By default, small dinosaurs can only blink up to three times.The specific reasons are as follows:

Let's start with this code in Trex's update method:

if (this.timer >= this.msPerFrame) {
  // Update the current animation frame, if at the last frame, to the first frame, otherwise to the next frame
  this.currentFrame = this.currentFrame ==
    this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
  // Reset timer
  this.timer = 0;
}

This code keeps the current animation frame updated to the next frame.For small dinosaurs, the two frames of open and close eyes are constantly switched.If the current frame is "open eyes", then the little dinosaur will still be open after the blink function is executed, that is, the actual little dinosaur did not blink; similarly, only when the current frame is "closed eyes", will the little dinosaur blink truly.

As for the purpose, it is to prevent the small dinosaurs from blinking.For example, modify the blink function to:

// Little Dinosaur Blinking
blink: function () {
  this.draw(this.currentAnimFrames[this.currentFrame], 0);
},

So the little dinosaurs keep blinking.So you need to restrict it, here's what the Chrome developers do: set an interval to allow a small dinosaur to blink when the interval between blinks is longer than this and the current animation frame is "closed eyes".Then after each blink, the random blinks of the dinosaurs were achieved by resetting the blink interval (default set to 0-7 seconds).

Starting animation of a small dinosaur

Here's how the little dinosaur responds to keyboard keystrokes.

First, when the game egg is triggered, the little dinosaur jumps once and moves 50 pixels to the right (the default setting is 50 pixels).

Add a way for the little dinosaurs to start jumping:

Trex.prototype = {
  // Start jumping
  startJump: function(speed) {
    if (!this.jumping) {
      // Update Little Dinosaurs to Jump 
      this.update(0, Trex.status.JUMPING);
      
      // Adjust the speed of the jump according to the speed of the game
      this.jumpVelocity = this.config.INITIAL_JUMP_VELOCITY - (speed / 10);
      
      this.jumping = true;
      this.reachedMinHeight = false;
      this.speedDrop = false;
    }
  },
};

To invoke:

Runner.prototype = {
  onKeyDown: function (e) {
    if (!this.crashed && !this.paused) {
      if (Runner.keyCodes.JUMP[e.keyCode]) {
        e.preventDefault();
        
        // ...

        // Start jumping
+       if (!this.tRex.jumping && !this.tRex.ducking) {
+         this.tRex.startJump(this.currentSpeed);
+       }
      }
    }
  },
};

In this way, the little dinosaur will remain stationary on the ground after pressing the space bar.Next, the animation frame needs to be updated to animate the little dinosaur running.

Add a way to update the animation frame of a small dinosaur:

Trex.prototype = {
  // Update the animation frame of a small dinosaur jumping
  updateJump: function(deltaTime) {
    var msPerFrame = Trex.animFrames[this.status].msPerFrame; // Get the frame rate of the current state
    var framesElapsed = deltaTime / msPerFrame;

    // Accelerated drop
    if (this.speedDrop) {
      this.yPos += Math.round(this.jumpVelocity *
        this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
    } else {
      this.yPos += Math.round(this.jumpVelocity * framesElapsed);
    }

    // The speed of the jump is influenced by gravity, decreasing upwards and then reversing
    this.jumpVelocity += this.config.GRAVITY * framesElapsed;

    // Minimum allowed jump height reached
    if (this.yPos < this.minJumpHeight || this.speedDrop) {
      this.reachedMinHeight = true;
    }

    // The maximum allowable jump height has been reached
    if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
      this.endJump(); // End Jump
    }

    // Return to the ground, jump complete
    if (this.yPos > this.groundYPos) {
      this.reset();     // Reset the status of the baby dinosaurs
      this.jumpCount++; // Number of jumps plus one
    }
  },
  // End of Jump
  endJump: function() {
    if (this.reachedMinHeight &&
        this.jumpVelocity < this.config.DROP_VELOCITY) {
      this.jumpVelocity = this.config.DROP_VELOCITY; // Drop Speed Reset to Default
    }
  },
  // Reset Little Dinosaur State
  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;
  },
};

The attribute value of minJumpHeight is:

Trex.prototype = {
  init: function() {
    // Minimum Jump Height
+   this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
    
    // ...
  },
}

Then make a call:

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 was not defined at first! this.playingIntro is true
-     if (!this.playingIntro) {
+     if (this.tRex.jumpCount == 1 && !this.playingIntro) {
        this.playIntro(); // Perform opening animation
      }

      // ...
    }

    // ...
  },
};

This way, after pressing the space bar, the little dinosaur will jump once and run animation.Figure:

Here's how it works: After the first jump, the little dinosaur moves 50 pixels to the right.

Modify Trex's update method.Move the little dinosaur when you know the opening animation is being performed:

Trex.prototype = {
  update: function(deltaTime, opt_status) {
    this.timer += deltaTime;

    // Parameters to update status
    if (opt_status) {
      // ...
    }

    // Performing opening animation, moving the little dinosaur 50 pixels to the right
+   if (this.playingIntro && this.xPos < this.config.START_X_POS) {
+     this.xPos += Math.round((this.config.START_X_POS /
+       this.config.INTRO_DURATION) * deltaTime);
+   }

    // ...
  },
};

You can see that when the playingIntro attribute is true, the small dinosaurs move to the right.So control the movement of the small dinosaurs after their first jump by controlling the value of this property.

Modify the playIntro method on Runner to mark the small dinosaurs as performing the opening animation:

Runner.prototype = {
  playIntro: function () {
    if (!this.activated && !this.crashed) {
+     this.tRex.playingIntro = true; // Small Dinosaurs Perform Opening Animations

      // ...
    }
  },
};

Then you need to end the opening animation of the little dinosaur after you start the game, that is, when you execute the startGame method:

Runner.prototype = {
  startGame: function () {
    this.setArcadeMode();           // Enter arcade mode
    
+   this.tRex.playingIntro = false; // The opening animation of a small dinosaur ends
    
    // ...
  },
};

The results are as follows:

It is clear that the little dinosaur moved a distance to the right after its first jump (the default is 50 pixels).

Keyboard control of small dinosaurs

In this game, when the_key is pressed, if the small dinosaur is jumping, it will fall down quickly. If the small dinosaur is on the ground, it will enter a dodging state, to achieve these effects below.

Accelerated drop:

Trex.prototype = {
  // Set the little dinosaur to drop faster and cancel the current jump immediately
  setSpeedDrop: function() {
    this.speedDrop = true;
    this.jumpVelocity = 1;
  },
};

Set whether the little dinosaurs are dodging:

Trex.prototype = {
  // Set up whether the little dinosaurs are dodging while running
  setDuck: function(isDucking) {
    if (isDucking && this.status != Trex.status.DUCKING) { // Dodge state
      this.update(0, Trex.status.DUCKING);
      this.ducking = true;
    } else if (this.status == Trex.status.DUCKING) {       // Running Status
      this.update(0, Trex.status.RUNNING);
      this.ducking = false;
    }
  },
};

Called in the onKeyDown method:

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(); // Accelerated drop
+       } else if (!this.tRex.jumping && !this.tRex.ducking) {
+         this.tRex.setDuck(true);  // Enter dodging state
+       }
+     }
    }
  },
};

This achieves the effect mentioned above.But when the little dinosaur dodges, it doesn't get up again if it releases the key.Because there is no defined event to respond when keyboard keys are released.Define it below:

Runner.prototype = {
  onKeyUp: function(e) {
    var keyCode = String(e.keyCode);

    if (Runner.keyCodes.DUCK[keyCode]) { // Avoidance state
      this.tRex.speedDrop = false;
      this.tRex.setDuck(false);
    }
  },
};

Then call to modify the handleEvent method:

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);
  },
};

The results are as follows:

The first jump is a normal fall, the second is an accelerated fall

Handle the jumps of small dinosaurs

Small dinosaur jumps are divided into big jumps and small jumps, as shown in the figure:

To achieve this effect, you only need to end the jump of the small dinosaur as soon as the_key is released.

Modify the onKeyUp method:

Runner.prototype = {
  onKeyUp: function(e) {
    var keyCode = String(e.keyCode);
+   var isjumpKey = Runner.keyCodes.JUMP[keyCode];

+   if (this.isRunning() && isjumpKey) {        // jump
+     this.tRex.endJump();
    } else if (Runner.keyCodes.DUCK[keyCode]) { // Avoidance state
      this.tRex.speedDrop = false;
      this.tRex.setDuck(false);
    }
  },
};

The isRunning method is defined as follows:

Runner.prototype = {
  // Is the game in progress
  isRunning: function() {
    return !!this.raqId;
  },
};

This allows the small dinosaurs to do big and small jumps.

Finally, the effect is that if the page is out of focus and the little dinosaur is jumping, it resets its state (that is, it immediately returns to the ground).This effect is easy to achieve by calling the reset method defined earlier:

Runner.prototype = {
  play: function () {
    if (!this.crashed) {
      // ...

+     this.tRex.reset();
    }
  },
};

The results are as follows:

View the added code, Stamp here

Demo experience address: https://liuyib.github.io/pages/demo/games/google-dino/dino-gogogo/

Previous Next
Chrome Dinosaur Game Source Exploration Seven--Alternating Day and Night Modes TODO

Posted by frankienrg on Fri, 26 Apr 2019 17:36:35 -0700