4.10 预测(Prediction)
GAS 开箱即用地支持客户端预测;然而,它并不会预测所有内容。在 GAS 中,客户端预测意味着客户端无需等待服务器的许可即可激活 GameplayAbility
并应用 GameplayEffects
。它可以“预测”服务器允许其执行此操作,并预测将 GameplayEffects
应用于的目标。然后,服务器在客户端激活后运行 GameplayAbility
的网络延迟时间,并告知客户端其预测是否正确。如果客户端在任何预测中出错,它将“回滚”其“错误预测”中的更改以匹配服务器。
关于 GAS 相关预测的权威来源是插件源代码中的 GameplayPrediction.h
。
Epic 的心态是只预测你可以“逃脱”的东西。例如,Paragon 和 Fortnite 并不预测伤害。他们很可能使用 ExecutionCalculations
来计算其伤害,这无论如何都无法预测。这并不是说你不能尝试预测某些事情,如伤害。如果你这样做并且效果很好,那就太好了。
… 我们并没有“无缝且自动”地预测所有内容。我们仍然觉得玩家预测最好保持在最低限度(意思是:预测你可以“逃脱”的最小量的东西)。
来自 Epic 的 Dave Ratti 的评论,摘自新的 Network Prediction Plugin
预测的内容:
- Ability激活
- 触发事件
GameplayEffect
应用:
Attribute
修改(例外:执行(Execution)不进行预测,只有Attribute Modifiy
)GameplayTag
修改Gameplay Cue
事件(无论是在预测的游戏效果中还是单独)- 蒙太奇(
Montages
)- 移动(
Movement
)(内置于 UE 的UCharacterMovement
)
不可预测的内容:
GameplayEffect
移除GameplayEffect
的周期性效果(如持续伤害)
摘自 GameplayPrediction.h
虽然我们可以预测 GameplayEffect
的应用,但我们无法预测其移除。我们可以通过在想要移除 GameplayEffect
时预测逆向效果来解决此限制。比如说我们预测了移动速度降低 40%。我们可以通过应用 40% 的移动速度增益来预测性地移除它,然后同时移除两个 GameplayEffects
。这并不适用于每种情况,仍然需要支持预测 GameplayEffect
的移除。Epic 的 Dave Ratti 表示希望在 [GAS 的未来迭代(]https://epicgames.ent.box.com/s/m1egifkxv3he3u3xezb9hzbgroxyhx89)中添加此功能。
因为我们无法预测 GameplayEffects
的移除,所以我们无法完全预测 GameplayAbility
的冷却时间,并且没有逆向 GameplayEffect
的解决方案。服务器复制的 Cooldown GE
会存在于客户端,并且任何尝试绕过此复制(例如使用 Minimal
复制模式)都会被服务器拒绝。这意味着延迟较高的客户端需要更长时间告诉服务器进入冷却状态,并接收服务器的 Cooldown GE
的移除。也就是说,延迟较高的玩家的射击频率将低于延迟较低的玩家,这会使他们在与延迟较低的玩家对抗时处于劣势。Fortnite 通过使用自定义的账簿记录而不是 Cooldown GEs
来避免这个问题。
关于预测伤害,我个人不推荐尽管这是大多数人在开始使用 GAS 时尝试的第一件事。我尤其不推荐尝试预测死亡。虽然你可以预测伤害,但这样做很棘手。如果你错误地预测了伤害,玩家会看到敌人的健康值回升。这在你尝试预测死亡时尤其尴尬和令人沮丧。假如你错误地预测了 Character
的死亡,它开始软体化,但当服务器纠正它时,它会停止软体化并继续向你射击。
注意: Instant
GameplayEffects
(如 Cost GEs
)改变 Attributes
可以在自己身上无缝预测,预测对其他角色的 Instant
Attribute
更改将显示短暂的异常或“闪烁”。预测的 Instant
GameplayEffects
实际上被视为 Infinite
GameplayEffects
,以便在错误预测时可以回滚。当服务器的 GameplayEffect
被应用时,可能存在两个相同的 GameplayEffect
导致 Modifier
被应用两次或在短时间内根本没有应用。最终它会自行纠正,但有时玩家会注意到闪烁。
GAS 预测实现试图解决的问题:
- “我能做到吗?” 预测的基本协议。
- “撤销” 当预测失败时如何撤销副作用。
- “重做” 如何避免重放我们在本地预测但也从服务器复制的副作用。
- “完整性” 如何确保我们/真正/预测了所有副作用。
- “依赖性” 如何管理依赖的预测和预测事件链。
- “覆盖” 如何预测性地覆盖状态,否则由服务器复制/拥有。
摘自 GameplayPrediction.h
4.10.1 预测键(Prediction Key)
GAS 的预测基于 Prediction Key
的概念,它是客户端在激活 GameplayAbility
时生成的整数标识符。
-
客户端在激活
GameplayAbility
时生成一个预测键。这是Activation Prediction Key
。 -
客户端通过
CallServerTryActivateAbility()
将此预测键发送到服务器。 -
客户端将此预测键添加到其应用的所有
GameplayEffects
中,直到预测键有效。 -
客户端的预测键失效。在同一
GameplayAbility
中的进一步预测效果需要一个新的 Scoped Prediction Window。 -
服务器接收来自客户端的预测键。
-
服务器将此预测键添加到其应用的所有
GameplayEffects
中。 -
服务器将预测键复制回客户端。
-
客户端接收来自服务器的带有用于应用它们的预测键的复制
GameplayEffects
。如果任何复制的GameplayEffects
与客户端应用的具有相同预测键的GameplayEffects
匹配,则它们被正确预测。目标上将暂时存在两个GameplayEffect
的副本,直到客户端移除其预测的副本。 -
客户端从服务器接收预测键。这是
Replicated Prediction Key
。此预测键现在标记为陈旧。 -
客户端移除所有它创建的带有现已陈旧的复制预测键的
GameplayEffects
。服务器复制的GameplayEffects
将保留。客户端添加的任何GameplayEffects
且没有接收到来自服务器的匹配复制版本的被错误预测。
预测键保证在 GameplayAbilities
中的指令“窗口”中从 Activation
开始的原子分组期间有效。你可以将其理解为仅在一帧期间有效。任何来自潜在动作 AbilityTasks
的回调将不再有有效的预测键,除非 AbilityTask
内置同步点生成新的 Scoped Prediction Window。
4.10.2 在Ability中创建新的预测窗口(Prediction Window)
为了在AbilityTasks
的回调中预测更多的动作,我们需要创建一个新的作用域预测窗口,并使用新的作用域预测键。这通常被称为客户端与服务器之间的同步点(Synch Point
)。一些AbilityTasks
,特别是与输入相关的任务,内置了创建新作用域预测窗口的功能,这意味着在AbilityTasks
的回调中,原子操作(atomic code
)可以使用有效的作用域预测键。其他任务,比如WaitDelay
任务,则没有内置的创建作用域预测窗口的代码。如果你需要在没有内置功能的AbilityTask
之后进行动作预测(如WaitDelay
任务),则必须手动使用WaitNetSync AbilityTask
并选择OnlyServerWait
选项。
当客户端执行WaitNetSync
并选择OnlyServerWait
时,它会基于GameplayAbility
的激活预测键生成一个新的作用域预测键,将其RPC传输到服务器,并将该键添加到应用的新GameplayEffects
中。当服务器执行WaitNetSync
并选择OnlyServerWait
时,它会等待客户端发送新的作用域预测键后再继续。这种作用域预测键的作用与激活预测键相同——它应用于GameplayEffects
,并会被复制回客户端标记为过时。该作用域预测键在作用域失效(即作用域预测窗口关闭)之前是有效的。因此,再次强调,只有原子操作,不能有延迟的操作,才能使用新的作用域预测键。
你可以根据需要创建任意数量的作用域预测窗口。
如果你希望在自定义的AbilityTasks
中添加同步点功能,可以参考输入类任务的实现,了解它们是如何将WaitNetSync AbilityTask
代码注入其中的。
注意: 使用WaitNetSync
时,服务器的GameplayAbility
会被阻塞,直到接收到客户端的响应。如果不当使用,恶意用户可能会故意延迟发送新的作用域预测键,从而滥用这一机制。虽然Epic使用WaitNetSync
的频率较低,但如果你担心这个问题,Epic建议在AbilityTask
中构建一个新的延迟版本,使其在没有客户端响应的情况下自动继续执行。
示例项目中在Sprint GameplayAbility
中使用了WaitNetSync
,每次应用耐力消耗时都会创建一个新的作用域预测窗口,以便进行预测。理想情况下,在应用消耗和冷却时,我们希望有一个有效的预测键。
如果你在客户端上看到一个预测的GameplayEffect
被重复播放,说明你的预测键已经过时,正在经历“重做”问题。通常可以通过在应用GameplayEffect
之前插入一个WaitNetSync AbilityTask
(OnlyServerWait
选项)来解决此问题,从而生成一个新的作用域预测键。
4.10.3 预测性生成 Actor
在客户端上进行预测性生成Actor
是一个高级话题。GAS
本身并不提供开箱即用的功能来处理这一点(SpawnActor
AbilityTask
只会在服务器上生成Actor
)。关键概念是,在客户端和服务器上生成一个被复制的Actor
。
如果生成的Actor
只是装饰性元素或不影响游戏玩法,简单的解决方案是重写Actor
的IsNetRelevantFor()
函数,以限制服务器不向拥有者客户端复制。这样,拥有者客户端会有自己的本地生成版本,服务器和其他客户端则会有服务器的复制版本。
bool APAReplicatedActorExceptOwner::IsNetRelevantFor(const AActor * RealViewer, const AActor * ViewTarget, const FVector & SrcLocation) const
{
return !IsOwnedBy(ViewTarget);
}
如果生成的Actor
会影响游戏玩法,例如需要预测伤害的投射物,那么你需要一些更复杂的逻辑,超出了本文档的范围。可以参考UnrealTournament
如何在Epic Games的GitHub上预测性地生成投射物。他们会在拥有者客户端上生成一个虚拟的投射物,并与服务器复制的投射物同步。
4.10.4 GAS中预测的未来发展
GameplayPrediction.h
中提到,未来可能会增加预测GameplayEffect
移除和周期性GameplayEffects
的功能。
Epic的Dave Ratti 表示,他有兴趣解决预测冷却时间时的延迟对玩家的影响问题,特别是高延迟玩家相对于低延迟玩家的劣势问题。
Epic的新 Network Prediction
插件 预计将与GAS完全兼容,就像CharacterMovementComponent
以前与其兼容一样。
4.10.5 网络预测插件(Network Prediction plugin)
Epic最近启动了一项替换CharacterMovementComponent
的Network Prediction
的计划。该插件目前仍处于非常早期的阶段,但可以在Unreal Engine
的GitHub
上获得早期访问权限。暂时还不清楚它会在哪个版本的引擎中首次发布实验性测试版。