资讯专栏INFORMATION COLUMN

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

curried / 1518人阅读

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

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

上一篇文章:《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 米触发一次。效果如下:

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

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

上一篇 下一篇
Chrome 小恐龙游戏源码探究六 -- 记录游戏分数 Chrome 小恐龙游戏源码探究八 -- 奔跑的小恐龙

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

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

相关文章

  • Chrome 恐龙游戏源码探究八 -- 奔跑的恐龙

    摘要:例如,将函数修改为小恐龙眨眼这样小恐龙会不停的眨眼睛。小恐龙的开场动画下面来实现小恐龙对键盘按键的响应。接下来还需要更新动画帧才能实现小恐龙的奔跑动画。 文章首发于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龙游戏源码探究七 -- 昼夜模式交替》实现了游戏昼夜模式的交替,这一篇文章中,将实现:1、小恐龙的绘制 2、键盘对小恐龙的控制 3、页面失焦后,重新聚焦会重置...

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

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

    lixiang 评论0 收藏0
  • Chrome 恐龙游戏源码探究六 -- 记录游戏分数

    摘要:文章首发于我的博客前言上一篇文章小恐龙游戏源码探究五随机绘制障碍实现了障碍物仙人掌和翼龙的绘制。在游戏中,小恐龙移动的距离就是游戏的分数。 文章首发于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龙游戏源码探究五 -- 随机绘制障碍》 实现了障碍物仙人掌和翼龙的绘制。这一篇将实现当前分数、最高分数的记录和绘制。 在游戏中,小恐龙移动的距离就是游戏的分数。分数每达 1...

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

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

    yeooo 评论0 收藏0
  • Chrome 恐龙游戏源码探究四 -- 随机绘制云朵

    摘要:文章首发于我的博客前言上一篇文章小恐龙游戏源码探究三进入街机模式实现了开场动画和街机模式。 文章首发于我的 GitHub 博客 前言 上一篇文章:《Chrome 小恐龙游戏源码探究三 -- 进入街机模式》 实现了开场动画和街机模式。这一篇文章中,将实现云朵的随机绘制。 云朵类 Cloud 定义云朵类 Cloud: /** * 云朵类 * @param {HTMLCanvasEle...

    svtter 评论0 收藏0

发表评论

0条评论

curried

|高级讲师

TA的文章

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