在今年的 Unite 大会上,腾讯天美技术专家田亚涛和 Unity 中国的软件工程师司天语为大家带来了《合金弹头:觉醒》项目的客户端与服务端一体化方案,其中包括 DS 专用服务器进程弹性启动、多房间管理及高性能连接能力等。

田亚涛:大家好,我是来自天美 J1 工作室的田亚涛。首先感谢团结引擎团队对我们技术方案的认可,也非常高兴能在此与大家交流我们的实践经验。
我自 2014 年加入腾讯以来,先后完整参与了《魂斗罗:归来》与《合金弹头:觉醒》的研发工作,长期专注于 GamePlay 体系、实时联机同步以及 DS(Dedicated Server)架构相关的研发与优化。《合金弹头:觉醒》作为一款 2023 年上线的横版射击手游,目前已在国内与多个海外地区稳定运营。
本次分享将重点介绍两个方向:网络同步框架以及基于 Unity 的 DS 引擎架构优化。在《魂斗罗:归来》时期,我们探索并沉淀了一套基于 Unity 引擎的前后端一体化技术体系,而在《合金弹头:觉醒》中,我们在此基础上进一步迭代,对多项关键技术进行了系统化打磨与演进。
公平精准的网络同步框架
对于《合金弹头:觉醒》,我之所以在前面特别强调横版射击,是因为与第一人称或第三人称射击不同,横版射击的子弹飞行路径和命中过程对玩家是完全可见的,命中是否准确非常直观。因此,只要画面表现与最终伤害结果出现任何不一致,玩家都会立即察觉。在第一人称或第三人称射击中,受击反馈多以闪红等效果呈现,弹道细节并不会完全暴露,所以玩家对命中精度的敏感度没有这么高;但在横版射击中,这种差异被无限放大。
也正因为如此,早期常用的“攻击者优先、服务器权威”机制在《合金弹头:觉醒》中难以直接落地,会出现“看似没有被击中却掉血”或“明明命中却没有伤害”的情况,让玩家误以为是外挂或者同步异常。在研发的整个过程中,我们在联机相关(包括 DS)的部分投入了大量时间,希望在网络环境不可控的前提下,尽可能在“实时性”和“一致性”之间找到平衡,让玩家看到的画面与游戏真实的判定尽可能一致,也就是实现我们一直追求的“所见即所得”。

要解决“同步”问题,首先必须具备一套可靠且高性能的网络基础设施,这是整个联机技术的第一层能力。我们需要一个高效的网络中间件,它的核心职责是承载数据传输,既要支持可靠的 UDP 通道,也要支持非可靠的 UDP 通道,以满足不同类型同步数据对实时性和可靠性的要求。在此基础上,我们在联机框架层提供了 RPC 与状态同步两套能力,使上层逻辑能够在统一的通信抽象下完成指令发送、状态更新和关键事件传递,为后续所有同步机制提供一致而可靠的通信支持。

对于《合金弹头:觉醒》这类高实时性的同步游戏而言,每秒 30 次的同步频率对带宽要求非常高。在带宽优化上,除了常规处理,我们也针对同步内容本身做了细致打磨,包括数据类型定制、浮点精度控制以及差异化更新等。同时,在策略层面,我们结合对象池与缓冲机制,并在 MTU 自适应、合包和批量发送等方面进行了优化,以尽量降低同步成本并提高传输效率。

在带宽优化之后,我们进一步处理流量优化。在压缩和增量同步之外,我们希望将整体流量控制在可接受的量级上。为此,除了历史记录缓存,我们也结合序列号机制,并基于客户端与网络实际情况做了一些定制化策略,使整体流量的增长趋势更可控,也更符合我们的预期。

游戏中也大量使用了输入缓存和命令队列,这类逻辑由于触发频率极高,对 GC 的敏感度非常强。如果在这些高频路径上出现额外分配,就会加速触发 GC,从而导致主线程暂停,直接影响实时联机体验。为此,我们针对这些模块进行了优化,将无谓分配尽量压缩甚至消除,以在同步过程中尽可能减少 GC 对客户端流畅度的影响。

有了前面的优化,所有数据与策略本身也需要被有效监控。因此,我们在研发阶段和外网环境中都建立了一套完整的监控体系,用来确保网络质量与实际体验始终符合预期。同时,这些监控结果也为后续的动态调整提供了可靠的数据依据,使我们能够更及时地评估网络表现并进行针对性优化。

在此之外,我们还设计了自适应网络能力,会根据前面提到的策略与实时监控数据,动态选择更适合当前网络状况的同步方案。无论是在影子跟随还是航位推测等模式下,系统都会优先保证玩家在不同网络质量下都能获得尽可能平滑的体验。

讲完网络层,我们再看同步框架层面。项目在立项阶段首先完成了逻辑和表现的分离,这不是传统的 UI MVC,而是将一个 Game Object 拆分为逻辑体和表现体:逻辑体用于处理追帧、各种同步拉扯等核心逻辑,而表现体在动画、特效等层面保留更高的平滑性和表现灵活度。基于这样统一的逻辑层,我们在同步模式上采用了帧同步与状态同步的融合方案,并结合早期《魂斗罗:归来》的经验以及单机、联机都在 Unity 上统一研发的优势,使整体同步结构更清晰、更一致。在此基础上,我们从对时模式转向多端对帧,让所有端共享一致的帧号,使后续的同步行为与策略调整都能以统一帧号为依据。

接下来,看一下角色同步。对于这样一款高实时性的同步游戏而言,手感始终是第一优先级,《合金弹头:觉醒》也采用了客户端先行的方式。这里的 1P 指本地玩家自身;2P 指服务器或 DS;3P 则是我所看到的其他玩家。基本流程是本地先执行移动,同时同步地向服务器发送移动指令;服务器在收到后会进行校验,如果校验不通过,会下发纠正并触发相应的拉扯。对于 3P 来说,表现更多是基于服务器下发的缓存数据,再通过平滑插值来重建对方的移动轨迹。但由于是横版射击,3P 的画面本质上看到的仍然是一定程度的历史位置,因此我们也会做一定程度的预测补偿,让对方角色的表现尽量接近实时。

前面提到有预测就一定会有预测失败的情况,那失败该怎么处理呢?如果只是简单地把本地移动包发给 DS,由 DS 检测到不一致后直接把角色位置拉回到服务器认为的正确位置,那在玩家这边就会出现很强的“瞬间被拽回去”的感觉:前一刻一系列操作都很流畅,下一刻因为收到的是一帧稍晚的服务器确认(相对本地来说是历史状态),整个人突然被拉回原位置,体验会非常割裂。我们的做法是先回滚到出错时刻对应的状态,再基于那一刻之后的输入记录做一次前滚重放,让角色按正确的轨迹重新跑一遍,使得在玩家视角下动作依然连贯。比如说,角色原本在地面奔跑,随后触发一次跳跃,如果 DS 发现其中存在偏差并做了轻微纠正,本地会先回到纠正后的状态,再重新模拟奔跑到起跳的过程,最终在画面上仍然看到的是出现在正确位置上的那次跳跃,而不会出现突兀的拉扯感。

接下来是命中校验。在开始之前,先对《合金弹头:觉醒》中的子弹做一个简单介绍,共分为三类:第一类是 Hit-Scan(快速子弹);第二类是 Projectile(带弹道、带轨迹);第三类则是前两类特性的结合。针对不同类型,我们会采用不同的命中与同步策略。以第一类快速子弹为例,在实际玩法中,无论子弹速度多快,只要玩家在画面上成功躲开,就会自然认为自己不应该被击中,因此命中并不是在“开枪瞬间”就能直接判定的,这其中涉及的处理逻辑非常多。为了优先保证本地体验,快速子弹在本地命中时会立即给出反馈,同时将命中事件上报服务器,由服务器校验后再下发最终伤害结果。而对于另外两类子弹,通常由服务器直接进行命中判断并下发伤害。因此,在很长一段时间里,我们命中相关的技术优化主要围绕快速子弹展开,目标是让其在高实时性的前提下做到准确、可信,并具备足够的反外挂能力。

为什么我们会强调第一类快速子弹是“弹道”,而不是“弹幕”?原因在于它虽然速度很快,但仍然是以逐帧的方式在飞行。同时,在飞行过程中,它还会根据实时状态进行逻辑判断,例如命中的目标是否已死亡,从而决定是否继续穿透、穿透次数,以及是否需要分裂、分裂成什么类型的子弹。因此,看似简单的快速子弹,实际在逻辑上是非常重的。针对这种情况,我们采用的是客户端命中上报,再由 DS 校验的方式。那如何防外挂呢?在这里我们引入了一套“影子快照(Shadow Snapshot)”技术。它的含义是:DS 不仅在命中瞬间做历史校验,而是会在整个子弹飞行过程中持续采集行为数据。有了这些影子快照,DS 就可以在自己的视角下确保这些行为链路是可控且可信的,并在客户端上报命中时进行二次校验。

在反外挂层面,基于前面已经构建的一系列能力,我们会从三层来处理反外挂问题。首先是在框架层,服务器本身具备绝对权威,可以确保关键逻辑不会被客户端篡改;其次是在校验层,我们通过“影子快照”以及多种状态检查与记录机制,对行为链路进行持续验证;最后是在监控层,我们会对异常行为进行监测,包括异常加速、异常频率发包等模式。通过这三层的协同,基本能够覆盖大部分外挂风险点并确保整体的安全性。

前面的内容讲完之后,不可避免地会提到“同步”层面的效率问题。在实际研发中,我们经常会处理联机调试、内外网异常定位等情况,因此我们认为联机调试工具本身同样至关重要。为此,我们在多个维度上构建了相应的模拟与调试能力,用来复现和分析各种网络状态,从而更有效地优化整体的网络体验。

最后做一个简单总结:从底层通信能力,到预测、回滚、补偿,再到带宽优化和反外挂,构建了一套公平且精准的网络同步框架。

基于 Unity 引擎的 DS 架构
下图展示了构建 Unity DS 的一个简单参考。为什么选择 Unity 来做 DS?优势非常明显:一方面前后端逻辑可以直接复用,减少了重复开发;另一方面,后台逻辑中有大量场景需要依赖物理系统,Unity 自带的物理能力让前后端能够以更一致的方式处理相关逻辑,从而形成一套更连贯、成本更低的方案。

在实际开发过程中,除了前面提到的“逻辑与表现分离”之外,我们在资源和功能层面也尽可能做到前后端统一,一套开发同时满足两端需求,使整体流程更加简洁高效。

在研发效率层面,下方图中的左上部分展示了我们在实际开发过程中所使用的 DS 视图,在其中可以直接看到前端与后台的运行状态,这对于暂停、回溯以及日常调试都非常有帮助。右下部分则展示了我们在版本体验阶段的另一种用法:在手机上游玩时一旦发现联机问题,可以很轻松地将这一局接入本地 DS,并直接在编辑器中以 DS 视角进行调试。这套研发效率体系在项目过程中发挥了重要作用,特别是对于《合金弹头:觉醒》这样一款内容量较大的游戏,使我们能够更高效地构建和验证大量关卡内容。

既然《合金弹头:觉醒》是一款内容型游戏,那么对应的后台 DS 负担也会随之变重,特别是在内存占用和整体资源压力方面。项目早期我们做过一轮引擎瘦身和常规优化,但当这些手段都用到位之后,可继续优化的空间已经非常有限,而内容量却还在持续增长。从后台的视角来看,为避免运行时加载带来的卡顿,很多情况下 DS 在启动时就需要一次性加载全部内容,这也进一步加大了资源层面的压力。

于是我们继续深入分析。当时原始的 Unity 引擎模型是:一个进程对应一个实例、一个实例对应一场战斗,这意味着许多基础开销,包括我们自有的对象池,都是独占的。对于一些只需要“一名玩家 +AI”的轻量玩法来说,这种模式在资源利用上实际上相当奢侈,因为它会为这样的小场景跑起一个完整的进程。那么接下来该怎么办?我们当时有两条思路:第一是做共享内存,第二是做多房间。共享内存的方案在调研后发现实现成本较高,因此最终我们选择了多房间的方向。

改造的目标是什么呢?在之前的项目中,我们的做法是左图那种模式:一个进程跑完一场战斗后做一次 Reset,然后再串行复用这个进程。在《合金弹头:觉醒》中,我们希望能进一步提升这一模式,向右图那样的架构发展——也就是让底层的基础消耗(包括各类对象池和资源)保持共用,然后在其之上创建多个房间并行运行。这里需要解决的关键问题在于:多个房间、多个 Level 如何同时存在,并且彼此之间保持完全隔离。换句话说,从 DS 的视角看,即便它们位于同一个坐标系下,各房间之间也必须做到互不干扰。

早期我们评估过三种方案:空间分割、场景叠加以及逻辑 Room。考虑到《合金弹头:觉醒》在早期本身就是大世界结构,坐标体系依赖偏移来实现,如果继续采用前两种方案,随着时间推移不仅会引发坐标精度的问题,整体复杂度也会大幅提升。因此最终我们选择了第三种方案,即基于逻辑 Room 的方式来实现多房间并存。

要实现多房间完全并存,我们需要做到“三大隔离”。第一是引擎内置对象的隔离;第二是物理层的改造与隔离;第三是逻辑状态的隔离。由于开始进行这些改造时项目内容量已经相当庞大,这三类隔离在落地过程中大部分都属于工程层面的实际挑战,因此整体投入了相当多的时间来完成。

在完成改造之后,从DS的视角来看,我们希望整个系统能够以统一的方式驱动更新。有了 Room 的概念之后,我们内部把整体结构称为“大楼”,便于理解:一栋大楼里包含多个房间,由 RoomManager 来统一Tick各个房间。那么,一个进程应当承载多少房间才合适?我们主要从几个角度来评估:第一,希望在 30 帧的更新频率下能够稳定地完成所有房间的 Tick;第二,从稳定性出发,尽量避免单个房间出现异常时影响到整栋大楼;第三,需要结合 CPU 与内存的占用比例,寻找一个进程可承载房间数量的合理平衡点。

下方的视频展示了改造后的一个简单效果。从后台视角可以看到,房间是可以直接动态新增的,同一个 DS 进程中也能够不断创建新的房间。左侧则展示了两个独立运行的客户端,它们之间完全隔离、互不影响,由此也能初步验证我们的多房间方案达到了预期目标。
以 10 个房间为规模进行 DS 架构改造后,我们总结的收获大致如视频所示。实际上线外网后,我们又根据不同玩法在资源占用上的差异进行了多轮调整。最终外网的大多数玩法稳定在约 15 个房间的配置下运行,而部分资源较重或流程特殊的玩法则会动态调整为 10 个房间甚至 5 个房间,以确保资源使用保持在合理范围内。目前这套多房间方案已经稳定运行于国内与海外的正式环境中。

完成了 DS 的多房间优化后,随着游戏持续运营、内容规模不断扩大,我们又遇到了新的问题:即便在“大楼”架构下实现了资源复用,同一进程的整体内存占用依然偏高。从后台数据以及海外机型分布来看,部分场景已经出现了明显的内存瓶颈,因此我们需要进一步进行内存层面的优化。另一个问题是启动时间过长。前面提到,服务器的进程通常会提前拉起,但由于内容量不断积累,线上环境中一个进程的拉起时间已经达到 45 秒到 1 分钟,这对于后台的资源调度效率而言是明显偏慢的。

基于这两个问题,我们也调研了 Fork 方案。Fork 本身并不复杂,但与 Unity 结合在当时确实存在一定挑战,原理这里就不展开了。预期收益主要有两点:一是希望在“大楼”之间进一步复用内存;二是在进程拉起阶段能够更快启动,并减少 CPU 的毛刺开销。那么在改造过程中,我们具体做了哪些事情呢?

下图左侧列出了我们认为较为关键的改造点,包括引擎 I/O 的重定向、I/O 管理与业务侧的配套调整,以及线程层面的裁减和网络层的多路复用。右侧则展示了我们在评估方案时重点考虑的问题——也就是选择在何时进行 Fork 才是最合适、最稳定的时机。

下图展示了我们在引入 Fork 后获得的收益。可以看到,Fork 之后的内存占用下降得非常明显;在进程拉起方面,原本需要 45 秒到 1 分钟的启动时间,使用 Fork 生成子进程后基本能够缩短到毫秒级。随着这一优化落地,服务器的整体资源成本也随之显著下降。

完成上述改造后,我们在后台 DS 的开发过程中也经常需要进行大量性能测试。有些测试场景动辄要几十个房间,仅依靠人工很难构建和复现。因此,我们在这一层面也做了相应的自动化能力,包括在 DS 性能出现问题时,能够快速触发并复现相关场景。通过这套方案,只需执行一条指令,就能在 10 分钟内完成一轮完整的数据采集,并在右侧所示的分析工具中基于各模块输出清晰的耗时分布。

下方的视频展示了我们在测试过程,能够在本地编辑中直接看到在整个测试阶段发生的具体情况,帮助我们更直观地了解各项表现并定位可能的问题。
以上就是对这两个方案的介绍。我们在项目中的探索最终沉淀为一套完整且经过线上验证的解决方案,不仅支撑了我们的自身项目,也即将通过团结引擎服务于更多开发者。关于这项技术如何进一步融入团结引擎并开启新的可能,下面有请团结引擎的技术同学司天语为大家揭晓。谢谢大家!

团结引擎 DS 联机方案

司天语:感谢亚涛精彩的分享!接下来,由我跟大家讲一讲这套方案如何融入团结引擎,如何让各位开发者今天不仅仅是来听一场技术方案的分享,也是可以真实的把这一套方案运用到自己的游戏上去。我是来自于 Unity 中国底层架构组的软件工程师司天语。我们之所以会选择使用这套联机方案融入到引擎,最重要的是看中这一套框架成熟。如果一套框架没有经过大量线上游戏的验证,也没有经历过大规模的实践检验,我们是没有足够的信心向开发者去推荐这套框架和技术的。

如今不管是稳定性、功能性还是性能,各个方面都已经非常成熟。因此在腾讯天美 J1 工作室的框架方案基础上,团结引擎进行了代码编写,一起将它从“专用”打造成“通用”网络 DS 连接框架带给开发者们。既然这套框架已经非常成熟了,那么我接下来讲一下团结引擎会做些什么?我们在引擎中会使用更高性能的 C++ 来实现这套框架的内核,相比于纯 C# 的实现,我们可以预见会有进一步的性能提升。同时,我们也会继续优化这一套框架,对一些复杂模块进行解耦和重新封装,并且会设计一套简洁明了、易拓展的接口供开发者调用。我们会努力将这套框架变成更强大、更通用、更易用的产品。
说完了高性能的设计理念,在这个 feature 上面,我们还有另外一个思路,就是 All In One。一直我们都会思考:“哪些功能应该引擎去做,哪些功能应该让开发者自己去写业务代码?”一直以来我们在引擎的研发过程中都会去思考,哪些功能应该去引擎去做,哪些功能又应该让开发者自己去写业务代码。
一直以来团结引擎都保持着尽量只做引擎该做的事情,这就使得团结引擎是非常精简、高性能且高拓展的一个引擎。在这个 feature 上面,我们希望引擎可以尽可能做更多的事情。不仅是 DS 框架会下沉到引擎当中,同时我们也会将更多的业务核心的逻辑也封装成组件,下沉为引擎的能力。我们希望可以给开发者带来真正“开箱即用”的体验。除此之外,刚才亚涛刚才提到了关于分析 ping 值、ds 帧率、模拟断线、弱网模拟、轨迹校对、预测、插值等一系列的 Debugger 和 Profiler 工具。我们都会集成进引擎,而且会在部分分析工具中提供引擎源码级别的更加深入到引擎层面的分析能力。这些工具我们也都会内置的引擎当中,同时在部分工具(如果可以的话),我们还会提供引擎原码级别更加深入到引擎底层的一些分析和调试的能力。

我们框架会做到或者说我们希望帮助开发者做到的就是降低成本,这是所有项目在开发过程中都非常关心的问题。为了尽可能地榨干服务器的性能、避免造成浪费,这套方案在服务器高承载方面也做了很多的创新。一台 Linux 服务器先是通过 DSA 拉起“种子进程”,然后再 Fork 出多个 DS 进程,然后每个 DS 进程又可以并行地运行多个房间支撑起很多个对局,这就可以让我们尽可能榨干一台服务器的性能,以达到降低服务器的成本。其次,这套框架还会帮助项目降低开发成本。首先,该框架前后端会使用同一套逻辑,既保证一致性,也大大减轻逻辑开发的负担。接着就是刚才提到的,我们会将多数项目都有可能用到的核心功能、核心业务逻辑都组件化封装,然后常用的功能都会有官方提供的组件,大家不用再去造轮子、重复地去进行开发。同时,我们也会给出完整的功能、详细的文档,并且会积极地建设社区的专题,让大家可以快速地和研发人员面对面的交流问答,并且我们也会长期维护这套联机方案。

最后,我们希望和天美 J1 工作室,一起共同将这套方案尽可能地打造为迄今为止团结引擎上最全面、最丰富、最完整的通用 DS 联机框架。我们会在 2026 年的上半年带着这套框架与各位开发者见面,敬请期待。谢谢大家!

Unity 官方微信
第一时间了解Unity引擎动向,学习进阶开发技能
每一个“点赞”、“在看”,都是我们前进的动力

