这款休闲驾驶游戏采用程序随机生成的无限场景,发掘 WebGL 的潜力。
Slow Roads 是一款休闲驾驶游戏,侧重于无休止地按程序生成的场景,均以 WebGL 应用的形式托管在浏览器中。对许多人来说,在浏览器的有限上下文中,这种密集的体验似乎不合适。事实上,纠正这种态度一直是我此项目的目标之一。在本文中,我将详细介绍我用来克服性能障碍的一些技术,以重点介绍 3D 网络在网络领域经常被忽视的潜力。
浏览器中的 3D 开发
发布 Slow Roads 后,我看到反馈中反复出现这样一条评论:“我不知道在浏览器中可以实现这一点”。相信您一定不会属于少数群体;根据 2022 State of JS 调查,大约 80% 的开发者尚未尝试过 WebGL。我觉得可能错失了这么多潜力,这感觉很遗憾,尤其是在基于浏览器的游戏方面。通过“Slow Roads”,我希望让 WebGL 更受瞩目,或许能减少对“高性能 JavaScript 游戏引擎”一句话犹豫不决的开发者人数。
对很多人来说,WebGL 看起来很神秘和复杂,但近年来,其开发生态系统已大幅成熟,已经发展成功能强大且非常便捷的工具和库。现在,即使没有计算机图形相关经验,前端开发者也可以更轻松地将 3D 用户体验融入工作中。Three.js 是领先的 WebGL 库,是许多扩展的基础,包括将 3D 组件引入 React 框架的 react-three-fiber。现在,还有综合性 Web 的游戏编辑器(如 Babylon.js 或 PlayCanvas),它们提供熟悉的界面和集成工具链。
尽管这些库的效用显著,但雄心勃勃的项目最终会受到技术限制的约束。对基于浏览器的游戏概念的质疑可能强调 JavaScript 是单线程且资源受限。但克服这些局限,解锁了隐藏的价值:没有其他平台能提供相同的即时可访问性和通过浏览器实现的大规模兼容性。用户在任何支持浏览器的系统上,只需点击一下即可开始播放,无需安装应用,也无需登录服务。更不用说,开发者不但拥有强大的前端框架以用于构建界面,或能够为多人游戏模式处理网络,还能为开发者带来诸多便利。在我看来,这些价值观让浏览器成为了对玩家和开发者等同样优秀的平台。正如 Slow Roads 所述,技术限制往往可归结为设计问题。
在缓慢道路上实现流畅性能
由于“慢速道路”的核心元素涉及高速运动和成本高昂的场景生成,因此我的每个设计决策都凸显了对流畅性能的需求。我的主要策略是先采用简洁的游戏内容设计,允许在引擎架构内采用上下文快捷方式。从缺点来看,这意味着牺牲一些不可或缺的功能来追求极简主义,但最终您会得到一个定制的超优化系统,能够在不同的浏览器和设备上完美运行。
下面详细介绍一下让“慢路”保持顺畅运行的关键要素。
围绕游戏内容塑造环境引擎
作为游戏的关键组成部分,环境生成引擎的成本不可避免地很高,因此其内存和计算预算所占的比例最大是合理的。此处使用的技巧是在一段时间内调度和分配繁重的计算,以免因性能峰值而中断帧速率。
环境由几何图形图块组成,这些图块的大小和分辨率不同(分类为“细节级别”或 LoD),具体取决于图块在相机上显示的距离。在配备自由漫游摄像头的典型游戏中,必须不断加载和卸载不同的 LoD,以详述玩家前往的任何位置的周围环境。这可能是一项成本高昂且浪费的操作,尤其是在环境本身是动态生成的时。幸运的是,在“慢速道路”中,这一惯例完全可以被颠覆,这要归功于符合相关情境的预期,即用户应该继续在路上。相反,可以为直接沿路线两侧的狭窄走廊预留精细的几何图形。
道路本身的中线在玩家到达之前很长,从而可以准确预测需要环境细节的准确时间和地点。由此,您可以构建一个精简系统,主动安排开销大的工作,仅生成每个时间点所需的最少工作,而无需白白费力地处理看不见的细节。这种方法之所以能够实现,是因为道路是一条非分支路径,这是一个很好的例子,很好地权衡了游戏内容的特点,同时适应了不同的建筑风格。
严格遵守物理定律
除了环境引擎的计算需求外,还有物理模拟。Slow Roads 使用最小的自定义物理引擎,能够获取所有可用的快捷方式。
这样做最主要的节省是,一开始就避免模拟过多对象,而是采用极简的禅意环境,并打折动态碰撞和可破坏对象等因素。假设车辆会停在道路上,意味着可以合理地忽略与越野物体的碰撞。此外,将道路编码为稀疏中线有助于实现快速检测与路面和护栏的碰撞检测,所有这些操作均基于到道路中心的距离检查结果。越野驾驶的代价变得越来越高,但这是适合游戏情境的公平权衡的另一个示例。
管理内存占用量
作为另一个受浏览器限制的资源,务必要谨慎管理内存,尽管 JavaScript 是垃圾回收的。这很容易被忽视,但在以 60Hz 运行时,即使是在游戏循环中声明少量新内存,也可能会滚雪球球技,导致严重问题。除了在用户可能同时处理多任务的情境中消耗用户资源之外,大型垃圾回收可能需要数帧才能完成,从而导致明显卡顿。为了避免这种情况,可以在初始化时在类变量中预先分配循环内存,并在每个帧中回收循环内存。
经济型管理较重的数据结构(例如几何图形及其相关数据缓冲区)也非常重要。在《慢路》这样一个无限生成的游戏中,大部分几何图形都存在于跑步机上。当旧物品落在远处之后,它的数据结构可以再次存储和回收用于即将到来的世界,这种设计模式称为“对象池化”。
这些做法有助于优先实现精益执行,但代价是代码简洁性。在高性能环境中,请务必注意便捷功能有时会如何从客户端借用开发者的利益。例如,Object.keys()
或 Array.map()
等方法非常方便,但很容易被忽略,每个方法都会为其返回值创建一个新数组。了解此类黑盒子的内部工作原理有助于强化代码,避免欺骗性的性能损失。
利用程序生成的资源缩短加载时间
虽然运行时性能应该是游戏开发者的主要关注点,但关于初始网页加载时间的惯用公理仍然适用。如果用户是有意访问大量内容,其宽容程度可能会更高,但如果加载时间过长,即使不提高用户留存率,也会对体验产生负面影响。游戏通常需要大量资源,包括纹理、声音和 3D 模型,在可以保留细节的地方至少应仔细压缩这些资源。
或者,通过程序为客户端生成资产,一开始就可以避免长时间的传输。这对于网速较慢的用户来说是一个巨大的优势,可让开发者更直接地控制游戏的构建方式 - 不仅仅局限于初始加载步骤,还包括针对不同质量设置调整细节级别。
“慢速道路”中的大部分几何图形都是由程序随机生成的,而且经过简化,系统会使用自定义着色器组合多个纹理来呈现细节。其缺点是,这些纹理可能是大量资源,但通过随机纹理等方法能够从小源纹理中实现更大细节,这还有更多机会来节省成本。在极端程度上,您还可以使用 texgen.js 等工具完全在客户端上生成纹理。对于音频也是如此,借助 Web Audio API,您可以使用音频节点生成声音。
得益于程序化资产的优势,平均只需 3.2 秒即可生成初始环境。为了充分利用预先下载的数据量,一个简单的启动画面会问候新访问者,并将成本高昂的场景初始化推迟到用户确认按下按钮之后。这还可以充当跳出会话的便捷缓冲区,从而最大限度地减少动态加载的资源的无用传输。
采用灵活的方法进行后期优化
我一直认为 Slow Roads 的代码库是实验性的,因此采用了一种极其敏捷的开发方法。在使用快速发展的复杂系统架构时,可能很难预测重要瓶颈可能发生的位置。重点应放在快速(而非彻底)实现所需功能上,然后往回推算,以优化真正重要的系统。在执行此步骤时,Chrome 开发者工具中的性能分析器非常有用,可帮助我诊断早期版本游戏的一些重大问题。作为开发者,您的时间非常宝贵,因此切勿将时间花在可能无关紧要或多余的问题上。
监控用户体验
在实现所有这些技巧时,请务必确保游戏按预期运行。提供一系列硬件功能是任何游戏开发必不可少的方面,但网页游戏可以面向更广泛的范围,同时涵盖高端桌面设备和已有十年历史的移动设备。最简单的方法是提供设置来调整代码库中最有可能出现瓶颈的设置,同时适用于 GPU 密集型和 CPU 密集型任务,如性能分析器所示。
不过,在您自己的机器上进行性能分析只能涵盖很多方面,因此以某种方式结束与用户的反馈环是有价值的。对于“慢速道路”,我会运行简单的分析,报告性能以及屏幕分辨率等情境因素。这些分析数据会通过 socket.io 以及用户通过游戏内表单提交的任何书面反馈发送到基本的 Node 后端。早期,这些分析发现了许多重要问题,可以通过对用户体验进行一些简单的更改来缓解,例如在检测到始终低的 FPS 时突出显示设置菜单,或者在性能特别糟糕时警告用户可能需要启用硬件加速。
前方缓慢
即使在采取了所有措施之后,仍有很大一部分玩家需要在较低的设置下运行,主要是那些使用缺少 GPU 的轻量级设备的设备。虽然各种可用的质量设置会使性能分布相当均匀,但只有 52% 的玩家可以实现超过 55 FPS 的帧速率。
幸运的是,仍有许多机会可以节省性能。除了添加更多渲染技巧以减少 GPU 需求之外,我希望在短期内让 Web 工作器并行生成环境,并最终发现需要将 WASM 或 WebGPU 整合到代码库中。我能腾出的任何提升空间都能用于打造更丰富、更多元化的环境,这是项目剩余部分的长远目标。
随着业余爱好者项目的开发,《Slow Roads》以令人惊叹的方式展现了精心设计、性能卓越且广受欢迎的浏览器游戏之美。如果我成功地激发了您对 WebGL 的兴趣,请相信,从技术层面来看,慢道路只是其全部功能的一个相当浅显的示例。我强烈建议读者探索 Three.js 展示区,并且特别欢迎对网页游戏开发感兴趣的读者加入 webgamedev.com 社区。