网络同步:状态帧同步DS方案总结¶
在大厂也工作过一段时间了,当时还对网络了解的一知半解,以为就是普通的状态同步方案。现在自己写了帧同步项目后,想到自己写的那些逻辑分离的代码本不应该出现在状态同步游戏中。因此了解到这样一种游戏架构--状态帧同步
当然一个技术分享并不会面面俱到,这里主要就做一个汇总,主要以《合金弹头:觉醒》技术方案为主,并辅以《守望先锋》GDC内容补充。
技术背景¶
1. 专用服务器(DS,Delicated Server)¶
DS,作为专用服务器,他的特点主要如下: - 固定频率同步: 服务器管理联机所有战斗元素和状态,以固定频率同步状态到客户端。 - 实时性高:多用于同步实时性比较高的品类(如FPS、TPS) - 经典C/S权威服务器: - 服务器跑完整逻辑 - 广播最新数据和玩家仲裁(命中判定、外挂检测、战斗进度等) - 业务代码和逻辑关卡数据复用:Client和DS均采用Unity引擎,并且保证了一定的一致性 - 网络模块--RUDP同步:基于UDP的RPC+State Sync
具体实现¶
1.业务代码复用¶
80%+ 联机模式代码双端可复用 - 都采用Unity进行实现,代码轻量。 - 前后端协作效率提高“:逻辑&资源双端复用,两边不再需要单独写一遍代码,内容向游戏提高研发效率。 - 无用逻辑:使用条件编译去除代码编译。
如图,红色部分是客户端和服务端公用的部分。
- 逻辑和表现分离:使前后端分离处理更加方便。
客户端单独做表现,服务器单独做校验
2.玩法验证&代码DEBUG效率提升¶
这套DS+逻辑帧可以大幅度提高玩法验证与代码DEBUG效率: - 灵活验证:服务端依然有碰撞体的表现,因此可以看到服务端的运行时状态,便于DEBUG
-
暂停、恢复:
- 暂停:由于逻辑帧的存在,可以快速暂停游戏(暂停逻辑帧的运行),方便DEBUG
- 逐帧迭代:可以一帧一帧的看过程
- 恢复:也可以立刻恢复普通游玩模式。
-
状态变更时刻生效:
-
重复验证:Alt+F5可以快速清理游戏房间状态,重新开始游戏。减少游戏重启成本
-
手机端联机问题定位:
- 开发时,也可以开启本地DS引擎,重定向联机局还可以做本地服务器断点调试。
2. 引擎层 Unity Server服务器优化¶
- Linux:实际发布的服务器运行Linux版本的Unity,去除Windows大量图形界面和无用软件影响。
- Unity屏蔽非DS必须组件
- 线程裁剪
- 部署DSA与DS:DSA用来管理DS单局。
3.DS视角 UnityServer服务器优化¶
- 分帧(aI、寻路、刷怪)
- 降频、合包:网络层--属性同步/RPC重复帧合并
- 相关性裁剪:视野,具体是啥不太清楚,AOI这种?
- 离线计算:寻路
-
支持降帧:部分模式--30帧->15帧
-
逻辑子线程化:这块做了啥还不太清楚。。。
4. IL2CPP化¶
il2CPP代替Mono:流程分析
- 中间语言生成:Mono和il2cpp都会先对C#编译期生成的中间语言做处理。
- C++Code转化:但是iI2cpp会再次生成C++版本的代码,让本地C++编译期对生成的代码进行优化
- 编译生成机器码:让C++编译期编译生成机器码效率远比Mono生成的机器码效率高。
优点:执行效率高
缺点:编译时长显著增长
因此采用开发期Mono、发布期IL2CPP的优化。
5. 系统级别的优化¶
《合金》采用的是单线程多进程的架构(对!你没有听错!不是单进程多线程!!!)
主要做的也是进程和系统调用方面的优化:
线程调度相关的系统调用。。。听说达到了百万级,上面这三个分别是 - 允许线程放弃对处理器的控制 - 线程阻塞 - 纳秒级高精度休眠**
为什么要改造?¶
原来的DS: - 单进程X1场战斗,战斗独占资源,串行(回收/再分配)复用,CPU&内存不对等 预期计划: - 合并引擎消耗 - 进程内战斗并行, - 资源复用最大化 - 减少进程拉起频率 - 空间换时间,优化CPU消耗
方案考虑¶
- 空间分割:
- 利用Unity自带功能的场景叠加 ,单个Scene中划分不同区域,用坐标偏移实现多局战斗并存 但这是不行的,合金说了两点:
- 一个是游戏中本身还有大世界的拼接(比如一局游戏同时加载了多个Scene场景做偏移)
- 场景叠加物理都是一套,一个房间的物理(比如子弹)可能影响到其他房间。
看到这里我表示存疑,难道不能分层,或者做逻辑上的边界处理吗?不过后面讲了处理方法
最终方案¶
引入了逻辑ROOM的概念,scene内部其他组件做了隔离,还是做了场景叠加 - Scene 顶层容器、基本信息维护、管理gameobject、对象列表等。 - SceneManager:创建和卸载Scene,提供了对Scene的一系列操作方法。 - Physics:停用了全局物理,物理更新统一交给Room控制,对应一个PhysicsWorld - 单房间物理只能在PhysicsWorld里处理。 - GameLogic:游戏逻辑
其中Scene、Room、PhysicsWorld,GameLogic都需要进行绑定为同一个映射关系。
最终实现了一个服务器跑多场战斗的功能!!!
但其实客户端这边看着只有一个ROOM
当然Entry到战斗也有一些收敛的功能:
- 资源接口通用
- SceneManager转为Room控制
- dontdestroyonload为公共区域
6. 日志模块¶
【时间-帧号】【级别】【房间号】【模块】:【内容】
拜托,有帧号+暂停模拟回放真的能加快不少DUBUG速度!!!
7. Room稳定性优化¶
单Room更新:由每个Tick驱动所有Room实现,Room内部再执行物理和逻辑更新。
考量因素:¶
- 同步稳定性:30帧/s,Tick完所有房间
- 代码稳定性:避免一个房间崩溃,整个场景受影响。
- 平衡进程与CPU&内存比例: 单进程80%内最大可承受房间数为极限
最终实现了15个房间并行战斗,单房间异常直接隔离掉(断掉)。
8. 进一步优化:ECS¶
ECS这个概念跟随《守望先锋》的状态帧同步一起火了起来,我们得理解为什么守望先锋要用这种结构:
- 加快CPU缓存读写,整齐的内存排布更有利于状态快照下发。
但是《合金弹头》最开始并没有考虑使用ECS,个人猜测是因为两点:
- Unity DOTS对ECS支持相当不完善
- ECS这种编程模式,对于开发效率及其不友好。
9.其他未来考虑的优化¶
- fork快速拉起进程
- 快照快速恢复异常单局。
网络同步-状态帧同步实现¶
为什么要用状态帧同步: - 传统帧同步强锁帧机制和客户端逻辑不可控 - 传统状态同步延迟过大。 状态帧同步可以保证: - 服务器权威性保障公平(反作弊) - 本地预表现降低延迟(体验流畅) - 动态同步优化带宽(支持复杂场景)。
1.时钟同步¶
客户端可能存在卡顿,回导致运行时间不同步,因此需要同步
如果客户端和服务器或者其他游戏客户端时间不同步,会有遇见些问题: 举点例子: - 玩家视角中某些属性(比如他人动作或者其他物体)可能会瞬移、跳变。 - 常用时间敏感相关的功能会记录错误的时间导致问题
一般采用NTP方案进行网络时间同步。此外,时间同步需要考虑到双端网络信息传输时间。
实现方式:¶
通信: - 客户端连接上时同步时间 - 服务器每隔一段时间 发一个TimeAsyncMssage给客户端,让客户端Sync
具体同步方式,先说明一些概念:
- 先记录四个时间戳
- 客户发送时间t0
- 服务端接受时间t1
- 服务端发送时间t2
- 服务端发送时间t3
1. RTD:双端传输时间(t3-t2)-(t1-t0),减去了服务端的时间消耗,获得网络上上花费的延迟。
2. NTP:这是一个估算了传输延迟的时间校对算法。
- Offset:(t1-t0+t2-t3)/2,该offset就是算出来客户端与服务器的平均偏差
最终时间纠正: t3+Offset
极端环境时钟同步:¶
有些极端环境的Offset算出来偏差很大,如果用这种offset的纠正会导致错误纠正,如何处理? - 先大量快速算一些offset,缓存下来 - 如果某个offset算出来偏差很大,抛弃。 - 采用offset均值进行同步
2.命中同步¶
子弹验证: - 目前除普通直线为快速子弹(联机时ds不生成该子弹,直接验证伤害)外,其他轨迹的子弹均为碰撞检测.
客户端命中预测(高响应): - 预测优化:一个是所有实体都有一个代表了预测范围的检测碰撞盒,子弹或者射线需要命中这个射线盒才会对这个实体做出预测,避免了对整个世界实体都做出预测 - 命中预测方式:玩家看到的敌方,都是一段延迟以前的模样 - 在高帧率下(250ms内):客户端判断命中后会直接播放击中特效,击中音效和实际属性变化等待服务器下发 - 在低帧率下:客户端也不播放击中特效了,击中特效、实际属性变化、击中音效都有服务端验证下发的数据决定。
3.网络波动处理¶
客户端网络并不保证如此稳定,时有丢包发生,即客户端消息有概率会无法到达服务器。
服务器会预留一个buffer(尽量保证小,保证流畅),用来调节网络波动与丢包处理 - 如果服务端如果消耗完了输入,没有收到客户端发的包,会进行预测模拟,复制原有的操作 - 此时服务端会告诉客户端丢包了 - 客户端会加速模拟(如原来16ms一帧,现在15.2ms一帧), - 此时服务器的缓冲也会变大。 尽量不浪费的情况下,度过丢包的难关。
当服务器恢复健康时,恢复原样。
冗余发包: 此外,当客户端丢包严重,可以携带缓冲的多帧。这样可以避免丢包带来的数据丢失。
4.位置同步¶
内插值¶
即服务器同步下发新位置后,才做跟随插值 缺点:网络波动影响大,不稳定。
外插值(又称==预测==)¶
现在一般采用影子跟随算法,由Dead Reckoning导航插值算法演变过来。 - 即物体表现上的位置永远在追逐实际位置的影子。 - 影子可以离散的变化,而实际位置需要时连续的变化
5.预测回滚¶
如果服务器和客户端发生了偏差,将以服务器的验证结果为主,立刻打断客户端。
如何验证? - 客户端存储有移动缓冲环形数组、技能缓冲环形数组、操作缓冲环形数组 - 服务端和环形缓冲作比较,实现验证。
5.延迟处理¶
高频低伤害表现与特效,推后一起处理
最终效果实现¶
Reference¶
- 18.网络游戏的架构基础 (Part 1) | GAMES104-现代游戏引擎:从入门到实践_哔哩哔哩_bilibili
- 合金弹头:基于Unity引擎的前后端研发实战-2023深圳UUG·田亚涛_哔哩哔哩_bilibili
- Peeking into VALORANT's Netcode | Riot Games Technology