服务器倒带(Server Rewind)主要用于解决多人在线游戏中,由于网络延迟导致的客户端与服务器表现不一致问题。该技术特别适用于第一人称射击(FPS)和动作类游戏,能够在不牺牲响应性的前提下保证游戏的公平性。
服务器倒带算法的实现思路
在服务器上保存玩家一段时间内的历史状态信息(位置、旋转等),当收到客户端的命中请求时,根据客户端上报的时间戳将目标玩家的状态回滚到对应时刻,重新进行碰撞检测以验证命中有效性。
所以我们需要如下结构体:
1 | struct FHistoryCache |
保存的时间
在这个结构体中我们保存了游戏的运行时间,UE给我们提供了游戏自开始一共运行了多少时间,我们可以很容易的获取并将其保存到缓存中,但需要注意的是:这个时间会因为客户端服务端的差异而存在不同,所以我们需要对时间进行校准。
时间校准的方法
我们可以向服务器请求一次当前服务器的运行时间并将客户端时间一并上报,当服务器收到客户端的请求后便将服务器运行时间于客户端上报的时间一并下发给客户端,这样便可以计算出服务器的运行时间。
具体过程:
- 在需要校准时间时将客户端的时间上报给服务器
- 服务器收到客户端上报的时间后将服务器的时间于客户端的时间一并发送给客户端
- 客户端收到后可以根据上报时的客户端时间计算出RTT,这里假设客户端服务端进行一次通信所花费的时间相同,即服务端收到客户端上报的时间于客户端收到服务器下发的数据所花费的时间相同,就可以计算出客户端在收到服务器下发数据时的运行时间
具体实现
我们将实现放在玩家的控制器中,继承APlayerController类。
ASGPlayerController的头文件
1 |
|
ASGPlayerController的实现
1 | ASGPlayerController::ASGPlayerController() |
位置与旋转
为了便于实现,我们只在玩家身上挂载一个UBoxComponent, 将这个BoxComponent用作玩家回滚后的击中判定:
1 | class ASGCharacter : public ACharacter |
倒带过程
我们使用一个组件来承担倒带的主要逻辑,主要实现功能为:
- 记录一段时间内玩家的历史位置信息
- 根据上报的信息回滚玩家位置并确认击中
- 使用循环数组保存信息以减少开销
- 需要提供可视化调试
USGLagComponent的头文件
1 | /** 历史缓存的结构体,存储每帧玩家的位置以及时间信息 */ |
USGLagComponent的实现
1 | USGLagComponent::USGLagComponent() |
总结
工作流程
以下mermaid展示了整个服务器倒带的工作流程:
1 | graph LR |
调试与优化
如果要模拟高延迟的情况,可以选择在DefaultEngine.ini中添加:
1 | [PacketSimulationSettings] |
可修改PktLag这个值来改变测试的延迟
依然可优化的方案:
- 如果保存的历史帧非常长的话使用遍历会影响性能,可以使用二分查找来优化
- 在进行回滚检测时,可以使用更细致的检测方案
参考
https://zhuanlan.zhihu.com/p/690661788
https://zhuanlan.zhihu.com/p/1929071131677143054
- 本文作者: KongXinQing
- 本文链接: https://13114987559.github.io/2025/10/25/essay/服务器倒带/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!