了解Edgegap的工作原理

了解Edgegap的工作原理

RUST - 多人游戏后台深度解析

已发布

已发布

已发布

最后更新时间

最后更新时间

最后更新时间

最近一次准确性审核

最近一次准确性审核

最近一次准确性审核

RUST - 多人游戏后台深度解析

关键洞察

关键洞察

关键洞察

  • 审查你的联网中间件:Rust 采用 RakNet 的经历表明,对一个成熟库进行开源分支并不代表没有隐藏漏洞。要验证你实际发布的具体版本,而不只是库的声誉。

  • 一个代码库,两个目标:Facepunch 使用预处理器标志,从同一个共享代码库编译服务器和客户端,消除了两者之间的偏差,并降低了长期维护开销。

  • 尽早投入编辑器测试:为 Unity 编辑器构建一个监听服务器模式,消除了繁琐的多步骤测试流程,并让网络代码迭代速度大幅提升——这一原则同样适用于任何多人项目。

  • 部署基础设施不是可选项:当 Rust 爆红时,团队不得不在压力下重建整个构建流水线;这段经历直接说明,应在发布前而不是之后自动化部署。

  • PVS 剔除让大型世界保持可扩展性:Rust 基于网格的可见性系统确保玩家只会收到附近单元格的状态更新,使带宽和 CPU 成本与局部玩家密度成正比,而不是与服务器总规模成正比。

Facepunch Studios 创始人 Garry Newman 在其个人博客 garry.net 的一系列文章中,详细记录了《Rust》的网络和后端架构。这些文章写于 2013 年至 2016 年之间——正值游戏经历动荡的早期开发及出人意料的爆火上线时期。内容涵盖了网络栈的选择、服务器/客户端代码架构、编辑器测试工作流,以及几乎在一夜之间必须重构的部署流水线。其中最相关的两篇文章是《Rust's Networking》和《The Rust backend》,并辅以同一时期 Facepunch 官方开发博客(devblog)的内容作为补充。

这些文章间接揭示了任何多人游戏工作室(从小型独立团队到不断壮大的在线服务型游戏)迟早都将面临的抉择。

选择网络库(并盲目信任它)

Newman 在构建《Rust》的网络层时,选择了 RakNet。“Raknet 是一个经过检验的成熟网络解决方案,”他写道,并指出它在许多已发行的游戏中有过长期的成功记录。当时 Oculus 收购了它并将其开源,这在当时看来是个好消息。

然而,事实并非完全如此。

Newman 注意到公开仓库中存在漏洞,并怀疑与支持那些已发行游戏的内部版本相比,这个开源版本“肯定被删减了大量内容”。一个在行业内享有盛誉的库,却以一种不稳定的社区版状态呈现出来。

这带来的实际教训并不是要避开第三方网络库,而是要针对你实际的用例去验证你具体要发布的版本,而不是一味依赖该库过去的名声。

商业工具的开源分支需要额外的仔细审查。Newman 本人也提到,计划最终用 Valve 的 Steam Networking Sockets 来取代 RakNet,这很好地提醒了我们:网络栈从一开始就应该被视为可替换的组件。

同样值得注意的是,这篇文章写于 2016 年。此后,行业格局发生了巨大变化。尤其是 Unity 开发者,现在有大量易于获取且维护良好的网络代码(netcode)方案可供选择。如果想了解当前的可用方案,例如 Edgegap 的 Unity 网络代码解决方案指南就是一个非常有用的起点。

服务器与客户端共享代码

《Rust》中最持久的架构决策之一,就是不把服务器和客户端视为独立的代码库,而是将其作为同一份代码的两个编译目标。整个代码库在编译时会定义 SERVER 或 CLIENT,而特定于某一侧的逻辑则通过这些预处理器标记进行隔离。Newman 是从 Valve 的 Source 引擎中借鉴了这一模式,他在那里成长为一名开发者。

这避开了一个常见的痛点:随着游戏的成长,两个原本相同的代码库逐渐分化并产生偏差。而采用这种模式,共享系统始终保持共享,错误修复可以同时应用到两个编译目标。这种“一个游戏,两个视图”的思想模型,能够清晰地对应多人游戏系统的实际运行方式。

折中之处在于纪律性。

在编写代码时,必须时刻清楚它在哪个上下文中运行。边界情况需要小心处理:例如,在编辑器的监听服务器(listen server)模式下,一个物理对象会同时以服务器和客户端的形式存在,必须防止它们彼此发生碰撞干扰。这些问题是可以解决的,但它们不会自己解决。

监听服务器:消除测试中的摩擦

用 Newman 的话来说,《Rust》最初用于测试网络代码的开发工作流简直是“一场该死的噩梦”。测试任何改动都需要编译一个独立的服务器版本、在后台启动它,然后从编辑器客户端进行连接。这种流程在旧架构中根深蒂固,难以轻易更改,这也是团队选择彻底重构游戏的主要原因之一。

在重构版本中,Unity 编辑器中的监听服务器支持从第一天起就成为了高优先级任务。其解决方案是同时定义 SERVER 和 CLIENT,使编辑器在不需要任何外部进程的情况下,自行托管并加入自己的游戏。Newman 特别指出,该实现必须忠实地模拟真实的网络环境。如果通过走捷径让服务器和客户端直接共享变量,将会掩盖那些只在实际网络版本中才会暴露的漏洞。

即使具体的工具已经更新换代,其底层原则依然适用。与 2013 年相比,如今编译和部署用于本地测试的专用服务器要容易得多。容器化和定制插件已经分担了大部分痛苦。例如,Edgegap 的 Unity 插件包含一个构建工具,即使对于大型项目,也能将专用服务器的编译时间缩短至几分钟;而 Unreal Docker 扩展则完全免去了从源码构建 Unreal 的麻烦,将过去需要数小时的过程缩短至 8 分钟左右。其目标与 Newman 的监听服务器一致:尽快将一个可运行的服务器呈现在开发者面前,以便网络代码漏洞在迭代过程中就暴露出来,而不是等到上线之后。

在需要之前就建立好部署流水线

在《Rust》于 2013 年底爆火之后,团队花了一周时间从头开始重构了他们的整个部署流程。“在此之前,部署新版本速度缓慢且漏洞百出,”Newman 写道。构建版本是在“某个家伙的电脑上”编译的,整个过程“简直是一场折磨”。

替换后的系统采用了 Jenkins CI/CD,由 SVN 提交自动触发。服务器和客户端的独立构建任务并行运行,将构建时间从大约 40 分钟缩短至 10 分钟以内。资源被分割成 60 多个资源包(bundles),每个控制在 5 MB 以下,以便浏览器可以像缓存图片一样缓存它们。每个资源包都使用 CRC 哈希命名,因此只有当文件内容确实发生变化时才会触发重新下载,而不是每次部署都重新下载。正如 Newman 所解释的,“我们不想让玩家每次都下载 300MB 的内容。”

系统奏效了。但这必须在没人完全预料到的爆火上线后,在巨大的压力下仓促建成。

Newman 对重构前状态的描述与重构后一样具有启发性。构建版本在一台开发者的个人电脑上编译,整个过程苦不堪言。而替代它的工具却很简单:Jenkins、SVN、S3、CRC 哈希,没有任何奇特的东西。真正产生改变的是将这些工具落实到位并自动运行,从而使团队能够以形势所要求的速度发布更新。

PVS 剔除:只发送玩家看得见的内容

在 Facepunch 早期的开发博客中,有一篇在技术上极具启发性的文章,描述了在《Rust》的网络代码中引入潜在可见性集(PVS)剔除系统。该方法基于网格:世界被划分为若干单元格,每个玩家只接收自己所在单元格及周围单元格的状态更新。

这个概念很简单,但实现起来却不然。你需要跟踪在单元格之间移动的对象、向每个受影响的玩家发送进入和离开通知、处理同时跨越多个单元格的玩家,并管理实体处于运动状态时出现的所有边界情况。正如 Newman 在开发博客中所写,“这里面有许多活动部件。”

但回报是巨大的。如果没有可见性剔除,向每个连接的玩家广播完整世界状态的服务器会迅速达到带宽和 CPU 的瓶颈。PVS 将这些开销控制在与局部密度成正比的范围内,而不是与服务器总人数成正比。这正是大型世界生存游戏在大规模运营时得以生存的关键,而且这一技术不仅适用于《Rust》这一特定类型。任何拥有庞大可探索世界的多人游戏,在考虑更复杂的优化之前,都应该具备某种形式的状态相关性过滤。

—

本文基于并引用了 Garry Newman 发表在 garry.net 上的原创博客文章(链接 1 和 链接 2)以及 Facepunch 的官方《Rust》开发博客。

原创内容的所有权利归其各自所有者所有。

书写者

Edgegap团队

Get your Game Online Easily & in Minutes

立即开始集成!

轻松在线游戏
且在几分钟内

Get your Game Online Easily & in Minutes