了解Edgegap的工作原理

了解Edgegap的工作原理

回弹

延迟回弹(Rubberbanding)是指当服务器纠正客户端的错误预测位置时,所发生的肉眼可见的瞬间回弹现象。

由

由

由

雅库布·莫蒂尔

雅库布·莫蒂尔

雅库布·莫蒂尔

,

产品总监

产品总监

产品总监

已发布

已发布

已发布

同步失调不等于延迟

这两个概念经常被混淆使用,但它们是不同的故障。

延迟(Lag)是滞后:你看到的一切都是正确的,只是晚了。而同步失效(Desync)则是冲突:你看到的是错误的,而针对它做出的操作会产生令人感到莫名其妙的结果。你射击了一个已经离开的人,或者你刚转过拐角就被一个你从未见过的玩家击杀了。

这种区分之所以重要,是因为它决定了你需要修复什么。由于确定性漏洞,一个延迟仅 15 毫秒的玩家也可能会严重同步失效。而一个延迟 120 毫秒的玩家通过良好的预测和重构,也可以保持完美的同步。当问题出在代码上时,去盲目追求降低延迟只会浪费一个冲刺周期。

文章的核心洞察

  • 不同步是分歧,而非延迟:滞后(Lag)意味着信息到达较晚。不同步(Desync)意味着两台机器保存着相同世界的不同版本,这是一个具有独立解决方案的独立故障。

  • 存在两种截然不同的类型:复制不同步(Replication desync),即权威服务器纠正预测错误的客户端;以及确定性不同步(Determinism desync),即对等端模拟产生分歧,导致会话直接停止。

  • “橡皮筋”效应有两个控制杠杆:客户端预测错误的频率(由延迟决定),以及每次纠正的明显程度(由网络代码决定)。调整其中任何一个,玩家看到的“橡皮筋”现象都会减少。

  • tick 率并不是这两个杠杆之一:提高 tick 率并不会使预测更准确,而且在已经丢包的连接上,它反而会使画面异常情况更加严重。

  • “发生不同步”是校验和失败:帧同步(Lockstep)游戏在每回合都会比较状态哈希值,因此一个分歧的浮点数或一个未同步的随机调用都会导致会话终止。

导致该情况发生的四个真实原因

不稳定的嘀嗒率(Tick Rate)。服务器每秒更新固定次数的游戏状态。当 CPU 争用导致该速率下降或波动时,客户端接收状态的频率会降低,并会基于更陈旧的信息进行更超前的预测。不稳定性比低但稳定的速率危害更大——运行在 60 Hz、然后 44 Hz、再到 60 Hz 的服务器产生的同步失调比始终保持 30 Hz 的服务器更严重,因为预测无法针对移动的目标进行校准。

延迟和抖动。高往返时间意味着预测是基于较旧的信息进行的。可变的往返时间则更糟糕:客户端无法确定一个修正节奏,因此会轮流出现预测过度和预测不足的情况。

非确定性模拟。在步进锁(Lockstep)架构中,每个客户端运行相同的模拟,并信任其产生完全一致的结果。平台之间的浮点数差异、未初始化的值或任何读取实际时间的行为都会导致偏差,并且这种偏差会默默累积,直到变得显而易见。这是低延迟(Ping)完全无法保护你免受影响的原因。

时钟错位。当服务器和客户端对当前时间的认知不一致时,每一个基于时间的计算(如技能冷却、弹道飞行、增益持续时间)在两端的解析结果都会有所不同。

为什么数据包丢失会让情况变得更糟,而不仅仅是变慢

丢失的状态更新并不是延迟的状态更新。在 UDP 协议下,客户端不会等待它,而是继续根据其拥有的最新状态进行预测。每丢失一个数据包,就会延长客户端推断未经验证状态的时间窗口,而当下一个更新到达时,相应的修正幅度也会更大、更明显。这就是大多数“橡皮筋”现象背后的机制。

解决它的方法,按杠杆作用大小排序

层

修复方法

解决的问题

架构

作为单一真理来源的权威服务器

消除两个客户端各自认为自己正确的不同步类型

客户端

带服务器对账的预测

保持输入响应灵敏,同时在出现分歧时以服务器为准

客户端

远程实体的插值

使更新之间其他玩家的可见动作更加平滑

服务器

延迟补偿 —— 回溯到射击者的视角

使不同延迟下的击中判定更加公平

代码

确定性模拟,必要时使用定点数

解决同步锁步分歧的唯一方法

基础设施

一致的滴答率、靠近玩家的服务器、性能余量

彻底消除基础设施端的原因

来自我们的赞助商(其实就是我们自己!)的寄语

大多数延迟来自距离,而非代码。Edgegap 的边缘云是一个分布式网络,按需提供高达 615+ 个可用位置,因此每场比赛都能在最适合玩家的可用位置运行。对直播工作室流量的独立重放测试表明,与公共云相比,平均往返时间减少了 58%。

顺序至关重要。当问题出在第四层时,团队往往在最后一层耗费时间;而当问题出在第六层时,又在第四层纠结。在优化之前先进行检测:记录服务器 tick 时间和客户端修正幅度,一天之内你就能清楚自己究竟处于哪一层。

// Unity: 在固定的时间步长上运行模拟,与帧率无关。
// 在 Update() 中进行模拟步进会在帧率变化时导致不同步。
 
void Awake()
{
    // 60 Hz 模拟 tick。客户端和服务器必须一致。
    Time.fixedDeltaTime = 1f / 60f;
}
 
void FixedUpdate()
{
    // 绝不要在模拟代码中使用 Time.deltaTime 或 Time.time。
    // 使用固定步长和显式 tick 计数器。
    currentTick++;
    SimulateTick(currentTick, Time.fixedDeltaTime);
}
// Unity: 在固定的时间步长上运行模拟,与帧率无关。
// 在 Update() 中进行模拟步进会在帧率变化时导致不同步。
 
void Awake()
{
    // 60 Hz 模拟 tick。客户端和服务器必须一致。
    Time.fixedDeltaTime = 1f / 60f;
}
 
void FixedUpdate()
{
    // 绝不要在模拟代码中使用 Time.deltaTime 或 Time.time。
    // 使用固定步长和显式 tick 计数器。
    currentTick++;
    SimulateTick(currentTick, Time.fixedDeltaTime);
}
// Unity: 在固定的时间步长上运行模拟,与帧率无关。
// 在 Update() 中进行模拟步进会在帧率变化时导致不同步。
 
void Awake()
{
    // 60 Hz 模拟 tick。客户端和服务器必须一致。
    Time.fixedDeltaTime = 1f / 60f;
}
 
void FixedUpdate()
{
    // 绝不要在模拟代码中使用 Time.deltaTime 或 Time.time。
    // 使用固定步长和显式 tick 计数器。
    currentTick++;
    SimulateTick(currentTick, Time.fixedDeltaTime);
}

Edgegap 的观点(仅代表个人意见,姑且听之即可!)

Edgegap 的观点

“我们交流过的大多数工作室在到来时都深信自己遇到了网络代码问题,而其中大约有一半的人实际上遇到的是‘披着网络代码外衣’的容量问题。服务器部署密度过高会导致 CPU 竞争,刻率(tick rate)在负载下变得不稳定,预测系统也失去了可供校准的稳定基准。其特征在于,这种问题只在达到最高并发量时才会出现。在重写预测层之前,先对比绘制一下服务器刻时间与玩家数量的关系图。如果两者存在相关性,那么解决办法是预留空间,而不是修改代码……而且这解决起来要省事得多。”

-

-

-

雅库布·莫蒂尔

雅库布·莫蒂尔

雅库布·莫蒂尔

,

产品总监

产品总监

产品总监

常见问题

音画不同步和延迟是一回事吗?

更高的滴答率(Tick Rate)能解决同步不同步(Desync)的问题吗?

为什么我延迟低(ping值好)还会出现不同步?

回弹(Rubberbanding)和不同步(Desync)是一回事吗?

点对点游戏能避免不同步吗?

Get your Game Online Easily & in Minutes

立即开始集成!

轻松在线游戏
且在几分钟内