本页尚未翻译,当前显示英文版本。
Lag Compensation
这是服务器端的机制,它一旦出错,就会让玩家丢掉一条命,而且偏向网络更差的一方。本页的一切设计都源于这种不对称。
它回答的问题很窄:射击者当时究竟看到了什么? 一个操作可以携带视角时间,即主体(actor)行动时所看到的 tick,平台会恢复目标在那个 tick 的位姿,让这一枪按射击者屏幕上的画面判定。
视角时间是声称,不是事实
视角时间来自客户端,所以它是调用方的断言,平台也按断言对待。由此有两个结果:
- 补偿窗口有上限,超出窗口时平台拒绝。 平台不会好心去外推:拒绝是你看得见的决定,静默外推则是你看不见的。
- 读取目标的过去状态同样遵守可见性规则。 查询历史 tick 并不能绕过 Visibility:当时看不到的,现在也读不到。
另外,“命中不算数”是一个裁决,而不是错误:它是一次成功的应答,附带机器可读的原因。
回滚哪些内容由声明决定,而且不是全部
全部回滚会导致双杀:两名玩家互相射击,双方都被回退到两人都还活着的时刻,于是都命中。什么都不回滚,延迟补偿(lag compensation)本身就失去了意义。两者之间的边界是一份声明的清单。
回退本身由本模块负责,而不是由被回退的模块负责。历史环形缓冲区恢复有争议的那个 tick 的位姿,再就这些位姿向 Collision 提出普通的重叠查询:碰撞模块不保存历史,里面也没有任何部分知道视角 tick 是什么。环形缓冲区就是实体的历史轨道,而不是另一份存储。
判定基于过去,效果作用于现在
延迟补偿回答的是关于射击者视角时刻的问题。后果(伤害、死亡、奖励)作用于当前状态。视角时刻与判定时刻之间发生的事情既不撤销,也不重新计算。
玩家被击杀后仍可能开出一枪
一名玩家已经被别人经过回退判定的一枪击杀,之后仍可能开出一枪。这是有意为之:要撤销这一枪,就得在不保证可复现的回退之上重放整个世界,这只会制造偏差,而不是消除偏差。
服务器端重新模拟不在范围之内。 要按新的事实重新计算后果,需要一个固定的参照点,而浮点状态给不了。保留下来的是本模块依赖的全部基础:客户端重放自己未确认的输入(预测自身移动),以及把延迟补偿当作为一次判定读取过去。“射击者优先”在实践中就是这样实现的。
如果你用过其他方案
- “射击者优先”的延迟补偿,大多数竞技射击游戏都采用这种做法:机制相同,本页讲的就是它。
- 完整的 rollback netcode。 这里有回退,但没有事后重放世界,原因见上一段。如果你的设计依赖事后重新计算后果,请尽早和我们沟通这一点,不要等到后期才发现。
其他须知
- 实现可以覆盖。 如果你的游戏需要不同的补偿规则,可以替换我们的实现,替换后的实现需声明它遵守哪些声明。
- 预测和校正路径上没有扩展点。 它们按 tick 频率运行。
- 在外部权威下,以上都不适用。 延迟补偿只用于由平台模拟运行的房间(room)。如果 tick 归工作室的游戏服务器或主客户端运行,补偿也归运行方负责,见由谁运行 tick。