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 小恐龙游戏源码探究三 -- 进入街机模式 #6

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

Chrome 小恐龙游戏源码探究三 -- 进入街机模式 #6

liuyib opened this issue Apr 16, 2019 · 0 comments

Comments

@liuyib
Copy link
Owner

liuyib commented Apr 16, 2019

前言

上一篇文章:《Chrome 小恐龙游戏源码探究二 -- 让地面动起来》 实现了地面的移动。这一篇文章中,将实现效果:1、浏览器失焦时游戏暂停,聚焦游戏继续。 2、开场动画。 3、进入街机模式。

街机模式的效果就是:游戏开始后,进入全屏模式。例如:

arcade-mode-show

可以看到,进入街机模式之前,有一段开场动画。我们先来实现一下这个开场动画。

这里先只实现地面的开场动画,小恐龙的开场动画后续再去实现。

实现开场动画

首先修改 CSS 样式:

.offline .runner-container {
  position: absolute;
  top: 35px;
- width: 100%;
+ width: 44px;
  max-width: 600px;
  height: 150px;
  overflow: hidden;
}

canvas 初始只显示 44px 的宽度。

然后在 Runner 的原型链上添加方法:

Runner.prototype = {
  // 游戏被激活时的开场动画
  // 将 canvas 的宽度调整到最大
  playIntro: function () {
    if (!this.activated && !this.crashed) {
      this.playingIntro = true; // 正在执行开场动画

      // 定义 CSS 动画关键帧
      var keyframes = '@-webkit-keyframes intro { ' +
          'from { width:' + Trex.config.WIDTH + 'px }' +
          'to { width: ' + this.dimensions.WIDTH + 'px }' +
        '}';
      // 将动画关键帧插入页面中的第一个样式表
      document.styleSheets[0].insertRule(keyframes, 0);

      this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
      this.containerEl.style.width = this.dimensions.WIDTH + 'px';

      // 监听动画。当触发结束事件时,设置游戏为开始状态
      this.containerEl.addEventListener(Runner.events.ANIMATION_END,
        this.startGame.bind(this));

      this.setPlayStatus(true); // 设置游戏为进行状态
      this.activated = true;    // 游戏彩蛋被激活
    } else if (this.crashed) {
      // 这个 restart 方法的逻辑这里先不实现
      this.restart();
    }
  },
  // 设置游戏为开始状态
  startGame: function () {
    this.playingIntro = false; // 开场动画结束
    this.containerEl.style.webkitAnimation = '';
  },
};

补充数据:

Runner.events = {
  // ...

+ ANIMATION_END: 'webkitAnimationEnd',
};

这里用到了小恐龙类里的数据,我们先临时定义一下(后面讲到小恐龙那一章时,需要把这段临时代码删除):

function Trex() {}

Trex.config = {
  WIDTH: 44,
};

然后在 Runnerupdate 方法中调用上面定义的 playIntro 方法:

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

    if (this.playing) {
      this.clearCanvas();

+     // 刚开始 this.playingIntro 未定义 !this.playingIntro 为真
+     if (!this.playingIntro) {
+       this.playIntro(); // 执行开场动画
+     }

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

    // ...
  },
};

解释一下这段代码:

if (this.playingIntro) {
  this.horizon.update(0, this.currentSpeed);
} else {
  deltaTime = !this.activated ? 0 : deltaTime;
  this.horizon.update(deltaTime, this.currentSpeed);
}

当程序走 if 逻辑的时候,this.horizon.update 接收到的第一个参数为 0,这样在这个方法内部计算出来的位移也是 0。所以只要还在执行开场动画,地面就不会移动。当程序走 else 逻辑的时候,开场动画执行完毕,此时 playIntro 函数已经执行结束,this.activated 值为 truedeltaTime 值大于零,计算出的地面位移就不再为 0

这样,就实现了地面的开场动画:

horizonline-animation-in-the-first

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

监听窗口 blur、focus 事件

接下来要实现的效果是:浏览器窗口失焦时游戏暂停,聚焦时游戏继续。

Runner 原型链上添加方法,来判断浏览器窗口是否失焦:

Runner.prototype = {
  // 当页面失焦时,暂停游戏,否则进行游戏
  onVisibilityChange: function (e) {
    if (document.hidden || document.webkitHidden || e.type == 'blur' ||
      document.visibilityState != 'visible') {
      this.stop();
    } else if (!this.crashed) {
      this.play();
    }
  },
  play: function () {
    if (!this.crashed) {
      this.setPlayStatus(true);
      this.paused = false;
      this.time = getTimeStamp();
      this.update();
    }
  },
  stop: function () {
    this.setPlayStatus(false);
    this.paused = true;
    cancelAnimationFrame(this.raqId);
    this.raqId = 0;
  },
};

startGame 方法中添加对 blur、focus 事件的监听:

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

+   window.addEventListener(Runner.events.BLUR,
+     this.onVisibilityChange.bind(this));

+   window.addEventListener(Runner.events.FOCUS,
+     this.onVisibilityChange.bind(this));
  },
};

补充数据:

Runner.events = {
  // ...

+ BLUR: "blur",
+ FOCUS: "focus"
};

效果如下:

add-blur-focus

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

实现街机模式

Runner 原型链上添加方法:

Runner.prototype = {
  // 设置进入街机模式时 canvas 容器的缩放比例
  setArcadeModeContainerScale: function () {
    var windowHeight = window.innerHeight;
    var scaleHeight = windowHeight / this.dimensions.HEIGHT;
    var scaleWidth = window.innerWidth / this.dimensions.WIDTH;
    var scale = Math.max(1, Math.min(scaleHeight, scaleWidth));
    var scaledCanvasHeight = this.dimensions.HEIGHT * scale;

    // 将 canvas 横向占满屏幕,纵向距离顶部 10% 窗口高度处
    var translateY = Math.ceil(Math.max(0, (windowHeight - scaledCanvasHeight -
        Runner.config.ARCADE_MODE_INITIAL_TOP_POSITION) *
        Runner.config.ARCADE_MODE_TOP_POSITION_PERCENT)) *
        window.devicePixelRatio;
    this.containerEl.style.transform = 'scale(' + scale + ') translateY(' +
        translateY + 'px)';
  },
  // 开启街机模式(全屏)
  setArcadeMode: function () {
    document.body.classList.add(Runner.classes.ARCADE_MODE);
    this.setArcadeModeContainerScale();
  },
};

补充数据:

Runner.config = {
  // ...
  
+ ARCADE_MODE_INITIAL_TOP_POSITION: 35,  // 街机模式时,canvas 距顶部的初始距离
+ ARCADE_MODE_TOP_POSITION_PERCENT: 0.1, // 街机模式时,canvas 距页面顶部的距离,占屏幕高度的百分比
};

Runner.classes = {
  // ...

+ ARCADE_MODE: 'arcade-mode',
};

定义 CSS 类 arcade-mode 里的样式:

.arcade-mode,
.arcade-mode .runner-container,
.arcade-mode .runner-canvas {
  image-rendering: pixelated;
  max-width: 100%;
  overflow: hidden;
}

.arcade-mode .runner-container {
  left: 0;
  right: 0;
  margin: auto;
  transform-origin: top center;
  transition: transform 250ms cubic-bezier(0.4, 0.0, 1, 1) .4s;
  z-index: 2;
}

最后调用 setArcadeMode 方法,就可以进入街机模式:

Runner.prototype = {
  startGame: function () {
+   this.setArcadeMode();      // 进入街机模式

    // ...
  },
};

效果如下:

arcade-mode

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

Demo 体验地址:https://liuyib.github.io/demo/game/google-dino/arcade-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