虚幻引擎 GAS(GameplayAbilitySystem) 基础概念06 ...

2

主题

5

帖子

9

积分

新手上路

Rank: 1

积分
9
发表于 2022-12-17 21:21:28 | 显示全部楼层
前记:

很多翻译都是用的机器翻译不是很准确,我认真看了英文文档,再加上中文翻译做了该摘要,方便后来的学习者,也方便自己后续查阅。统编内容都看完了,只是把重点部分逐字逐句的翻译了过来,为了翻译的通顺,很多地方都是读了好几遍英文文档才翻译过来的,虽然翻译不易但是这个过程也加深了自己的理解。
字字不易,希望多多点赞,点喜欢和收藏。也希望多多提意见
正文:

GAS 的预测

1、GAS中客户端预测的意思是客户端无需等待服务端的许可而激活GameplayAbility和应用GameplayEffect 。GAS带有开箱即用的客户端预测功能, 然而, 它不能预测所有.
2、它可以"预测"服务端许可的,应用GameplayEffect的目标.客户端激活之后, 服务端在一个网络延迟之后运行GameplayAbility并告知客户端它的预测是否正确, 如果客户端的预测出错, 那么客户端就会"回滚"其"错误预测"的修改以匹配服务端.
3、GAS预测相关的代码都在这个插件源码的GameplayPrediction.h中
4、Epic的理念是只能预测"不受伤害(get away with)"的事情. 例如, Paragon和Fortnite不能预测伤害值, 它们很可能对伤害值使用了ExecutionCalculations, 而这无论如何是不能预测的.
5、可以预测的功能:

  • Ability激活
  • 触发事件
  • GameplayEffect application:
     Attribute修改(注意: Executions 目前无法预测, 只有attribute modifiers 可以预测)
     GameplayTag modification

  • GameplayCue事件(both from within predictive gameplay effect and on their own)
  • 播放蒙太奇动画
  • 移动(基于UE4 UCharacterMovement)
不可以预测的功能:

  • GameplayEffect removal
  • GameplayEffect periodic effects (dots ticking)
6、Fortnite通过使用自定义Bookkeeping而不是Cooldown GE的方法避免冷却时间不能预测的问题.
因为我们不能预测GameplayEffect的移除, 所以就不能完全预测GameplayAbility的冷却时间, 并且也没有相反的GameplayEffect这种变通方案可供使用. 服务端同步的Cooldown GE将会存于客户端上, 并且任何对其绕过的尝试(例如使用Minimal同步模式)都会被服务端拒绝. 这意味着高延迟的客户端会花费较长事件来告知服务端开始冷却和接收到服务端Cooldown GE的移除. 这意味着高延迟的玩家会比低延迟的玩家有更低的触发率, 从而劣势于低延迟玩家.
7、关于预测伤害值, 我个人不建议使用
       尽管它是大多数刚接触GAS的人最先做的事情之一, 我特别不建议尝试预测死亡, 虽然你可以预测伤害值, 但是这样做很棘手. 如果你错误预测地应用了伤害, 那么玩家将会看到敌人的生命值会跳动, 如果你尝试预测死亡, 那么这将会是特别尴尬和令人沮丧的, 假设你错误预测了某个Character的死亡, 那么它就会开启布娃娃模拟, 只有当服务端纠正后才会停止布娃娃模拟并继续向你射击.
8、Instant GameplayEffects(例如Cost GEs)修改自己属性的时候,可以被平滑的预测。预测其他角色的立即生效的属性,属性可能会有轻微的异常或者来回闪烁。
9、预测Instant GameplayEffects 和预测Infinite GameplayEffects 一样,当预测错误的时候,会做RollBack。
10、当服务端的GameplayEffect被应用时,短时间内,在客户端可能会出现同样的GameplayEffect被应用两次Modifier。虽然最终会被纠正,但是有时候这原因造成的属性闪烁是很明显的。
11、GAS的预测试图要解决的问题:

  • "Can I do this?" Basic protocol for prediction.
  • "Undo"当预测错误时如何消除其副作用.
  • "Redo"如何避免已经在客户端预测了,服务器同步过来之后又被重复执行了一遍.
  • "Completeness" How to be sure we /really/ predicted all side effects.
  • "Dependencies" 如何管理预测需要依赖的模块和已经预测的事件链表.
  • "Override" How to override state predictively that is otherwise replicated/owned by the server
12、GAS's 预测基于Prediction Key来运行,Prediction Key是客户端激活GameplayAbility时生成的整型标识符。

  • 客户端激活GameplayAbility时生成Prediction Key, 这是Activation Prediction Key.
  • 客户端通过CallServerTryActivateAbility()将该Prediction Key发送到服务端.
  • 当Prediction Key有效时, 客户端将其添加所有GameplayEffect上,使GameplayEffect被应用.
  • Client's prediction key falls out of scope. Further predicted effects in the same GameplayAbility need a new Scoped Prediction Window.
  • 服务端从客户端接收Prediction Key.
  • 服务端将Prediction Key添加到其应用的所有GameplayEffect.
  • 服务端同步该Prediction Key回客户端.
  • 客户端使用Prediction Key从服务端接收同步的GameplayEffect, 该Prediction Key用于应用GameplayEffect. 如果任何同步的GameplayEffect与客户端使用相同Prediction Key应用的GameplayEffect相匹配, 那么其就是正确预测的. 目标上暂时会有GameplayEffect的两份拷贝直到客户端移除它预测的那一个.
  • 客户端接收到服务端返回的Prediction Key, 这是之前用于同步的Prediction Key, 该Prediction Key现在被标记为Stale.
  • 客户端移除所有Stale Prediction Key对应的GameplayEffect. 由服务端同步的GameplayEffect会持续存在. 任何客户端添加的,但是没有收到从服务端通过来一个匹配的Prediction Key的,都被视为错误预测.
13、Prediction keys are guaranteed to be valid during an atomic grouping of instructions "window" inGameplayAbilitiesstarting withActivationfrom the activation prediction key. You can think of this as being only valid during one frame. Any callbacks from latent actionAbilityTaskswill no longer have a valid prediction key unless theAbilityTaskhas a built-in Synch Point which generates a newScoped Prediction Window.
14、 Creating New Prediction Windows in Abilities:
To predict more actions in callbacks from AbilityTasks, we need to create a new Scoped Prediction Window with a new Scoped Prediction Key. This is sometimes referred to as a Synch Point between the client and server. Some AbilityTasks like all of the input related ones come with built-in functionality to create a new scoped prediction window, meaning atomic code in the AbilityTasks' callbacks have a valid scoped prediction key to use. Other tasks like the WaitDelay task do not have built-in code to create a new scoped prediction window for its callback. If you need to predict actions after an AbilityTask that does not have built-in code to create a scoped prediction window like WaitDelay, we must manually do that using the WaitNetSync AbilityTask with the option OnlyServerWait. When the client hits a WaitNetSync with OnlyServerWait, it generates a new scoped prediction key based on the GameplayAbility's activation prediction key, RPCs it to the server, and adds it to any new GameplayEffects that it applies. When the server hits a WaitNetSync with OnlyServerWait, it waits until it receives the new scoped prediction key from the client before continuing. This scoped prediction key does the same dance as activation prediction keys - applied to GameplayEffects and replicated back to clients to be marked stale. The scoped prediction key is valid until it falls out of scope, meaning the scoped prediction window has closed. So again, only atomic operations, nothing latent, can use the new scoped prediction key.
You can create as many scoped prediction windows as you need.
If you would like to add the synch point functionality to your own custom AbilityTasks, look at how the input ones essentially inject the WaitNetSync AbilityTask code into them.
Note: When using WaitNetSync, this does block the server's GameplayAbility from continuing execution until it hears from the client. This could potentially be abused by malicious users who hack the game and intentionally delay sending their new scoped prediction key. While Epic uses the WaitNetSync sparingly, it recommends potentially building a new version of the AbilityTask with a delay that automatically continues without the client if this is a concern for you.
The Sample Project uses WaitNetSync in the Sprint GameplayAbility to create a new scoped prediction window every time we apply the stamina cost so that we can predict it. Ideally we want a valid prediction key when applying costs and cooldowns.
If you have a predicted GameplayEffect that is playing twice on the owning client, your prediction key is stale and you're experiencing the "redo" problem. You can usually solve this by putting a WaitNetSync AbilityTask with OnlyServerWait right before you apply the GameplayEffect to create a new scoped prediction key.
15、Spawning Actors 的预测
      在客户端预测性地生成Actor是一项还没有实现的功能。GAS 目前SpawnActor AbilityTask 只在服务端Spawns Actor 。
      如果Actor只是用于场景装饰或者不服务于任何游戏逻辑, 简单的解决方案就是重写Actor的IsNetRelevantFor()函数以限制服务端同步到所属(Owning)客户端, 所属(Owning)客户端会拥有其本地生成的版本, 而服务端和其他客户端会拥有服务端同步的版本.
bool APAReplicatedActorExceptOwner::IsNetRelevantFor(const AActor * RealViewer, const AActor * ViewTarget, const FVector & SrcLocation) const
{
        return !IsOwnedBy(ViewTarget);
}      如果生成的Actor影响了游戏逻辑, 像投掷物就需要预测伤害值, 那么你需要本文档范围之外的高级知识, 在Epic Games的Github上查看UnrealTournament是如何生成投掷物的。有一个dummy projectile会在owning client 生成,dummy projectile同时融合了服务端的同步逻辑.
16、 GAS  Prediction 将来会进一步做的事情:
      在GameplayPrediction.h中说明了在未来可能会增加对GameplayEffect移除预测和对periodic GameplayEffect的预测.
      来自Epic的Dave Ratti已经表达过对其修复预测冷却时间的兴趣,冷却时间不可以预测会导致,高延迟玩家对低延迟玩家的劣势.
      来自Epic的新网络预测插件(Network Prediction plugin)有网与GAS充分交互, 就像在次之前的CharacterMovementComponent一样.
17、Network Prediction Plugin
      Epic最近开始提倡, 使用新的网络预测插件替换CharacterMovementComponent。该插件目前仍处于比较早期的阶段,但是在Unreal Engine Github上已经可以访问了。

GAS Targeting 相关

1、Target Data:
FGameplayAbilityTargetData是用于通过网络传输定位数据的通用结构体. TargetData一般用于保存AActor/UObject引用, FHitResult和其他通用的Location/Direction/Origin信息. 然而, 本质上你可以继承它以增添想要的任何数据, 其可以简单理解为在客户端和服务端的GameplayAbility中传递数据. 基础结构体FGameplayAbilityTargetData不能直接使用, 而是要继承它. GAS的GameplayAbilityTargetTypes.h中有一些开箱即用的派生FGameplayAbilityTargetData结构体.
TargetData一般由Target Actor或者手动创建, 供AbilityTask使用, 或者GameplayEffect通过EffectContext使用. 因为其位于EffectContext中, 所以Execution, MMC, GameplayCue和AttributeSet的后端函数可以访问该TargetData.
我们一般不直接传递FGameplayAbilityTargetData而是使用FGameplayAbilityTargetDataHandle, 其包含一个FGameplayAbilityTargetData指针类型的TArray, 这个中间结构体可以为TargetData的多态性提供支持.
2、Target Actor:
      为了在 world中能够可视化并且能够捕获到targeting information ,GameplayAbility会通过通过WaitTargetData AbilityTask生成TargetActor。TargetActors 可以通过使用GameplayAbilityWorldReticles(类似下图,一种标注选中目标的方式) 显示当前选中的目标。一旦点击确认之后,targeting information会作为可以传入GameplayEffects的TargetData返回。


      3、TargetActor是基于AActor的, 因此它可以使用任意种类的可视组件来表示它选取的目标在何处和现在怎么样了, 例如Static Mesh和Decal(贴花)。样例项目使用带有地面贴花的AGameplayAbilityTargetActor_GroundTrace表示陨石技能的伤害区域效果
      4、GameplayAbilities通过基础的射线检测或者碰撞的overlaps事件来捕获targeting information,根据TargetActor的实现方式不同会将命中结果转换为FHitResults或者AActor数组放入TargetData里面。WaitTargetData AbilityTask  通过它的TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType参数决定targets何时被确认。
5、当不使用TEnumAsByte<EGameplayTargetingConfirmation::Type::Instant时,TargetActor一般就在Tick()中执行射线/Overlap, 并根据它的实现来更新位置信息到FHitResult ,当Tick()中的检测对客户端响应非常频繁时, 如果性能影响很大的话, 你可能就要考虑降低TargetActor的Tick频率.对于TEnumAsByte<EGameplayTargetingConfirmation::Type::Instant,TargetActor会立即生成, 产生TargetData, 然后销毁,不会调用Tick().


并不是所有的TargetActor都支持每个EGameplayTargetingConfirmation::Type, 例如,AGameplayAbilityTargetActor_GroundTrace就不支持Instant的确认方式。
6、WaitTargetData AbilityTask将一个AGameplayAbilityTargetActor类作为参数, 其会在每次AbilityTask激活时生成一个实例并且在AbilityTask结束时销毁该TargetActor.WaitTargetDataUsingActor AbilityTask接受一个已经生成的TargetActor, 但是在该AbilityTask结束时仍会销毁它. 这两种AbilityTask都是低效的, 因为它们在每次使用时都要生成或需要一个新生成的TargetActor, 它们用于原型开发是很好的, 但是在实际发布版本中, 如果有持续产生TargetData的场景, 像全自动开火, 你可能就要探索优化它的办法. GASShooter有一个自定义的AGameplayAbilityTargetActor子类和一个完全重写的WaitTargetDataWithReusableActor AbilityTask, 其允许你复用TargetActor而无需将其销毁.
7、TargetActor默认是不可同步的,但是,如果我们的游戏需要向其他玩家展示本地客户端选取的目标在那里。在WaitTargetData AbilityTask 上包含与服务器交互,通过RPC来同步的功能的。
8、根据使用的不同AGameplayAbilityTargetActor子类,WaitTargetData AbilityTask节点会暴露不同的ExposeOnSpawn参数, 一些常用的参数包括:


9、使用默认的TargetActor类时, Actor只有直接在射线/Overlap中时才是有效的, 如果它离开射线/Overlap(它移动开或你的视线转向别处), 就不再有效了.如果你想TargetActor记住最后有效的Target, 就需要添加这项功能到一个自定义的TargetActor类。称之为持久化Target, 因为其会持续存在直到TargetActor接收到确认或取消消息,或者TargetActor在它的射线/Overlap中找到一个新的有效Target, 或者Target不再有效(已销毁).GASShooter对火箭筒二技能的"定位火箭目标"使用了持久化Target.
10、Target Data Filters:
同时使用Make GameplayTargetDataFilter和Make Filter Handle节点, 你可以过滤玩家或者只选择某个特定类. 如果需要更多高级过滤条件, 可以继承FGameplayTargetDataFilter并重写FilterPassesForActor函数.
USTRUCT(BlueprintType)
struct GASDOCUMENTATION_API FGDNameTargetDataFilter : public FGameplayTargetDataFilter
{
        GENERATED_BODY()

        /** Returns true if the actor passes the filter and will be targeted */
        virtual bool FilterPassesForActor(const AActor* ActorToBeFiltered) const override;
};
然而,因为Wait Target Data节点需要一个FGameplayTargetDataFilterHandle,所以这个过滤不会对Wait Target Data节点直接生效。必须创建一个新的自定义Make Filter句柄来接受子类:
FGameplayTargetDataFilterHandle UGDTargetDataFilterBlueprintLibrary::MakeGDNameFilterHandle(FGDNameTargetDataFilter Filter, AActor* FilterActor)
{
        FGameplayTargetDataFilter* NewFilter = new FGDNameTargetDataFilter(Filter);
        NewFilter->InitializeFilterContext(FilterActor);

        FGameplayTargetDataFilterHandle FilterHandle;
        FilterHandle.Filter = TSharedPtr<FGameplayTargetDataFilter>(NewFilter);
        return FilterHandle;
}

11、Gameplay Ability World Reticles
      当使用non-Instant的确认方式的TargetActor时,AGameplayAbilityWorldReticles可以可视化正在定位的目标.TargetActor负责所有Reticle生命周期的生成和销毁,Reticle是AActor, 因此其可以使用任意种类的可视组件作为表现形式.GASShooter中常见的一种实现方式是使用WidgetComponent在屏幕空间中显示UMG Widget(永远面对玩家的摄像机).Reticle不知道其正在定位的Actor, 但是你可以通过继承在自定义TargetActor中实现该功能.TargetActor一般在每次Tick()中更新Reticle的位置为Target的位置.
      GASShooter对火箭筒二技能"锁定的目标"使用了Reticle. 敌人身上的红色标识就是Reticle, 相似的白色图像是火箭筒的准星.


12,Reticle带有一些面向设计师的蓝图处理接口,BlueprintImplementableEvents(它们被设计用来在蓝图中开发):
/** Called whenever bIsTargetValid changes value. */
UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void OnValidTargetChanged(bool bNewValue);

/** Called whenever bIsTargetAnActor changes value. */
UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void OnTargetingAnActor(bool bNewValue);

UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void OnParametersInitialized();

UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void SetReticleMaterialParamFloat(FName ParamName, float value);

UFUNCTION(BlueprintImplementableEvent, Category = Reticle)
void SetReticleMaterialParamVector(FName ParamName, FVector value);
13、Reticle默认是不可同步的, 但是如果在你的游戏中向其他玩家展示本地玩家正在定位的目标是有意义的, 那么它也可以被设计成可同步的.
14、Reticle只会显示在默认TargetActor的当前有效Target上,  例如你正在使用AGameplayAbilityTargetActor_SingleLineTrace对目标执行射线检测, 敌人只有直接处于射线路径上时Reticle才会显示, 如果角色视线看向别处, 那么该敌人就不再是一个有效Target, 因此该Reticle就会消失. 如果你想Reticle保留在最后一个有效Target上, 就需要自定义TargetActor来记住最后一个有效Target并使Reticle保留在其上。
15、Gameplay Effect Containers Targeting:
GameplayEffectContainer提供了一个可选的产生TargetData的高效方法. 当EffectContainer在客户端和服务端上应用时, targeting会立即进行。它比TargetActor更有效率, 因为它是运行在targeting object的CDO上的(没有Actor的生成和销毁),但是它不支持用户输入, 无需确认即可立即进行, 不能取消, 并且不能从客户端向服务端发送数据(在两者上产生数据), 它对即时射线检测和碰撞Overlap很有用。Epic的Action RPG Sample Project包含两种使用Container瞄准目标的样例.
回复

举报 使用道具

您需要登录后才可以回帖 登录 | 立即注册
快速回复 返回顶部 返回列表