前言

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

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

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

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

实现开场动画

首先修改 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,};

然后在 Runner 的 update 方法中调用上面定义的 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 值大于零,地面的位移也就有了。

到此,就实现了地面的开场动画:

查看添加的代码,戳这里

监听窗口 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"};

效果如下:

查看添加的代码,戳这里

实现街机模式

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

修改 Runner 上的 startGame 方法来调用 setArcadeMode 方法:

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

到这里街机模式就实现了,效果如下:

查看添加的代码,戳这里

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

上一篇下一篇
Chrome 小恐龙游戏源码探究二 -- 让地面动起来Chrome 小恐龙游戏源码探究四 -- 随机绘制云朵