Bubble Hell Duel 是一款多人 HTML5 空战游戏。游戏的目的是躲避对手发射的泡泡,同时还击。这款游戏主要作为学习的原型编写,源代码可以在 GitHub 上获取。你可以试着在单人或多人模式下玩这个游戏 这里。目前游戏没有声音效果,但使用了 CreateJS 和 TogetherJS。
在这篇文章中,我想分享一些我在开发游戏时的经验。如果你同意或有其他建议,请在评论中分享你的想法。
游戏引擎
在开发 2D 游戏时,你可以自己编写引擎,也可以使用一些很棒的库。在花了一些时间研究各种选择之后,我决定使用 CreateJS。由于我有一些 Flash 的经验,CreateJS 很适合我的需求,因为学习曲线并不陡峭。我还想使用一些 Flash 动画,CreateJS 支持此功能。我将在本文后面详细介绍动画。
作为一名 C++ 开发人员,我认为 emscripten 也是一个不错的选择。它允许将 C/C++ 代码编译为 JavaScript,可以在浏览器中执行。我认为静态类型检查和编译时优化在开发大型代码库时非常有用。我之前使用过 emscripten,它运行良好,但是对于这个项目,我想要 JavaScript 快速便捷的原型设计功能。我还想扩展我的 JavaScript 知识。
我想提几个其他看起来非常有趣的库:Cocos2d-x 正在进行 emscripten 移植,并且他们已经支持 HTML5 绑定。我还喜欢 pixi.js,因为它提供 webGL 渲染器,但当浏览器不支持 webGL 时也支持 Canvas 回退。
C++ 与 JavaScript
起初,我对 JavaScript 的性能有点担心,这就是为什么我难以决定是使用 CreateJS 还是 emscripten。幸运的是,一个简单的基准测试表明,带有约 400 个球的屏幕上的简单碰撞检测算法仍然可以达到 40 多帧/秒,这足以满足我的简单实验。
作为一个使用 C++ 比 JavaScript 多的人,我喜欢我能够快速地将我的想法转化为代码并在多个浏览器上进行测试。另一方面,调试 JavaScript 并不是很舒服。C++ 编译器非常擅长指出拼写错误和其他导致运行时问题的错误。虽然“use strict”指令和其他机制(如闭包编译器)有其目的,但它们对我来说并没有多大帮助,尤其是在变量变得未定义时。相比之下,寻找错误原因可能有点困难。
例如,我遇到了以下调试问题。我使用浮点数表示坐标和其他几何值(如角度)。这些值通过 TogetherJS.send 方法传递给另一个玩家,以进行同步。
var player = { x: 10.0, y: 10.0 };
TogetherJS.send({type:'sync',x:player.x,y:player.y});
TogetherJS.hub.on('sync', function(msg){
enemy.x = msg.x;
enemy.y = msg.y;
});
这有效,但以这种方式发送了大量的十进制数,因此我决定降低精度。
TogetherJS.send({type:'sync', x:Math.round(player.x), y:Math.round(player.y) });
然后我认为整数对于碰撞检测可能不够准确,因此我在消息中添加了更多位数。
TogetherJS.send({type:'sync', x:player.x.toFixed(2), y:player.y.toFixed(2) });
虽然这似乎是一个合理的解决方案,但它实际上引发了一个很难找到的错误,直到我在实现了一些其他功能之后测试游戏时才注意到它。我注意到在玩游戏时,对手永远不会移动。
我花了几个小时调试才找到原因。我认为如果使用 C++,我不会犯这个错误。
如果你想看看这个错误是如何发生的,可以查看这个 jsFiddle 项目。看一下三个 canvas 标签的输出,你会注意到第三个 canvas 包含错误。这个问题的发生是因为 toFixed 返回的是字符串表示形式。
我不确定使用闭包编译器是否能避免这个问题,但我确实在 另一个项目 中发现它对优化确实有所帮助。
使用 Flash 进行动画制作
与大多数游戏一样,我想使用大量的动画。我对在 Flash 中创建动画非常熟悉,并且发现 CreateJS 支持多种方法来使用 Flash 动画并将它们呈现在 HTML5 中。CreateJS 是一套用于创建交互式 HTML5 内容的库和工具。因此,通过使用 CreateJS,我可以使用我的动画以及使用其他库进行循环处理、资源管理以及将来的声音操作。要快速了解 CreateJS,请查看这个 视频。
CreateJS(现在由 Mozilla 赞助)为 Flash 动画提供了很好的支持。
使用 CreateJS 在 HTML5 中使用 Flash 动画有两种方法。第一种选择是直接以一种可以访问所有元素原始形式(包括路径、转换和补间动画)的方式导出 Flash 动画。这种方法的优点是它会生成更小的文件,CreateJS 允许你将它们传输到客户端的精灵表中,以实现更快的渲染。Adobe Flash CS6 提供了 CreateJS Toolkit 插件,允许设计人员将动画的所有内容导出到 HTML5 文件。这通常会生成一个包含所有图形和补间动画的 JavaScript 文件,一个 HTML 文件和一组图像文件。你可以在浏览器中打开 HTML 文档,并查看动画。
另一种选择是将动画导出到精灵表中,即包含所有帧的图像,以及描述每个帧的位置和大小的 JavaScript 文件。这些文件可以通过 CreateJS 中的 SpriteSheet 类轻松地集成到基于 HTML 的游戏或应用程序中。这是我在这个游戏中使用的方法。要查看我在其中使用 SpriteSheet 的代码,请查看这个 链接。如果你想了解更多关于这种方法的信息,可以查看这个 视频。
我还应该注意,你可以使用名为 Zoë 的工具,直接将 Flash 动画导出到精灵表或 JSON 文件中。
上面的图像是我在游戏中使用的一个精灵表的示例,它是按照上述方法生成的。原始图像来自游戏《东方非想天则 ~ Scarlet Weather Rhapsody》,可在 http://www.spriters-resource.com 获取。
使用 TogetherJS 实现多人游戏
在我代码的第一个版本中,游戏不是多人游戏。最初,它是一个单人弹幕游戏,有一个随机在屏幕上移动的 Boss 敌人。在我屈服于毁灭性的火力之前,我撑不过 30 秒。这很有趣,我认为多人游戏会很刺激。
Together.js 发布后不久,我就听说过它。jsFiddle 项目由 Together.js 提供支持,并提供了一个令人印象深刻的协作模式。这促使我在我的游戏中使用 Together.js。Mozilla 还提供了一个默认的集线器服务器,简化了创建基于 Web 的多人游戏的过程。要详细了解 Together.js,请务必查看这篇文章 文章。
将 Together.js 集成到我的游戏中既简单又舒适,因为它与其他事件分派器/侦听器框架的工作方式类似。
使用 Together.js,我能够在游戏中实现随机匹配和仅限邀请的多人模式。在设计通信协议时,我确实遇到了一些设计挑战,我必须克服它们。
首先,我没有添加代码来防止使用双向通信作弊,并假设玩家之间存在一定程度的信任。在当前的游戏设计中,所有玩家的碰撞检测都在本地完成。理论上,如果你阻止相应的訊息,就可以掩盖你受到伤害的事实。
另一个我略作处理的方面是,敌方化身的泡泡是在本地随机生成的。这意味着你的人物化身看到的泡泡不一定与你的对手看到的一样。
实际上,这些捷径都不会影响游戏的乐趣。
我确实遇到了几个关于 Together.JS 的问题或注意事项。
- 我没有找到在 Together.js 中禁用光标更新的方法。虽然这在协作工具中很有用,但我并不需要在我的游戏中使用它。
- 我以不对称的方式使用 Together.js,其中两个玩家都将自己视为红色的裙子化身(灵梦)。这使得将玩家更容易放置在屏幕底部,将对手放置在顶部。这也意味着,当你在对手的游戏视图中移动主要玩家时,你的移动会被视为对手的移动,反之亦然。
犯错的乐趣
游戏中有两个视觉效果,它们是意想不到的惊喜。
- 当一轮结束并出现“你赢了”或“你输了”的消息时,时间会暂停几秒钟。这就像戏剧性的暂停。
- 当释放蓄力攻击时,子弹会被固定,然后逐渐吹向敌人。
这些效果都没有按这种方式设计。我并不想要暂停,我希望子弹在释放后继续绕着玩家旋转。但是,我犯了错误,结果似乎比我预期的要好得多,因此它们最终被保留了下来。
结论和未来计划
学习新事物总是有趣的。我喜欢能够快速地进行原型设计和可视化。将来,我可能会为子弹幕添加更多模式,以及一些声音效果。此外,我可能还会绘制更多背景图像,或者可能对它们进行动画处理。
在开发游戏时,我确实意识到,为了获得自然直观的体验,需要比我预期的付出更多努力。这正是我在玩游戏时一直理所当然的事情。
代码是开源的,所以请随意fork和玩耍。如果您有任何关于改进游戏或现有代码的建议,请务必发表评论。
关于 Lu Wang
pdf2htmlEX 和 scanmem/GameConqueror 背后的快乐黑客。
16 条评论