游戏开发的许多方面都和玩游戏没有关系。显示说明、暂停游戏、级别之间的过渡和滚动游戏分数,这些都是游戏">开发人员必须在游戏本身以外实现的一些特性。
当游戏的灵感来临时,这些灵感中通常不包括显示高分数或级别之间的过渡的巧妙方式,开发人员会很自然地深入研究如何实现游戏机制,但对于游戏的基础架构却没有太多的想法。但在大多数项目中,如果想在开发后添加功能,所需的工作量比从一开始就添加功能要大得多。
在本系列的 上一期文章 中,我讨论了图形和动画,这些是 Snail Bait 游戏的基础内容。在本文中,我将临时转向去实现该游戏的一些基础架构。首先,我将 Snail Bait 的代码封装在一个 Game 对象中。最初实现该游戏时,我就是从这一步开始的,但在上一期文章中,我不想在对象中实现它们而混淆了对图形和动画的讨论,所以我将对 Game 对象的介绍推迟到了现在。
我还将告诉您如何暂停和冻结 Snail Bait,以及随后如何利用动画倒计时解冻并重启游戏。在文章的结尾,我会回到游戏机制的主题,向您展示如何通过处理键盘事件来控制跑步小人的垂直位置。
在本文中,您将学习以下内容:
将游戏函数封装在一个对象中。 暂停和恢复游戏。 当窗口
失去焦点时自动暂停游戏。 当窗口重新获得焦点时,利用动画的倒计时继续游戏。 暂时显示给用户的消息(被称为 toast)。 处理键盘输入。
在本文中,您将学习如何定义和实例化 JavaScript 对象,如何使用 CSS3 过渡,以及如何结合使用 setTimeout() 和这些过渡来实现分步动画。
游戏对象
在本系列文章中,到现在为止,我已经实现了所有 Snail Bait 函数,并将它们的几个变量定义为全局变量。当然,我们以后不会再这样做。如果您尚未了解全局变量的可恶之处,请参阅 参考资料,获得来自 Douglas Crockford 和 Nicholas Zakas 等 JavaScript 名人的支持论据。
从现在开始,我不再使用全局变量,而是将所有 Snail Bait 函数和变量封装在一个对象中。该对象由两部分组成,如清单 1 和清单 2 所示。
清单 1 是本游戏的构造函数,它定义了对象的属性:
清单 1. 本游戏的构造函数(部分清单)
var SnailBait = function (canvasId) { this.canvas = document.getElementById(canvasId); this.context = this.canvas.getContext('2d'); // HTML elements this.toast = document.getElementById('toast'), this.fpsElement = document.getElementById('fps'); // Constants this.LEFT = 1; this.RIGHT = 2; ... // Many more attributes are defined in the rest of this function};
清单 2 是本游戏的原型,它定义了对象的方法:
清单 2. 是本游戏的原型(部分清单)
SnailBait.prototype = { // The draw() and drawRunner() methods were // discussed in the second article in this series. draw function (now) { this.setPlatformVelocity(); this.setOffsets(); this.drawBackground(); this.drawRunner(); this.drawPlatforms(); }, drawRunner: function () { this.context.drawImage(this.runnerImage, this.STARTING_RUNNER_LEFT, this.calculatePlatformTop(this.runnerTrack) - this.RUNNER_HEIGHT); }, ... // Many more methods are defined in the rest of this object};
为了在整个系列中添加一些新特性,我需要添加和删除一些方法,以及修改一些方法来实现。表 1 列出了 Snail Bait 的方法,因为它们会在本文结尾处出现:
表 1. Snail Bait 在此开发阶段的方法(按调用顺序列出)
方法 描述 initializeImages() 初始化游戏的图像。背景图像的 onload 事件处理器调用 start()。 start() 通过调用 requestAnimationFrame() 启动游戏,在可以绘制第一个动画帧的时候,它调用了 animate() 方法。 splashToast() [1] 向玩家显示一个临时消息。 animate() [2] 如果游戏没有暂停,此方法将会绘制下一个动画帧,并调用 requestNextAnimationFrame() 来安排 animate() 的另一次调用。如果游戏暂停,
那么 animate() 会等待 200 毫秒,
然后调用 requestNextAnimationFrame()。 calculateFps() 根据自最后一个动画帧起所
经过的时间,计算帧速率。 draw() 绘制一个动画帧。 setTranslationOffsets() 为背景和平台设置过渡偏移。 setBackgroundTranslationOffset() 根据当前时间设置背景过渡偏移。 setPlatformTranslationOffset() 根据当前时间设置平台过渡偏移。 setPlatformVelocity() 将平台速度设置为背景速度的倍数,以产生轻微的视差效果。 drawBackground() 平移 Canvas 坐标系统,绘制背景两次,并将坐标系平移回它的原始位置。 drawRunner() [3] 使用 drawImage() 绘制跑步小人。 drawPlatforms() [3] 使用 2D 上下文的 strokeRect() 和 fillRect() 绘制矩形平台。 calculatePlatformTop() 针对给定轨道计算平台顶部的 Y 坐标(平台在三个水平轨道之一上移动)。 turnLeft() 将背景和平台向右滚动。 turnRight() 将背景和平台向左滚动。 togglePaused() [1] 切换游戏的暂停状态。
[1] 在本文中介绍
[2] 由浏览器调用
[3] 在本系列的下一期文章中将被替换
在本系列前两期文章中,我介绍过在 表 1 中列出的大部分方法,它们在当时只是函数。在本文中,我会讨论两个新的方法:togglePaused() 和 splashToast(),还会修改其他方法,比如 animate()。
清单 1 和 清单 2 中的 JavaScript 定义了一个函数和一个原型,但没有实例化一个 SnailBait 对象。我会在下一节中完成此操作。