帧同步和状态同步
前言
谈到网络游戏,不可避免要谈到现有两种比较常见的网游同步技术:帧同步和状态同步
说到这两个名词,大家夸夸奇谈,都能讲上些许自己的见解,我反正啥也不懂
这篇文章就打算着重学习一下这两种技术的基础和原理
网络同步的目标就是时刻保证多台机器的游戏表现完全一致。
网络同步 = 实时的多端数据同步+实时的多端表现
同步战斗逻辑是包括技能逻辑、普攻、属性、伤害、移动、AI、检测、碰撞等等的一系列内容,这常常也被视为游戏开发过程中最难的部分。
网络同步按大类来分有两种做法:状态同步和帧同步。需要强调的是这两个概念并不是简单的对立概念,其中的差异包括:”数据格式与内容”、“逻辑的计算位置”和“是否有权威服务器”等。
帧同步【LockStep】
LockStep的翻译是锁步同步,是齐步行军的意思
有明确的逻辑帧概念,按照固定的逻辑帧间隔同步数据
- 帧同步的战斗逻辑在客户端
- 在帧同步下,通信就比较简单了,服务端只转发操作,不做任何逻辑处理。
- 客户端按照一定的帧速率(理解为逻辑帧,而不是客户端的渲染帧)去上传当前的操作指令,服务端将操作指令广播给所有客户端,
- 当客户端收到指令后执行本地代码,如果输入的指令一致,计算的过程一致,那么计算的结果肯定是一致的,这样就能保证所有客户端的同步,这就是帧同步。
属性 | 帧同步(LockStep) | 状态同步 |
---|---|---|
确定性 | 严格确定 | 允许小误差,定时纠正误差数据 |
表现与响应速度 | 传统严格帧锁定要等其他客户端消息全部到达,响应比较慢;乐观帧锁定可以做到本地立刻响应,但是需要回滚的时候,体验就没那么好了 | 一般会做预测,可以做到立刻响应。不做预测的话,响应时间是一个往返时间(RTT) |
带宽与流量 | 带宽随人数增加而增加,不适合MMO | 需要发送各种状态数据,带宽占用比较高。可以通过压缩、裁剪、增量等方式优化。人数较少时候不如帧同步剩流量 |
网络延迟适应性 | 要求较低的延迟。如果延迟较高,所有玩家体验都不好。即使采用乐观帧锁定优化,高延迟下也容易产生卡顿 | 适应性较高,方便做各种插值优化。当然高延迟下,也容易产生位置突变 |
开发难度 | 初期开发减法,框架容易实现,但是后期解决bug和完善系统很困难。比如浮点数、随机数、执行顺序导致计算结果不一致,问题很难排查 | 框架比较复杂,客户端服务端一套代码,每个功能都需要客户端服务端联调。问题定位比较容易。也会出现时序问题 |
玩家数量 | 适合少量的玩家,比如ACT、MOBA | 可多可少 |
跨平台 | 不适合跨平台,会有浮点数问题,可以用定点数来将误差控制在一个可接受范围,同时可以定时纠正结果 | 适合。有权威服务器 |
反外挂 | P2P架构不适合反外挂,如果引入战斗服务器来校验各个客户端结果,可以解决常见外挂,但是透视和全图视野防不了 | 与服务器加入校验机制,可以起到比较好的反外挂效果。但是一样防不了透视外挂 |
中途加入和断线重连 | 比较复杂。可以在断线的时候,通过快捷播放服务器同步的帧数据来快速跟上游戏 | 容易。由于实时记录了各个对象的状态信息,所以重连的时候,直接创建这些对象,并同步信息即可 |
性能(客户端) | 客户端要跑完整逻辑,还要执行渲染逻辑,开销比较大 | 可以灵活优化,客户端跑较少逻辑 |
回放(离线) | 本身收集了所有玩家的输入信息进行逻辑推进,天然支持回放,且回放文件比较小 | 可以支持回放,但是逻辑比较复杂,需要不断记录状态信息,同时回放时候需要读取合适的时间。回放文件大 |
回放(实时) | 比较复杂,客户端需要本地对全场状态进行序列化,才能回到目标时间。播完回放后还需要加速追上实时游戏状态 | 相对容易,可以方便的记录快照信息,并按照录制内容随时播放 |
状态同步
本节中提到的状态同步和帧同步都是广义上的,虽然它们的底层实现都可以基于上面提到的模式,但从广义上讲,它们的同步思路不同。
状态同步指的是将其他玩家的状态行为同步,例如,怪物的AI控制、角色技能释放、战斗伤害计算等。纯粹的状态同步模式下,这些内容都由服务器运算,只是将结果下发给客户端,客户端根据得到的数据驱动显示即可。这种模式的缺点是流量消耗比较大,消耗的流量取决于场景中需要转发数据的人数和内容。另外,先天的结构导致响应速度存在问题,无法做到高频交互的顺畅体验。最明显的特点就是“拉扯”现象。它表现为某个角色会突然出现在某个位置,或某个技能效果突然出现,甚至角色忽然死亡等。引发这个现象的原因是,网络波动导致数据未能及时送达,而客户端进行了某些程度的预表现或航位推测。
对于大多数实时性要求不高、交互简单的游戏,一般会使用状态同步。通常来说,常规MMO类型的游戏只能使用状态同步。
帧同步
帧同步是指客户端之间只同步用户的输入指令,例如,向前走、按下哪些技能等,不同的客户端各自计算自己的结果。由于消耗的流量只取决于指令数,因此会大大减少消息。另外由于消息结构的原因,帧同步的速度要比状态同步更快,因此适用于一些高频交互的游戏。
不过由于每个客户端需要独立计算,因此需要保证计算结果的一致性。理论上讲,相同的时机,输入相同的内容,会得到相同的结果。不过从实际情况看,做到完全相同还是有些难度的。以Unity引擎为例,一方面,各个脚本之间Start、Update等生命周期函数的调用顺序不确定;另一方面,使用Physic物理系统也不保证是确定性模拟。比如一个单位的坐标偏差了0.01导致技能未能击中目标,那么这个目标的血量判断就会受到影响。如果这个目标的行为受到血量的影响,那么最终结果会完全不同。因此让每个客户端计算结果完全相同不是一件容易的事。一些常见的注意事项如下。
- 不使用浮点数,用整数代替。
- 不同客户的同步频率要保证一致。
- 随机种子相同,并自定义接口防止其他公用系统干扰。
- 使用排序容器,保证遍历顺序。
- 逻辑显示分离。
- 使用补间过渡,调整速率,掩盖卡顿。
除了一致性的难点,帧同步还需要解决流畅度的问题。由于通过网络传输过来的数据一定会慢于本地,而我们又希望在相同的时刻输入信息,因此就会引发等待,反映到用户体验就是不流畅。
优化方法有很多,例如,在帧同步游戏中,由于广播的频率非常高,因此每次广播的数据就要足够小,这样可以节省很多消息处理的时间。对于消息,可以将需要所有客户端同时发生的内容提前广播给其他用户,采用时钟同步。客户端逻辑先行,显示通过平滑追赶的方式处理。很多改进是体验优化的范畴,需要结合具体游戏进行。
在传输层,移动端的同步建议使用UDP作为传输协议。TCP为了保证传输的可信性,很多机制不太适合波动较大的移动网络。在弱网络环境下,UDP的RTT几乎不受影响,而TCP的RTT波动比较大,特别是在丢包重发时影响比较明显。虽然使用UDP会引入丢包、乱序的问题,但可以通过冗余的方式来解决这个问题。比如每帧三个数据包,实际上是包含了过去两帧的数据,也就是每次发三帧的数据来对抗丢包。
两种同步模式:状态同步和帧同步 - 知乎 (zhihu.com)
【网络同步】浅析帧同步和状态同步 - 知乎 (zhihu.com)
Unity多人游戏框架Netcode for GameObjects
Unity 实时多人游戏,第 8 部分:探索现成的网络解决方案 | HackerNoon