4、GAS相关概念4.10 预测(Prediction)

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 预测实现试图解决的问题:

  1. “我能做到吗?” 预测的基本协议。
  2. “撤销” 当预测失败时如何撤销副作用。
  3. “重做” 如何避免重放我们在本地预测但也从服务器复制的副作用。
  4. “完整性” 如何确保我们/真正/预测了所有副作用。
  5. “依赖性” 如何管理依赖的预测和预测事件链。
  6. “覆盖” 如何预测性地覆盖状态,否则由服务器复制/拥有。

摘自 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 AbilityTaskOnlyServerWait选项)来解决此问题,从而生成一个新的作用域预测键。

4.10.3 预测性生成 Actor

在客户端上进行预测性生成Actor是一个高级话题。GAS本身并不提供开箱即用的功能来处理这一点(SpawnActor AbilityTask只会在服务器上生成Actor)。关键概念是,在客户端和服务器上生成一个被复制的Actor

如果生成的Actor只是装饰性元素或不影响游戏玩法,简单的解决方案是重写ActorIsNetRelevantFor()函数,以限制服务器不向拥有者客户端复制。这样,拥有者客户端会有自己的本地生成版本,服务器和其他客户端则会有服务器的复制版本。

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最近启动了一项替换CharacterMovementComponentNetwork Prediction的计划。该插件目前仍处于非常早期的阶段,但可以在Unreal EngineGitHub上获得早期访问权限。暂时还不清楚它会在哪个版本的引擎中首次发布实验性测试版。