资讯专栏INFORMATION COLUMN

Chrome 小恐龙游戏源码探究六 -- 记录游戏分数

Jingbin_ / 1113人阅读

摘要:文章首发于我的博客前言上一篇文章小恐龙游戏源码探究五随机绘制障碍实现了障碍物仙人掌和翼龙的绘制。在游戏中,小恐龙移动的距离就是游戏的分数。

文章首发于我的 GitHub 博客
前言

上一篇文章:《Chrome 小恐龙游戏源码探究五 -- 随机绘制障碍》 实现了障碍物仙人掌和翼龙的绘制。这一篇将实现当前分数、最高分数的记录和绘制。

在游戏中,小恐龙移动的距离就是游戏的分数。分数每达 100,就会有闪动的特效。首次进行游戏的时候,由于没有记录过游戏的历史得分,所以不会显示最高分,只有当第一次 game over 后才能显示历史最高分。

分数记录

定义分数类 DistanceMeter

/**
 * 记录移动的距离(分数等于移动距离)
 * @param {HTMLCanvasElement} canvas 画布
 * @param {Object} spritePos 图片在雪碧图中的位置
 * @param {Number} canvasWidth 画布的宽度
 */
function DistanceMeter(canvas, spritePos, canvasWidth) {
  this.canvas = canvas;
  this.ctx = canvas.getContext("2d");

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

  this.x = 0;               // 分数显示在 canvas 中的 x 坐标
  this.y = 5;

  this.maxScore = 0;        // 游戏分数上限
  this.highScore = [];      // 存储最高分数的每一位数字

  this.digits = [];         // 存储分数的每一位数字
  this.achievement = false; // 是否进行闪动特效
  this.defaultString = "";  // 游戏的默认距离(00000)
  this.flashTimer = 0;      // 动画计时器
  this.flashIterations = 0; // 特效闪动的次数

  this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS; // 分数的最大位数

  this.init(canvasWidth);
}

有关的配置参数:

DistanceMeter.config = {
  MAX_DISTANCE_UNITS: 5,          // 分数的最大位数
  ACHIEVEMENT_DISTANCE: 100,      // 每 100 米触发一次闪动特效
  COEFFICIENT: 0.025,             // 将像素距离转换为比例单位的系数
  FLASH_DURATION: 1000 / 4,       // 一闪的时间(一次闪动分别两闪:从有到无,从无到有)
  FLASH_ITERATIONS: 3,            // 闪动的次数
};

DistanceMeter.dimensions = {
  WIDTH: 10,
  HEIGHT: 13,
  DEST_WIDTH: 11, // 加上间隔后每个数字的宽度
};

补充本篇文章中会用到的一些数据:

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

+ this.msPerFrame = 1000 / FPS; // 每帧的时间
},

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

DistanceMeter 上添加方法:

DistanceMeter.prototype = {
  // 初始化分数
  init: function (width) {
    var maxDistanceStr = "";     // 游戏的最大距离

    this.calcXPos(width);        // 计算分数显示在 canvas 中的 x 坐标

    for (var i = 0; i < this.maxScoreUnits; i++) {
      this.draw(i, 0);           // 第一次游戏,不绘制最高分
      this.defaultString += "0"; // 默认初始分数 00000
      maxDistanceStr += "9";     // 默认最大分数 99999
    }
    
    this.maxScore = parseInt(maxDistanceStr);
  },
  // 计算 x 坐标
  calcXPos: function (canvasWidth) {
    this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
      (this.maxScoreUnits + 1));
  },
  /**
   * 将分数绘制到 canvas 上
   * @param {Number} digitPos 数字在分数中的位置
   * @param {Number} value 数字的具体值(0-9)
   * @param {Boolean} opt_highScore 是否显示最高分
   */
  draw: function (digitPos, value, opt_highScore) {
    // 在雪碧图中的坐标
    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;

    // 绘制到 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) { // 显示最高分
      var hightScoreX = this.x - (this.maxScoreUnits * 2) *
        DistanceMeter.dimensions.WIDTH;

      this.ctx.translate(hightScoreX, this.y);
    } else {            // 不显示最高分
      this.ctx.translate(this.x, this.y);
    }

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

    this.ctx.restore();
  },
  /**
   * 将游戏移动的像素距离转换为真实的距离
   * @param {Number} distance 游戏移动的像素距离
   */
  getActualDistance: function (distance) {
    return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
  },
  // 更新分数
  update: function (deltaTime, distance) {
    var paint = true;      // 是否绘制分数
    var playSound = false; // 是否播放音效

    // 没有进行闪动特效
    if (!this.achievement) {
      distance = this.getActualDistance(distance);

      // 分数超出上限时,上限增加一位数。超出上限两位数时,分数置零
      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) {
        // 触发闪动特效
        if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) {
          this.achievement = true;
          this.flashTimer = 0;
          playSound = true;
        }

        // 分数前面补零来凑位数
        var distanceStr = (this.defaultString + distance).substr(-this.maxScoreUnits);
        this.digits = distanceStr.split("");
      } else {
        // 将默认分数 00000 中的每一位数字存到数组中
        this.digits = this.defaultString.split("");
      }
    } else {
      // 控制特效的闪动次数
      if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
        this.flashTimer += deltaTime;

        // 第一闪不绘制数字
        if (this.flashTimer < this.config.FLASH_DURATION) {
          paint = false;
        }
        // 进行了两闪,闪动次数加一
        else if (this.flashTimer > this.config.FLASH_DURATION * 2) {
          this.flashTimer = 0;
          this.flashIterations++;
        }
      } else { // 闪动特效结束
        this.achievement = false;
        this.flashIterations = 0;
        this.flashTimer = 0;
      }
    }

    // 绘制当前分
    if (paint) {
      for (var i = this.digits.length - 1; i >= 0; i--) {
        this.draw(i, parseInt(this.digits[i]));
      }
    }

    // 绘制最高分
    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();
  },
  /**
   * 将游戏的最高分数存入数组
   * @param {Number} distance 游戏移动的像素距离
   */
  setHighScore: function (distance) {
    distance = this.getActualDistance(distance);
    var highScoreStr = (this.defaultString
      + distance).substr(-this.maxScoreUnits);
    
    // 分数前面字母 H、I 在雪碧图中位于数字后面,也就是第 10、11 位置
    this.highScore = ["10", "11", ""].concat(highScoreStr.split(""));
  },
};

下面是对分数类的调用。

首先给 Runner 类添加属性:

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

+ this.distanceMeter = null;     // 距离计数类
+ this.distanceRan = 0;          // 游戏移动距离
+ this.highestScore = 0;         // 最高分
}

然后初始化分数类 DistanceMeter

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

+   // 加载距离计数器类 DistanceMeter
+   this.distanceMeter = new DistanceMeter(this.canvas,
+     this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH);
  },
};

更新游戏分数(移动距离):

Runner.prototype = {
  update: function () {
    this.updatePending = false; // 等待更新

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

    // ...
  },
};

这样就实现了分数的绘制,效果如下:

查看添加或修改的代码,戳这里

上面定义了保存、绘制游戏最高分的方法,但还没有调用,现在只要在游戏结束时,将分数保存下来,就能实现最高分的绘制。添加 gameOver 方法:

Runner.prototype = {
  // 游戏结束
  gameOver: function () {
    this.stop();

    if (this.distanceRan > this.highestScore) {
      this.highestScore = Math.ceil(this.distanceRan);
      this.distanceMeter.setHighScore(this.highestScore); // 保存最高分
    }

    // 重置时间
    this.time = getTimeStamp();
  },
};

这里为了演示,当页面失焦时,结束游戏(等后面讲到实现游戏结束时,需要删除这里的临时代码):

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

效果如下:

查看添加或修改的代码,戳这里

Demo 体验地址:https://liuyib.github.io/blog/demo/game/google-dino/show-score/

上一篇 下一篇
Chrome 小恐龙游戏源码探究五 -- 随机绘制障碍 Chrome 小恐龙游戏源码探究七 -- 昼夜模式交替

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103880.html

相关文章

  • Chrome 恐龙游戏源码探究七 -- 昼夜模式交替

    摘要:文章首发于我的博客前言上一篇文章小恐龙游戏源码探究六记录游戏分数实现了游戏分数最高分数的记录和绘制。这一篇文章中将实现昼夜模式交替的的效果。原来的游戏中,昼夜交替每米触发一次,这里为了演示,改成了米触发一次。 文章首发于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龙游戏源码探究六 -- 记录游戏分数》实现了游戏分数、最高分数的记录和绘制。这一篇文章中将实现昼夜模式...

    curried 评论0 收藏0
  • Chrome 恐龙游戏源码探究一 -- 绘制静态地面

    摘要:首先是绘制静态的地面。上一篇下一篇无小恐龙游戏源码探究二让地面动起来 文章首发于我的 GitHub 博客 目录 Chrome 小恐龙游戏源码探究一 -- 绘制静态地面 Chrome 小恐龙游戏源码探究二 -- 让地面动起来 Chrome 小恐龙游戏源码探究三 -- 进入街机模式 Chrome 小恐龙游戏源码探究四 -- 随机绘制云朵 Chrome 小恐龙游戏源码探究五 -- 随机绘...

    lixiang 评论0 收藏0
  • Chrome 恐龙游戏源码探究五 -- 随机绘制障碍

    摘要:文章首发于我的博客前言上一篇文章小恐龙游戏源码探究四随机绘制云朵实现了云朵的随机绘制,这一篇文章中将实现仙人掌翼龙障碍物的绘制游戏速度的改变障碍物的类型有两种仙人掌和翼龙。 文章首发于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龙游戏源码探究四 -- 随机绘制云朵》 实现了云朵的随机绘制,这一篇文章中将实现:1、仙人掌、翼龙障碍物的绘制 2、游戏速度的改变 障碍物...

    tomorrowwu 评论0 收藏0
  • Chrome 恐龙游戏源码探究九 -- 游戏碰撞检测

    摘要:文章首发于我的博客前言上一篇文章小恐龙游戏源码探究八奔跑的小恐龙实现了小恐龙的绘制以及键盘对小恐龙的控制,这一篇文章中将实现游戏的碰撞检测。 文章首发于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙》实现了小恐龙的绘制以及键盘对小恐龙的控制,这一篇文章中将实现游戏的碰撞检测。 碰撞检测原理 这个游戏采用的检测方法是盒子碰撞,这种检...

    cpupro 评论0 收藏0
  • Chrome 恐龙游戏源码探究三 -- 进入街机模式

    摘要:文章首发于我的博客前言上一篇文章小恐龙游戏源码探究二让地面动起来实现了地面的移动。街机模式的效果就是游戏开始后,进入全屏模式。例如可以看到,进入街机模式之前,有一段开场动画。 文章首发于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龙游戏源码探究二 -- 让地面动起来》 实现了地面的移动。这一篇文章中,将实现效果:1、浏览器失焦时游戏暂停,聚焦游戏继续。 2、开场动...

    yeooo 评论0 收藏0

发表评论

0条评论

Jingbin_

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<