通过这款休闲驾驶游戏中无限的程序化生成景色,探索 WebGL 的潜力。
Slow Roads 是一款休闲驾驶游戏,其重点是无休止的程序化生成的场景,所有这些场景都作为 WebGL 应用托管在浏览器中。对许多人来说,在浏览器的有限环境中,这种沉浸式体验似乎不合时宜。事实上,改变这种态度是本项目的目标之一。在本文中,我将详细介绍我在解决性能问题时所用的一些技术,以便突出 Web 3D 经常被忽视的潜力。
在浏览器中进行 3D 开发
发布“慢速行驶”功能后,我发现反馈中反复出现这样的评论:“我不知道在浏览器中可以这样做”。如果您也这样想,那么您绝不是少数派;根据 2022 年 JavaScript 现状调查,大约 80% 的开发者尚未尝试过 WebGL。我认为,错失如此多的潜力机会,尤其是在基于浏览器的游戏方面,实在有些可惜。通过“Slow Roads”,我希望进一步让 WebGL 成为焦点,或许还能减少对“高性能 JavaScript 游戏引擎”这一说法嗤之以鼻的开发者数量。
对于许多人来说,WebGL 可能看起来神秘而复杂,但近年来,其开发生态系统已大大成熟,并发展出功能强大且便捷的工具和库。现在,前端开发者可以比以往更轻松地将 3D 用户体验融入到自己的工作中,即使没有计算机图形方面的经验也是如此。领先的 WebGL 库 Three.js 是许多扩展的基础,包括将 3D 组件引入 React 框架的 react-three-fiber。现在,还有一些全面的基于 Web 的游戏编辑器,例如 Babylon.js 或 PlayCanvas,它们提供熟悉的界面和集成的工具链。
尽管这些库非常实用,但雄心勃勃的项目最终还是会受到技术限制的约束。对基于浏览器的游戏概念持怀疑态度的人士可能会强调 JavaScript 是单线程且受资源限制。不过,克服这些限制可以释放隐藏的价值:没有任何其他平台能提供浏览器所提供的即时无障碍访问和广泛兼容性。任何支持浏览器的系统的用户都可以一键开始游戏,无需安装应用,也无需登录服务。更不用说,开发者还可以使用强大的前端框架来构建界面或处理多人游戏模式的网络连接,从而享受优雅便捷的体验。我认为,正是这些价值使浏览器成为对玩家和开发者来说都非常出色的平台。正如《Slow Roads》所展示的那样,技术限制往往可以归因于设计问题。
在慢速道路上实现流畅的性能
由于“慢速行驶”的核心元素涉及高速运动和高成本的场景生成,因此在做出每项设计决策时,我都非常注重流畅的性能。我的主策略是从简化游戏玩法设计入手,以便在引擎架构中采用情境快捷方式。缺点是,这意味着为了追求极简主义,我们不得不舍弃一些很实用的功能,但最终会得到一个量身定制的极其优化的系统,可在不同的浏览器和设备上正常运行。
下面详细介绍了让 Slow Roads 保持精简的关键组件。
围绕游戏内容塑造环境引擎
作为游戏的关键组件,环境生成引擎不可避免地会产生高昂的费用,因此理应占据内存和计算预算的最大比例。这里所用的技巧是,在一段时期内安排和分配大量计算,以免因性能突增而中断帧速率。
环境由几何图形图块组成,这些图块的大小和分辨率(分类为“细节级别”或 LoD)因其相对于相机的显示距离而异。在具有自由漫游摄像头的典型游戏中,必须不断加载和卸载不同的 LoD,以详细呈现玩家选择前往的任何位置的周围环境。这可能是一个费时又浪费的操作,尤其是当环境本身是动态生成的。幸运的是,由于用户应保持在道路上的情境预期,因此在慢速道路上可以完全颠覆这一惯例。相反,可以为直接沿着路线两侧的狭窄走廊保留高细节几何图形。

道路本身的中线会在玩家到达之前生成,从而准确预测需要环境细节的确切时间和位置。这样一来,系统就会变得精简,能够主动安排耗费高昂的工作,在每个时间点只生成所需的最少数据,不会浪费精力在不会被看到的细节上。之所以能够采用这种方法,是因为这条道路是单一的非分支路径,这是一个很好的示例,说明了如何进行游戏权衡以适应架构捷径。

对物理定律挑剔
物理模拟是仅次于环境引擎计算需求的第二大需求。Slow Roads 使用自定义的极简物理引擎,该引擎会采用所有可用的捷径。
这里的主要节省方法是,首先避免模拟过多对象,通过忽略动态碰撞和可破坏对象等内容,从而采用极简禅宗风格。假设车辆会留在道路上,这意味着可以合理地忽略与非道路物体的碰撞。此外,将道路编码为稀疏中心线可实现一些巧妙的技巧,以便快速检测与路面和护栏的碰撞,所有这些都基于与道路中心的距离检查。这样一来,越野驾驶的费用就会更高,但这又是一个适合游戏情境的合理权衡。
管理内存占用
作为另一种受浏览器限制的资源,请务必小心管理内存,即使 JavaScript 会进行垃圾回收也是如此。这很容易被忽略,但在 60Hz 下运行时,即使在游戏循环中声明少量新内存也可能会积累成严重问题。除了在用户可能在多任务处理的情况下耗尽用户资源之外,大量垃圾回收可能需要数帧才能完成,从而导致明显的卡顿。为避免这种情况,可以在初始化时在类变量中预分配循环内存,并在每个帧中回收。

此外,经济高效地管理较大的数据结构(例如几何图形及其关联的数据缓冲区)也非常重要。在无限生成的游戏(例如《Slow Roads》)中,大多数几何图形都存在于一种跑步机上 - 一旦旧图形落后到远处,其数据结构便可存储起来,并在世界即将出现的部分中再次回收使用,这种设计模式称为对象池。
这些做法有助于优先执行精简型代码,但会牺牲一些代码简单性。在高性能环境中,请务必注意便捷功能有时会如何从客户端借用资源来为开发者提供便利。例如,Object.keys()
或 Array.map()
等方法非常方便,但很容易忽略的是,它们都会为其返回值创建一个新数组。了解此类黑盒的内部运作有助于优化代码并避免性能出现不良影响。
使用程序化生成的资源缩短加载时间
虽然游戏开发者应将运行时性能作为首要关注事项,但有关初始网页加载时间的常规公理仍然适用。用户在有意访问大型内容时可能会更宽容,但加载时间过长仍会对体验(如果不是用户留存率)造成不利影响。游戏通常需要纹理、声音和 3D 模型等大型资源,因此至少应在可以省略细节的地方仔细压缩这些资源。
或者,在客户端上以程序化方式生成资源可以从一开始就避免漫长的传输。这对连接速度缓慢的用户来说非常有益,并让开发者能够更直接地控制游戏的构成方式,不仅仅是初始加载步骤,还包括根据不同的质量设置调整细节级别。
“慢速道路”中的大部分几何图形都是程序化生成的,并且非常简单,自定义着色器会组合使用多种纹理来呈现细节。缺点是这些纹理可能是大型资源,但我们可以通过随机纹理等方法进一步节省资源,从小型源纹理中实现更精细的细节。在极端情况下,您还可以使用 texgen.js 等工具完全在客户端上生成纹理。同样,Web Audio API 还支持使用音频节点生成声音。
得益于程序化素材资源,生成初始环境平均只需 3.2 秒。为了充分利用较小的前期下载大小,系统会显示简单的启动画面来欢迎新访问者,并将耗时的场景初始化推迟到用户按下确认按钮之后。这还可以作为跳出会话的便捷缓冲区,最大限度地减少动态加载的资源传输浪费。
采用敏捷方法进行后期优化
我一直认为 Slow Roads 的代码库是实验性的,因此在开发时采用了极其敏捷的方法。在处理复杂且快速发展的系统架构时,很难预测可能出现重要瓶颈的位置。重点应放在快速实现所需功能(而不是整洁地实现),然后再回过头来优化真正重要的系统。Chrome 开发者工具中的性能分析器对此步骤非常有用,帮助我诊断了游戏早期版本的一些重大问题。作为开发者,您的时间非常宝贵,因此请务必避免花时间考虑可能不重要或多余的问题。
监控用户体验
在实现所有这些技巧时,请务必确保游戏在实际环境中能按预期运行。适应各种硬件功能是任何游戏开发的关键要素,但网页游戏可以同时定位到高端桌面设备和十年前的移动设备等更广泛的设备。解决此问题的最简单方法是提供相应设置,以适应性能分析器揭示的代码库中最可能的瓶颈(对于 GPU 密集型和 CPU 密集型任务)。
不过,在您自己的机器上进行性能分析只能涵盖这么多内容,因此,通过某种方式与用户建立反馈环非常有用。对于“Slow Roads”,我会运行简单的分析,以报告效果以及屏幕分辨率等情境因素。 这些分析数据会使用 socket.io 发送到基本 Node 后端,同时发送用户通过游戏内表单提交的任何书面反馈。在早期,这些分析捕获了许多重要问题,通过对用户体验进行简单的更改即可缓解这些问题,例如在检测到 FPS 持续偏低时突出显示设置菜单,或者在性能特别差时警告用户可能需要启用硬件加速。
前方限速道路
即使采取了所有这些措施,仍有相当一部分玩家需要在较低的设置下玩游戏,主要是使用缺少 GPU 的轻量级设备的玩家。虽然可用的画质设置范围导致性能分布相当均匀,但只有 52% 的玩家能达到 55 FPS 以上。

幸运的是,您仍然有许多机会来提升广告效果。除了添加更多渲染技巧来减少 GPU 需求之外,我希望近期尝试使用 Web Worker 并行生成环境,最终可能需要将 WASM 或 WebGPU 纳入到代码库中。我能够释放的任何空间都会让环境变得更加丰富多元,这将是项目剩余部分的持久目标。
作为一项业余爱好项目,Slow Roads 让我们深感满足,它证明了浏览器游戏可以如此精致、高效且受欢迎。如果我成功激发了您对 WebGL 的兴趣,请注意,Slow Roads 在技术层面只是 WebGL 全部功能的一个较浅层面的示例。我强烈建议读者探索 Three.js 展示,对于对 Web 游戏开发感兴趣的读者,我们诚邀您访问 webgamedev.com 社区。