GAS带有开箱即用的客户端预测的支持;但是,这并不意味着它能够完美预测所有的事情。GAS中的客户端预测意味着客户端不需要去等待服务器的许可就可以去激活GameplayAbility
并且应用GameplayEffects
。它也可以“预测”服务器给与它的许可并且预测它所应用GameplayEffects
的目标。 在客户端激活后,服务器会在网络延迟的时间后运行GameplayAbility
并且告诉客户端它所作的预测是否正确。如果客户端的预测中的某一项是错误的,它会对错误的预测进行回滚直到与服务器匹配为止。
GAS相关的预测内容是在插件源码中的GameplayPrediction.h
里。
Epic的思路是只去预测你需要应付的内容。例如,Paragon和Fortnite就不会去预测伤害的相关内容。最有可能的方案是,他们都使用了ExecutionCalculations
来进行伤害的处理,这也是无法进行预测的。当然,这并不是说你不可以去预测伤害或者跟其类似的东西,只是需要稍微进行一定的开发工作。
… we are also not all in on a “predict everything: seamlessly and automatically” solution. We still feel player prediction is best kept to a minimum (meaning: predict the minimum amount of stuff you can get away with).
我们也并不是希望搞出一个“完美、自动的预测”的解决方案。我们依旧认为,玩家的预测最好保持在一个最低的限度,这意思是,只去预测你需要应付的那些内容,越少越好。
摘自Dave Ratti关于新的Network Prediction Plugin网络预测插件的叙述
什么是可以预测的:
- 技能激活
- 触发事件
- GameplayEffect的应用:
Attribute
的修改(例外:Executions
当前并不能够预测,只有attribute modifiers
可以)GameplayTag
的修改Gameplay Cue
事件(从可预测的GE里发出的以及从其自身发出的)- 动画蒙太奇
- 移动(UE4内置的
UCharacterMovement
)
什么是不可以预测的:
GameplayEffect
的移除 removalGameplayEffect
的周期性效果(比如dot效果)
摘自GameplayPrediction.h
虽然我们可以预测GameplayEffect
的应用,我们却不能够预测GameplayEffect
的移除。这个限制也有对应的解决之道,就是去预测我们希望移除的GameplayEffect
的反效果。假设我们现在在预测一个速度减缓40%的效果。我们可以通过应用一个加速40%的速度buff来作为替代,最后再将两个效果同时移除。当然这并不是一劳永逸的完美的解决方法,我们还是需要有一个专门的针对GameplayEffect
的移除的解决方案。Epic的Dave Ratti在future iteration of GAS后续的迭代中能够逐步支持这个。
因为我们不能预测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的预测解决方案试图去解决的一些问题
“Can I do this?” Basic protocol for prediction.
“Undo” How to undo side effects when a prediction fails.
“Redo” How to avoid replaying side effects that we predicted locally but that also get replicated from the server.
“Completeness” How to be sure we /really/ predicted all side effects.
“Dependencies” How to manage dependent prediction and chains of predicted events.
“Override” How to override state predictively that is otherwise replicated/owned by the server.
我可以这样做吗?——预测的基本协议
撤销——当预测失败时如何撤销副作用
重播——如果避免重播我们本地预测和从服务器复制而来副作用
完整性——如果确保我们确实预测了所有的副作用
依赖性——如果管理依赖性预测和预测事件链条
覆盖——如果预测性得覆盖服务器原本已复制/拥有得状态
摘自GameplayPrediction.h
4.10.1 预测键 - Prediction Key
GAS的预测的进行是基于一个名为Prediction Key
预测键的概念,具体来说它是一个在客户端激活技能时所生成的一个整型标识符。
-
客户端激活一个
GameplayAbility
时生成一个预测键,这就是Activation Prediction Key
。 -
客户端利用
CallServerTryActivateAbility()
发送这个预测键到服务器。 -
当预测键有效时,客户端将这个预测键添加给所有它应用的
GameplayEffects
。 -
客户端的预测键超出作用范围,在同一个
GameplayAbility
中进一步预测效果则需要一个新的Scoped Prediction Window. -
服务器从客户端接收预测键。
-
服务器将这个预测键添加给所有它应用的
GameplayEffects
。 -
服务器将预测键复制回客户端。
-
客户端从服务器接收复制的
GameplayEffects
,如果复制回来得到的GameplayEffects
与客户端应用的GameplayEffects
有着相同的预测键,那这意味着预测正确。在这个瞬时的时间点会同时有着两份GameplayEffect
的拷贝,直到客户端将它预测的那个删除掉。 -
客户端从服务器接收预测键。即
Replicated Prediction Key
。这个预测键现在被标记为旧的。 -
客户端移除所有的标有旧的预测键的
GameplayEffects
。而由服务器复制得来的GameplayEffects
将会被保留。任何客户端添加的但是却没有收到服务器返回版本的GameplayEffects
都意味着预测的失败。
在Activation Prediction Key
中以Activation
开头的GameplayAbilities
中的一个原子指令组“window”期间,预测键保准有效。对于这句话你可以直接理解成仅在一帧内有效。任何后续AbilityTasks
中的回调都不会有有效的预测键,除非AbilityTask
中包含有内置的Synch Point
同步点,其会生成一个新的Scoped Prediction Window。
4.10.2 在技能中创建新的预测窗口 - Creating New Prediction Windows in Abilities
要预测AbilityTasks
的回调中的更多的行为,我们需要用一个新的预测键创建一个新的范围预测窗口。这个有时候也称为是客户端和服务器之间的同步点。一些AbilityTasks
,比如说所有的输入相关的那些,他们都内置了创建新的范围控制窗口的功能,意味着AbilityTasks
的回调中的那些原子代码可以使用一个有效的预测键。其他的一些任务,如WaitDelay
这种任务并没有内置的代码来为他的回调创建新的范围预测窗口。如果你希望去预测这样类型的AbilityTask
,就必须手动调用WaitNetSync
的AbilityTask
,并选择OnlyServerWait
。当客户端遇到带有OnlyServerWait
的WaitNetSync
时,它会基于GameplayAbility
的激活预测键来生成一个新的范围预测键,利用RPC将其发送到服务器,再将其添加给它所应用的新的GameplayEffects
。当服务器遇到带有OnlyServerWait
的WaitNetSync
,它将等待直到它从客户端接收到新的范围预测键才会继续。这个fa那位预测键的行为和激活预测键的行为基本一致 —— 应用到GameplayEffects
并且复制回客户端,并标记为旧的。范围预测键在超出作用域时会失效,随即范围预测窗口关闭。所以在此强调,仅仅那些非延迟的原子操作可以使用新的范围预测键。
可以根据你的需求随意去创建范围预测窗口,不用担心数量。
如果你为你定义的AbilityTasks
添加同步点的功能,可以参考输入的那些技能任务是如何将WaitNetSync
的AbilityTask
嵌入其中的。
**注意:**当使用WaitNetSync
时,它会阻塞服务器上GameplayAbility
的执行,直到接收到客户端的消息。这一点可能会被恶意的玩家利用,他们会黑掉游戏,延迟发送新的范围预测键。Epic就较少使用WaitNetSync
,如果你需要使用它,Epic的建议是你自己构建一个新的AbilityTask
,能够在一定的延迟后自动继续而不一定非得等待客户端的消息而造成阻塞。
示例项目在冲刺的GameplayAbility
中使用了WaitNetSync
为每次应用体力消减效果都创建了一个新的范围预测窗口,这样就可以针对这一点进行预测。理想状况下,在应用消耗和冷却时我们都想要一个有效的预测键。
如果你由一个预测的GameplayEffect
在其所属客户端上播放了两次,你的预测键被标记为旧的,并且遭遇了“redo”重播的问题。你通常可以在应用GameplayEffect
之前使用一个带有OnlyServerWait
的`WaitNetSync来创建一个新的范围预测键。
4.10.3 预测性得生成Actors - Predictively Spawning Actors
在客户端预测性得生成 Actors
是一个进阶的议题。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
会影响游玩,比如说会产生伤害的子弹,那么你需要你就需要更加高阶的技巧,但是这并不包含在本文档内。可以在Epic Games的Github上查找UnrealTournament项目,其中实现了可预测得生成子弹。他是只在所属客户端上生成一个虚拟子弹,该虚拟子弹与服务器的复制子弹同步。
4.10.4 技能系统中关于预测机制的未来规划 - Future of Prediction in GAS
GameplayPrediction.h
中提到在未来,他们可能还会加入预测GameplayEffect
的移除和预测周期性 GameplayEffects
的功能。
Epic的Dave Ratti from Epic提过expressed interest,以解决预测冷却的latency reconciliation
问题,从而让低延迟玩家比高延迟玩家更具备优势的问题得以解决。
Epic开发的新的Network Prediction
plugin插件将会完美与GAS协调,就像CharacterMovementComponent
那样。
4.10.5 网络预测插件 - Network Prediction Plugin
最近Epic发起了一项新的计划,即用新的Network Prediction
替换掉CharacterMovementComponent
。这个插件仍然处于早期的开发阶段,但是关于其的讨论在Unreal Engine GitHubs上已经非常热烈。现在还不太好讲未来在哪个版本我们会正式体验到。