文章首发于个人 GitHub 博客
上一篇文章:《Chrome 小恐龙游戏源码探究二 -- 让地面动起来》 实现了地面的移动。这一篇文章中,将实现效果:一、浏览器失焦时游戏暂停,聚焦游戏继续。 二、开场动画。 三、进入街机模式。css
街机模式的效果就是:游戏开始后,进入全屏模式。例如:git
能够看到,进入街机模式以前,有一段开场动画。咱们先来实现一下这个开场动画。github
这里先只实现地面的开场动画,小恐龙的后续再去实现。
首先修改 CSS 样式:web
.offline .runner-container { position: absolute; top: 35px; - width: 100%; + width: 44px; max-width: 600px; height: 150px; overflow: hidden; }
让 canvas
初始只显示 44px
的宽度。canvas
而后在 Runner
的原型链上添加方法:segmentfault
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); + } } // ... }, };
解释一下这段代码:this
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
值大于零,计算出的地面位移就再也不为 0
。
这样,就实现了地面的开场动画:
查看添加或修改的代码, 戳这里
接下来要实现的效果是:浏览器窗口失焦时游戏暂停,聚焦时游戏继续。
在 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; }
最后调用 setArcadeMode
方法,就能够进入街机模式:
Runner.prototype = { startGame: function () { + this.setArcadeMode(); // 进入街机模式 // ... }, };
效果以下:
查看添加或修改的代码, 戳这里
Demo 体验地址:https://liuyib.github.io/blog/demo/game/google-dino/arcade-mode/
上一篇 | 下一篇 |
Chrome 小恐龙游戏源码探究二 -- 让地面动起来 | Chrome 小恐龙游戏源码探究四 -- 随机绘制云朵 |