前言
上一篇文章:《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
值为 true
,deltaTime
值大于零,地面的位移也就有了。
到此,就实现了地面的开场动画:
查看添加的代码,戳这里
监听窗口 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 小恐龙游戏源码探究四 — 随机绘制云朵 |