跳到主要内容
实时游戏

本页尚未翻译,当前显示英文版本。

Lag Compensation

这是服务器端的机制,它一旦出错,就会让玩家丢掉一条命,而且偏向网络更差的一方。本页的一切设计都源于这种不对称。

它回答的问题很窄:射击者当时究竟看到了什么? 一个操作可以携带视角时间,即主体(actor)行动时所看到的 tick,平台会恢复目标在那个 tick 的位姿,让这一枪按射击者屏幕上的画面判定。

视角时间是声称,不是事实

视角时间来自客户端,所以它是调用方的断言,平台也按断言对待。由此有两个结果:

  • 补偿窗口有上限,超出窗口时平台拒绝。 平台不会好心去外推:拒绝是你看得见的决定,静默外推则是你看不见的。
  • 读取目标的过去状态同样遵守可见性规则。 查询历史 tick 并不能绕过 Visibility:当时看不到的,现在也读不到。

另外,“命中不算数”是一个裁决,而不是错误:它是一次成功的应答,附带机器可读的原因。

回滚哪些内容由声明决定,而且不是全部

全部回滚会导致双杀:两名玩家互相射击,双方都被回退到两人都还活着的时刻,于是都命中。什么都不回滚,延迟补偿(lag compensation)本身就失去了意义。两者之间的边界是一份声明的清单。

回退本身由本模块负责,而不是由被回退的模块负责。历史环形缓冲区恢复有争议的那个 tick 的位姿,再就这些位姿向 Collision 提出普通的重叠查询:碰撞模块不保存历史,里面也没有任何部分知道视角 tick 是什么。环形缓冲区就是实体的历史轨道,而不是另一份存储。

判定基于过去,效果作用于现在

the shooterthe server fired, seeing the world several ticks agorewind the declared state to that tick, judge thereapply the effect in the presentthe outcome
这一枪按射击者看到的 tick 判定,效果作用于现在

延迟补偿回答的是关于射击者视角时刻的问题。后果(伤害、死亡、奖励)作用于当前状态。视角时刻与判定时刻之间发生的事情既不撤销,也不重新计算。

玩家被击杀后仍可能开出一枪

一名玩家已经被别人经过回退判定的一枪击杀,之后仍可能开出一枪。这是有意为之:要撤销这一枪,就得在不保证可复现的回退之上重放整个世界,这只会制造偏差,而不是消除偏差。

服务器端重新模拟不在范围之内。 要按新的事实重新计算后果,需要一个固定的参照点,而浮点状态给不了。保留下来的是本模块依赖的全部基础:客户端重放自己未确认的输入(预测自身移动),以及把延迟补偿当作为一次判定读取过去。“射击者优先”在实践中就是这样实现的。

如果你用过其他方案

  • “射击者优先”的延迟补偿,大多数竞技射击游戏都采用这种做法:机制相同,本页讲的就是它。
  • 完整的 rollback netcode。 这里有回退,但没有事后重放世界,原因见上一段。如果你的设计依赖事后重新计算后果,请尽早和我们沟通这一点,不要等到后期才发现。

其他须知

  • 实现可以覆盖。 如果你的游戏需要不同的补偿规则,可以替换我们的实现,替换后的实现需声明它遵守哪些声明。
  • 预测和校正路径上没有扩展点。 它们按 tick 频率运行。
  • 在外部权威下,以上都不适用。 延迟补偿只用于由平台模拟运行的房间(room)。如果 tick 归工作室的游戏服务器或主客户端运行,补偿也归运行方负责,见由谁运行 tick。