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

8次阅读

共计 5662 个字符,预计需要花费 15 分钟才能阅读完成。

文章首发于我的个人博客

前言

上一篇文章:《Chrome 小恐龙游戏源码探究六 — 记录游戏分数》实现了游戏分数、最高分数的记录和绘制。这一篇文章中将实现昼夜模式交替的的效果。

夜晚模式

定义夜晚模式类 NightMode:

/**
 * 夜晚模式
 * @param {HTMLCanvasElement} canvas 画布
 * @param {Object} spritePos 雪碧图中的坐标信息
 * @param {Number} containerWidth 容器宽度
 */
function NightMode(canvas, spritePos, containerWidth) {
  this.canvas = canvas;
  this.ctx = this.canvas.getContext('2d');

  this.spritePos = spritePos;
  this.containerWidth = containerWidth;

  this.xPos = containerWidth - 50; // 月亮的 x 坐标
  this.yPos = 30;                  // 月亮的 y 坐标
  this.currentPhase = 0;           // 月亮当前所处的时期
  this.opacity = 0;                // 星星和月亮的透明度
  this.stars = [];                 // 存储星星
  this.drawStars = false;          // 是否绘制星星
  
  // 放置星星
  this.placeStars();}

相关的配置参数:

NightMode.config = {
  WIDTH: 20,         // 半月的宽度
  HEIGHT: 40,        // 月亮的高度
  FADE_SPEED: 0.035, // 淡入淡出的速度
  MOON_SPEED: 0.25,  // 月亮的速度
  NUM_STARS: 2,      // 星星的数量
  STAR_SIZE: 9,      // 星星的大小
  STAR_SPEED: 0.3,   // 星星的速度
  STAR_MAX_Y: 70,    // 星星在画布上的最大 y 坐标
};

// 月亮所处的时期(不同的时期有不同的位置)NightMode.phases = [140, 120, 100, 60, 40, 20, 0];

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

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

+ this.inverted = false;         // 是否开启夜晚模式
+ this.invertTimer = 0;          // 夜晚模式的时间
}

Runner.config = {
  // ...

+ INVERT_FADE_DURATION: 12000,             // 夜晚模式的持续时间
+ INVERT_DISTANCE: 100,                    // 触发夜晚模式的距离
};


Runner.spriteDefinition = {
  LDPI: {
    // ...
+   MOON: {x: 484, y: 2},
+   STAR: {x: 645, y: 2},
  },
};


Runner.classes = {
  // ...

+ INVERTED: 'inverted',
};
body {transition: filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1),
              background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);
  will-change: filter, background-color;
}

.inverted {filter: invert(100%);
  background-color: #000;
}

来看下 NightMode 原型链上的方法:

NightMode.prototype = {draw: function () {
    // 月期为 3 时,月亮为满月
    var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 :
        NightMode.config.WIDTH;
    var moonSourceHeight = NightMode.config.HEIGHT;

    // 月亮在雪碧图中的 x 坐标
    var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase];
    var moonOutputWidth = moonSourceWidth;
    
    // 星星在雪碧图中的 x 坐标
    var starSourceX = Runner.spriteDefinition.LDPI.STAR.x;
    var starSize = NightMode.config.STAR_SIZE;

    this.ctx.save();
    this.ctx.globalAlpha = this.opacity; // 画布的透明度随之变化

    // 绘制星星
    if (this.drawStars) {for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
        this.ctx.drawImage(
          Runner.imageSprite,
          starSourceX, this.stars[i].sourceY,
          starSize, starSize,
          Math.round(this.stars[i].x), this.stars[i].y,
          NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE,
        );
      }
    }

    // 绘制月亮
    this.ctx.drawImage(
      Runner.imageSprite,
      moonSourceX, this.spritePos.y,
      moonSourceWidth, moonSourceHeight,
      Math.round(this.xPos), this.yPos,
      moonOutputWidth, NightMode.config.HEIGHT
    );
    
    this.ctx.globalAlpha = 1;
    this.ctx.restore();},
  /**
    * 更新星星和月亮的位置,改变月期
    * @param {Boolean} activated 是否夜晚模式被激活
    */
  update: function (activated) {
    // 改变月期
    if (activated && this.opacity === 0) {
      this.currentPhase++;

      if (this.currentPhase >= NightMode.phases.length) {this.currentPhase = 0;}
    }

    // 淡入
    if (activated && (this.opacity < 1 || this.opacity === 0)) {this.opacity += NightMode.config.FADE_SPEED;} else if (this.opacity > 0) { // 淡出
      this.opacity -= NightMode.config.FADE_SPEED;
    }

    // 设置月亮和星星的位置
    if (this.opacity > 0) {
      // 更新月亮的 x 坐标
      this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);

      // 更新星星的 x 坐标
      if (this.drawStars) {for (var i = 0; i < NightMode.config.NUM_STARS; i++) {this.stars[i].x = this.updateXPos(this.stars[i].x, 
            NightMode.config.STAR_SPEED);
        }
      }

      this.draw();} else {
      this.opacity = 0;
      this.placeStars();}

    this.drawStars = true;
  },
  // 更新 x 坐标
  updateXPos: function (currentPos, speed) {
    // 月亮移出画布半个月亮宽度,将其位置移动到画布右边
    if (currentPos < -NightMode.config.WIDTH) {currentPos = this.containerWidth;} else {currentPos -= speed;}

    return currentPos;
  },
  // 随机放置星星
  placeStars: function () {
    // 将画布分为若干组
    var segmentSize = Math.round(this.containerWidth /
      NightMode.config.NUM_STARS);

    for (var i = 0; i < NightMode.config.NUM_STARS; i++) {this.stars[i] = {};

      // 分别随机每组画布中星星的位置
      this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1));
      this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y);

      // 星星在雪碧图中的 y 坐标
      this.stars[i].sourceY = Runner.spriteDefinition.LDPI.STAR.y +
          NightMode.config.STAR_SIZE * i;
    }
  },
};

定义好 NightMode 类以及相关方法后,接下来需要通过 Horizon 来进行调用。

修改 Horizon 类:

function Horizon(canvas, spritePos, dimensions, gapCoefficient) {
  // ...
  
  // 夜晚模式
+ this.nightMode = null;
}

初始化 NightMode 类:

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

+   this.nightMode = new NightMode(this.canvas, this.spritePos.MOON,
+     this.dimensions.WIDTH);
  },
};

更新夜晚模式:

Horizon.prototype = {- update: function (deltaTime, currentSpeed, updateObstacles) {+ update: function (deltaTime, currentSpeed, updateObstacles, showNightMode) {
    // ...

+   this.nightMode.update(showNightMode);
  },
};

然后修改 Runner 的 update 方法:

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

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

      // 直到开场动画结束再移动地面
      if (this.playingIntro) {this.horizon.update(0, this.currentSpeed, hasObstacles);
      } else {
        deltaTime = !this.activated ? 0 : deltaTime;
-       this.horizon.update(deltaTime, this.currentSpeed, hasObstacles);
+       this.horizon.update(deltaTime, this.currentSpeed, hasObstacles,
+         this.inverted);
      }

+     // 夜晚模式
+     if (this.invertTimer > this.config.INVERT_FADE_DURATION) { // 夜晚模式结束
+       this.invertTimer = 0;
+       this.invertTrigger = false;
+       this.invert();
+     } else if (this.invertTimer) { // 处于夜晚模式,更新其时间
+       this.invertTimer += deltaTime;
+     } else { // 还没进入夜晚模式
+       // 游戏移动的距离
+       var actualDistance =
+         this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan));
+
+       if(actualDistance > 0) {
+         // 每移动指定距离就触发一次夜晚模式
+         this.invertTrigger = !(actualDistance % this.config.INVERT_DISTANCE);
+
+         if (this.invertTrigger && this.invertTimer === 0) {
+           this.invertTimer += deltaTime;
+           this.invert();
+         }
+       }
+     }
    }

    if (this.playing) {
      // 进行下一次更新
      this.scheduleNextUpdate();}
  },
};

上面用到的 invert 方法定义如下:

Runner.prototype = {
  /**
   * 反转当前页面的颜色
   * @param {Boolea} reset 是否重置颜色
   */
  invert: function (reset) {
    var bodyElem = document.body;

    if (reset) {bodyElem.classList.toggle(Runner.classes.INVERTED, false); // 删除 className

      this.invertTimer = 0;  // 重置夜晚模式的时间
      this.inverted = false; // 关闭夜晚模式
    } else {
      this.inverted = bodyElem.classList.toggle(Runner.classes.INVERTED,
        this.invertTrigger);
    }
  },
};

这样就是实现了昼夜交替的效果。原来的游戏中,昼夜交替每 700 米触发一次,这里为了演示,改成了 100 米触发一次。效果如下:

查看添加的代码,戳这里

Demo 体验地址:https://liuyib.github.io/pages/demo/games/google-dino/night-mode/

上一篇 下一篇
Chrome 小恐龙游戏源码探究六 — 记录游戏分数 Chrome 小恐龙游戏源码探究八 — 奔跑的小恐龙
正文完
 0