Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
liuyib opened this issue Apr 22, 2019 · 0 comments
Open

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

liuyib opened this issue Apr 22, 2019 · 0 comments

Comments

@liuyib
Copy link
Owner

liuyib commented Apr 22, 2019

前言

上一篇文章:《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);
  },
};

然后修改 Runnerupdate 方法:

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 米触发一次。效果如下:

test

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

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

上一篇 下一篇
Chrome 小恐龙游戏源码探究六 -- 记录游戏分数 Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant