Chrome Dinosaur Game Source Exploration 6 - Recording Game Scores

Keywords: Javascript github Google

Preface

Last article:< Chrome Dinosaur Game Source Exploration 5 - Random Drawing Obstacles > The rendering of obstacle cactus and pterosaur is realized. This article will record and draw the current score and the highest score.

In the game, the distance a small dinosaur moves is the score of the game. Every time the score reaches 100, there will be a special effect of flickering. When the first game is played, the highest score will not be displayed because the historical score of the game has not been recorded. Only when the first game is over can the highest score be displayed.

Score record

Define score categories:

/**
 * Record the distance of movement (fraction equals the distance of movement)
 * @param {HTMLCanvasElement} canvas canvas
 * @param {Object} spritePos The position of the picture in Sprite
 * @param {Number} canvasWidth Width of canvas
 */
function DistanceMeter(canvas, spritePos, canvasWidth) {
  this.canvas = canvas;
  this.ctx = canvas.getContext('2d');

  this.config = DistanceMeter.config;
  this.spritePos = spritePos;

  this.x = 0;               // x coordinates of fractions displayed in canvas
  this.y = 5;

  this.maxScore = 0;        // Game Score Upper Limit
  this.highScore = [];      // Store every digit with the highest score

  this.digits = [];         // Store every number of points
  this.achievement = false; // Whether to do flash effects or not
  this.defaultString = '';  // Default Distance of Game (00000)
  this.flashTimer = 0;      // Animation timer
  this.flashIterations = 0; // Number of special effect flickers

  this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS; // Maximum number of fractions

  this.init(canvasWidth);
}

Relevant configuration parameters:

DistanceMeter.config = {
  MAX_DISTANCE_UNITS: 5,          // Maximum number of fractions
  ACHIEVEMENT_DISTANCE: 100,      // Flash effect triggered every 100 meters
  COEFFICIENT: 0.025,             // Coefficient of converting pixel distance to proportional unit
  FLASH_DURATION: 1000 / 4,       // The time of a flash (two flickers in one flicker: from being to not, from nothing to being)
  FLASH_ITERATIONS: 3,            // Number of flickers
};

DistanceMeter.dimensions = {
  WIDTH: 10,
  HEIGHT: 13,
  DEST_WIDTH: 11, // Width of each digit after adding intervals
};

To supplement some of the data used in this article:

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

+ this.msPerFrame = 1000 / FPS; // Time per frame
},

Runner.spriteDefinition = {
  LDPI: {
    //...
+   TEXT_SPRITE: {x: 655, y: 2}, // Written words
  },
};

Add a method to DistanceMeter:

DistanceMeter.prototype = {
  init: function (width) {
    var maxDistanceStr = '';     // Maximum Distance of Game

    this.calcXPos(width);        // Calculating the x coordinates of the fraction displayed in canvas

    for (var i = 0; i < this.maxScoreUnits; i++) {
      this.draw(i, 0);           // The first game, do not draw the highest score
      this.defaultString += '0'; // Default initial score 00000
      maxDistanceStr += '9';     // Default maximum score 99999
    }
    
    this.maxScore = parseInt(maxDistanceStr);
  },
  calcXPos: function (canvasWidth) {
    this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
      (this.maxScoreUnits + 1));
  },
  /**
    * Draw fractions on canvas
    * @param {Number} digitPos The Position of Numbers in Fractions
    * @param {Number} value The specific value of the number (0-9)
    * @param {Boolean} opt_highScore Does it show the highest score?
    */
  draw: function (digitPos, value, opt_highScore) {
    // Coordinates in Sprite
    var sourceX = this.spritePos.x + DistanceMeter.dimensions.WIDTH * value;
    var sourceY = this.spritePos.y + 0;
    var sourceWidth = DistanceMeter.dimensions.WIDTH;
    var sourceHeight = DistanceMeter.dimensions.HEIGHT;

    // Coordinates drawn to canvas
    var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
    var targetY = this.y;
    var targetWidth = DistanceMeter.dimensions.WIDTH;
    var targetHeight = DistanceMeter.dimensions.HEIGHT;

    this.ctx.save();

    if (opt_highScore) { // Show the highest score
      var hightScoreX = this.x - (this.maxScoreUnits * 2) *
        DistanceMeter.dimensions.WIDTH;

      this.ctx.translate(hightScoreX, this.y);
    } else {            // Not showing the highest score
      this.ctx.translate(this.x, this.y);
    }

    this.ctx.drawImage(
      Runner.imageSprite,
      sourceX, sourceY,
      sourceWidth, sourceHeight,
      targetX, targetY,
      targetWidth, targetHeight
    );

    this.ctx.restore();
  },
  /**
    * Converting the Pixel Distance of Game Moving to the Real Distance
    * @param {Number} distance Pixel Distance of Game Moving
    */
  getActualDistance: function (distance) {
    return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
  },
  update: function (deltaTime, distance) {
    var paint = true;      // Whether to draw scores or not
    var playSound = false; // Whether to Play Sound Effect

    // No flash effects
    if (!this.achievement) {
      distance = this.getActualDistance(distance);

      // When the score exceeds the upper limit, the upper limit is increased by one digit. When two digits above the upper limit are exceeded, the fraction is set to zero.
      if (distance > this.maxScore &&
        this.maxScoreUnits === this.config.MAX_DISTANCE_UNITS) {
        this.maxScoreUnits++;
        this.maxScore = parseInt(this.maxScore + '9');
      } else {
        this.distance = 0;
      }

      if (distance > 0) {
        // Trigger flicker effect
        if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) {
          this.achievement = true;
          this.flashTimer = 0;
          playSound = true;
        }

        // Complete the zero before the score to make up the number of digits
        var distanceStr = (this.defaultString + distance).substr(-this.maxScoreUnits);
        this.digits = distanceStr.split('');
      } else {
        // Store each digit in the array with the default score of 00000
        this.digits = this.defaultString.split('');
      }
    } else {
      // Control the number of flickers of special effects
      if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
        this.flashTimer += deltaTime;

        // The first flash does not draw numbers
        if (this.flashTimer < this.config.FLASH_DURATION) {
          paint = false;
        }
        // Two flickers were made and the number of flickers was increased by one.
        else if (this.flashTimer > this.config.FLASH_DURATION * 2) {
          this.flashTimer = 0;
          this.flashIterations++;
        }
      } else { // Flash Special Effect End
        this.achievement = false;
        this.flashIterations = 0;
        this.flashTimer = 0;
      }
    }

    // Draw the current score
    if (paint) {
      for (var i = this.digits.length - 1; i >= 0; i--) {
        this.draw(i, parseInt(this.digits[i]));
      }
    }

    // Draw the highest score
    this.drawHighScore();
    return playSound;
  },
  drawHighScore: function () {
    this.ctx.save();
    this.ctx.globalAlpha = 0.8;

    for (var i = this.highScore.length - 1; i >= 0; i--) {
      this.draw(i, parseInt(this.highScore[i], 10), true);
    }
    this.ctx.restore();
  },
  /**
    * Store the highest score of the game in an array
    * @param {Number} distance Pixel Distance of Game Moving
    */
  setHighScore: function (distance) {
    distance = this.getActualDistance(distance);
    var highScoreStr = (this.defaultString
      + distance).substr(-this.maxScoreUnits);
    
    // The letters H and I in front of the score are behind the numbers in the Sprite chart, that is, positions 10 and 11.
    this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
  },
};

The following is a call to the score class.

First, add attributes to the Runner class:

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

+ this.distanceMeter = null;     // Distance Counting Class
+ this.distanceRan = 0;          // Game Moving Distance
+ this.highestScore = 0;         // Highest score
}

Then initialize the score class DistanceMeter:

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

    // Loading Distance Meter Class
    this.distanceMeter = new DistanceMeter(this.canvas,
      this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH);
  },
};

Update game scores (moving distance):

Runner.prototype = {
  update: function () {
    this.updatePending = false; // Waiting for updates

    if (this.playing) {
      // ...

+     this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;

      if (this.currentSpeed < this.config.MAX_SPEED) {
        this.currentSpeed += this.config.ACCELERATION;
      }

+     var playAchievementSound = this.distanceMeter.update(deltaTime,
+       Math.ceil(this.distanceRan));
    }

    // ...
  },
};

In this way, the score drawing is realized, and the effect is as follows:

Look at the added code. Poke here

The method of saving and drawing the highest score of the game is defined above, but it has not been invoked yet. Now, as long as the score is saved at the end of the game, the drawing of the highest score can be achieved:

Runner.prototype = {
  // Game over
  gameOver: function () {
    this.stop();

    if (this.distanceRan > this.highestScore) {
      this.highestScore = Math.ceil(this.distanceRan);
      this.distanceMeter.setHighScore(this.highestScore); // Keep the highest score
    }

    // reset time
    this.time = getTimeStamp();
  },
};

Here for demonstration, when the page is out of focus, end the game:

Runner.prototype = {
  onVisibilityChange: function (e) {
    if (document.hidden || document.webkitHidden || e.type == 'blur' ||
      document.visibilityState != 'visible') {
      this.stop();
      
+     this.gameOver();
    } else if (!this.crashed) {
      this.play();
    }
  },
};

The results are as follows:

Look at the added code. Poke here

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

Last article Next article
Chrome Dinosaur Game Source Exploration 5 - Random Drawing Obstacles Chrome Dinosaur Game Source Exploration

Posted by Gregadeath on Sat, 27 Apr 2019 11:54:36 -0700