前言

承接上篇 基于行为树的MOBA技能系统:基于状态帧的战斗,技能编辑器与录像回放系统设计 这篇文章记录一下将状态同步切换为状态帧同步所做的改动

网络同步

首先是最基础的网络同步模块

目前业界主流有两种做法

  • 使命召唤:客户端落后于服务器,服务器收到客户端消息时根据帧号回滚到那一帧进行模拟,并将得出的结果返回客户端
  • 守望先锋:客户端领先于服务器,服务器收到客户端消息以当前服务器所在帧为准,统一进行模拟

两种做法都是可以的,最大的不同在于服务器收到客户端消息时的处理方式,其他逻辑(比如预测,回滚)本质都是一样的,因为守望先锋的分享更加详尽一些,细节处理的可参考度也就更高,所以这里选择守望先锋的做法

这块内容在 基于行为树的MOBA技能系统:技能系统与网络同步 有提到,此处不再过多赘述,其中需要注意的一个概念是客户端一定是领先于服务器的,因为我们发送的网络包会在半个RTT+一个缓存帧时长才会到达服务端,所以如果服务端当前是95帧,RTT是8帧,缓冲帧时长为1帧,那么客户端就会运行在第100帧,这样才能保证客户端第100帧的输入在服务端接收输入并开始模拟时也是第100帧。所以会有下面这个平衡公式

客户端发包帧数 = 服务器当前帧数 + 半个RTT + 服务端一个缓存帧

平衡公式图例,客户端在第34帧发送的消息会在服务端第33帧收到,然后经过一个缓存帧(这里假设为1帧)被服务端正式加入模拟

那么如何实现这个超前操作呢?那就需要计算我们当前FixedUpdate的DeltaTime和目标DeltaTime的差距,然后修改即可,举例来说,客户端FixedUpdate频率是应该是稳定在30FPS,但是此时RTT变大,就需要加快FixedUpdate的频率,也就是减少每帧的Tick长度,直到再次满足我们的平衡公式,相应的,当RTT变小的时候,也需要增加FixedUpdate每帧Tick长度,直到再次满足平衡公式。这个操作会贯穿在整局游戏中,因为我们的RTT是不断变化的。

RTT变化时客户端与服务端做出的相应

TODO 加入网络监视器的GIF图

但是由于有时候RTT变化幅度过大,导致客户端没有办法在很短的时间内恢复到平衡状态,那就需要服务端这边再根据客户端的延迟情况,操作缓冲帧,做延迟补偿,将网络情况较差的客户端发送的指令往前放。(依稀记得我大学舍友对R6的延迟补偿机制破口大骂)

客户端网络同步处理流程示意

服务端网络同步处理流程示意

客户端发送指令帧数 + 1

客户端用户输入有他的特殊性,往往会在Update里收集输入,在FixedUpdate里进行指令发送,所以要放到下一帧

玩家输入处理

玩家的所有输入都需要由一个组件进行托管,然后由这个组件分发到所有需要玩家输入驱动的模块

此外,玩家的每次输入都需要放入一个玩家输入缓冲区中,因为客户端是超前于服务器的,所以难免会有预测失败的情况出现,那么我们就需要从预测失败的那一帧追帧到当前帧,在这个追帧的过程中我们就需要重新读取缓冲区的输入进行预测模拟

最后需要注意的是对于玩家的输入,发送间隔可以定帧发送,比如1s可以发送15次操作,但是对于玩家输入有效性的检测,则必须跑满帧率去检测,例如玩家会点击地面进行寻路,会判断玩家是否按下按键,然后发射射线到地面,得出目标点进行寻路,如果这个过程不跑满帧率的话就会产生漏检测的后果。

移动模块

对于基于寻路的移动模块来说,本地一般都是需要跑一个寻路系统的,因为我们的寻路指令要经过一整个RTT + 一个服务器缓存帧时长才能经过服务器将寻路结果传回来,例如ping是60ms,一帧为30ms,那么就需要60 + 60 + 30 = 150ms = 5帧的时间才能收到服务器的寻路结果,这还只是60ms延迟,更高的话效果更不理想。

预测

如果本地跑一个寻路库的话完全可以本地立即进行寻路而不用等服务器回包,再加上我们状态帧的特性:每帧发送变化快照到客户端,不需要定点数版本的寻路库(这一步很节省工程时间,毕竟谁愿意花费九牛二虎之力改造一个定点数版本的寻路库呢),因为每帧都在纠正玩家的位置,基本上不会有误差

回滚

移动的回滚条件检测主要是检测某一帧位置是否与服务器一致(由于浮点数的原因所以有一个容差值,一般是0.0001f),因为不论是角色朝向的改变,还是速度的改变,最后都会反映到位置的改变。

一旦发现位置信息同服务器不一致,就需要用当前服务器的输入进行修正,只修正还不行,还需要重新进行寻路模拟,毕竟位置发生了变化如果不重新模拟寻路就会导致寻路点对不上。

战斗模块

技能系统

动画系统

数值系统

录像回放&&观战系统