BlueRose
文章97
标签28
分类7
GAS的TargetData与TargetActor相关机制

GAS的TargetData与TargetActor相关机制

前言

TargetActor是GAS用于获取场景中物体、空间、位移等数据的机制,同时也可以用于制作可视化debug工具。,所以非常有必要掌握它。

一般流程为:使用WaitTargetData_AbilityTask生成TargetActor,之后通过TargetActor的内部函数或者射线获取场景信息,最后通过委托传递携带这些信息构建的FGameplayAbilityTargetDataHandle。

本文部分描述摘自GASDocumentation_Chinese,翻译的还不错,也请大家给此项目点赞。

TargetData

TargetData也就是FGameplayAbilityTargetData是用于通过网络传输定位数据的通用结构体。它主要用于存储目标数据(一般是TArray >)、FHitResult。当然可以传递一些自定义数据,这个可以参考源码中的FGameplayAbilityTargetData_LocationInfo。

TargetData一般由TargetActor或者手动创建(较少), 供AbilityTask或者GameplayEffect通过EffectContext使用. 因为其位于EffectContext中,所以Execution,MMC,GameplayCue和AttributeSet的后处理函数都可以访问该TargetData.

GAS不会直接传递数据,而是会借助FGameplayAbilityTargetDataHandle来进行传递。具体过程是

  1. 创建TargetData,并且填充数据。
  2. 创建FGameplayAbilityTargetDataHandle对象(也可以使用带参的构造函数直接构建),并且调用Add()添加上面创建的TargetData。
  3. 进行传递。

源代码中GameplayAbilityTargetTypes.h中实现了以下TargetData:

  • FGameplayAbilityTargetData_LocationInfo
  • FGameplayAbilityTargetData_ActorArray
  • FGameplayAbilityTargetData_SingleTargetHit

基本上是够用了,如果需要创建新的TargetData类型,就需要视携带的数据类型实现以下虚函数:

virtual TArray<TWeakObjectPtr<AActor>> GetActors() const;
virtual bool SetActors(TArray<TWeakObjectPtr<AActor>> NewActorArray);

virtual bool HasHitResult() const;
virtual const FHitResult* GetHitResult();
virtual void ReplaceHitWith(AActor* NewHitActor, const FHitResult* NewHitResult)

virtual bool HasOrigin() const;
virtual FTransform GetOrigin() const;

virtual bool HasEndPoint() const;
virtual FVector GetEndPoint() const;

virtual FTransform GetEndPointTransform() const;

/** See notes on delegate definition FOnTargetActorSwapped */
virtual bool ShouldCheckForTargetActorSwap() const;

debug相关虚函数

/** Returns the serialization data, must always be overridden */
virtual UScriptStruct* GetScriptStruct() const
{
    return FGameplayAbilityTargetData::StaticStruct();
}

/** Returns a debug string representation */
virtual FString ToString() const;

源代码在实现完类型后,还有附带下面这一段代码,看注释应该和网络同步序列化有关,反正依瓢画葫芦复制+替换类型名称即可。

template<>
struct TStructOpsTypeTraits<FGameplayAbilityTargetData_ActorArray> : public TStructOpsTypeTraitsBase2<FGameplayAbilityTargetData_ActorArray>
{
    enum
    {
        WithNetSerializer = true    // For now this is REQUIRED for FGameplayAbilityTargetDataHandle net serialization to work
    };
};

TargetActor

你可以把TargetActor理解为一个场景信息探测器,用来获取场景中数据以及进行可视化Debug。一般都是在Ability中通过AbilityTask_WaitTargetData来生成TargetActor(WaitTargetDataUsingActor用来监听已有的TargetActor),之后再通过ValidData委托接收TargetData。

GameplayAbilityWorldReticles

当使用non-Instant TargetActor定位时,TargetActor可以选择使用ReticleActor(GameplayAbilityWorldReticles)标注当前目标。

默认情况下,Reticle只会显示在TargetActor的当前有效Target上,如果想要让其显示在其他目标上,就需要自定义TargetActor,手动管理确定与取消事件,让目标持久化。

ReticleActor可以通过FWorldReticleParameters进行初始化(在TargetActor设置FWorldReticleParameters变量),但FWorldReticleParameters只有一个AOEScale变量,很明显完全不够用。所以你可以通过自定义ActorTarget与参数结构来改进这个功能。

Reticle默认是不可同步的,但是如果你想向其他玩家展示本地玩家正在定位的目标,那么它也可以被设置为可同步的。

ReticleActor还带有一些面向蓝图的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);

GAS默认的UAbilityTask_WaitTargetData节点会在FinalizeTargetActor()中调用AGameplayAbilityTargetActor_Trace::StartTargeting(),进行ReticleActor的Spawn。

GASShooter中的逻辑为:

  1. GA_RocketLauncherSecondaryAAset在Configure节点中指定为ReticleClass为BP_SingleTargetReticle。
  2. 在UGSAT_WaitTargetDataUsingActor::FinalizeTargetActor()中调用StartTargeting()
  3. 最后会在AGSGATA_Trace(GASShooter中定义的TargetActor的基类)的StartTargeting()中进行Spawn。

GASShooter中的实现了BP_SingleTargetReticle,大致实现为:

  1. 在根组件下连接一个WidgetComponent
  2. 使用Widget3DPassThrough_Masked_OneSided材质,并且绑定一个UI_TargetReticle UMGAsset

GameplayEffectContext

可以认为这是一个传递数据用的结构体。GameplayEffectContext结构体存有关于GameplayEffectSpec创建者(Instigator)和TargetData的信息,可以向ModifierMagnitudeCalculation/GameplayEffectExecutionCalculation, AttributeSet和GameplayCue之间传递任意数据.

使用方法:

  1. 继承FGameplayEffectContext。
  2. 重写FGameplayEffectContext::GetScriptStruct()。
  3. 重写FGameplayEffectContext::Duplicate()。
  4. 如果新数据需要同步的话, 重写FGameplayEffectContext::NetSerialize()。
  5. 对子结构体实现TStructOpsTypeTraits, 就像父结构体FGameplayEffectContext有的那样.
  6. 在AbilitySystemGlobals类中重写AllocGameplayEffectContext()以返回一个新的子结构体对象。(AbilitySystemGlobals还需要注册,请看下节)
FGameplayEffectContext* UGSAbilitySystemGlobals::AllocGameplayEffectContext() const
{
    return new FGSGameplayEffectContext();
}

在ExecutionCalculation中,你可以通过FGameplayEffectCustomExecutionParameters获取FGameplayEffectContext:

const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec();
FGSGameplayEffectContext* ContextHandle = static_cast<FGSGameplayEffectContext*>(Spec.GetContext().Get());

在GameplayEffectSpec中获取EffectContext:

FGameplayEffectSpec* MutableSpec = ExecutionParams.GetOwningSpecForPreExecuteMod();
FGSGameplayEffectContext* ContextHandle = static_cast<FGSGameplayEffectContext*>(MutableSpec->GetContext().Get());

在ExecutionCalculation中修改GameplayEffectSpec时要小心.参看GetOwningSpecForPreExecuteMod()的注释.

/** Non const access. Be careful with this, especially when modifying a spec after attribute capture. */
FGameplayEffectSpec* GetOwningSpecForPreExecuteMod() const;

PS.GASShooter实现了带有FGameplayAbilityTargetDataHandle变量的FGSGameplayEffectContext,以此来实现在GameplayCue中访问TargetData, 特别是对于霰弹枪来说十分有用, 因为它可以击打多个敌人并且产生效果。

InitGlobalData()

从UE 4.24开始, 必须调用UAbilitySystemGlobals::InitGlobalData()来使用TargetData, 否则你会遇到关于ScriptStructCache的错误, 并且客户端会从服务端断开连接, 该函数只需要在项目中调用一次. Fortnite从AssetManager类的起始加载函数中调用该函数, Paragon是从UEngine::Init()中调用的. 我发现将其放到UEngineSubsystem::Initialize()是个好位置, 这也是样例项目中使用的. 我觉得你应该复制这段模板代码到你自己的项目中以避免出现TargetData的使用问题.

如果你在使用AbilitySystemGlobals GlobalAttributeSetDefaultsTableNames时发生崩溃, 可能之后需要像Fortnite一样在AssetManager或GameInstance中调用UAbilitySystemGlobals::InitGlobalData()而不是在UEngineSubsystem::Initialize()中. 该崩溃可能是由于Subsystem的加载顺序引发的, GlobalAttributeDefaultsTables需要加载EditorSubsystem来绑定UAbilitySystemGlobals::InitGlobalData()中的委托.

上面说的意思是初始化需要通过Subsystem与其节点来进行初始化(这样就不需要通过继承来实现),GASDocument推荐在UEngineSubsystem(UEngine阶段)初始化时执行。下面是自定义AbilitySystemGlobals的注册方式。

UAbilitySystemGlobals::Get().InitGlobalData();

可以参考UGSEngineSubsystem。

AbilitySystemGlobals类保存有关GAS的全局信息. 大多数变量可以在DefaultGame.ini中设置. 一般你不需要和该类互动, 但是应该知道它的存在. 如果你需要继承像GameplayCueManager或GameplayEffectContext这样的对象, 就必须通过AbilitySystemGlobals来做.

想要继承AbilitySystemGlobals, 需要在DefaultGame.ini中设置类名:

[/Script/GameplayAbilities.AbilitySystemGlobals]
AbilitySystemGlobalsClassName="/Script/ParagonAssets.PAAbilitySystemGlobals"

RPGTargetType

这是GameplayEffectContainer提供的一种方便的产生TargetData方法。但因为使用CDO(Class Default Object)运行,所以不支持用户输入与取消等功能,功能上也不如TargetActor多。在官方的ActionRPG可以看到演示。这些Target类型定义于URPGTargetType,你可以根据获取Target的方式进行拓展,调用TargetType获取目标的逻辑位于MakeEffectContainerSpecFromContainer:

if (Container.TargetType.Get())
{
    TArray<FHitResult> HitResults;
    TArray<AActor *> TargetActors;
    const URPGTargetType *TargetTypeCDO = Container.TargetType.GetDefaultObject();
    AActor *AvatarActor = GetAvatarActorFromActorInfo();
    TargetTypeCDO->GetTargets(OwningCharacter, AvatarActor, EventData, HitResults, TargetActors);
    ReturnSpec.AddTargets(HitResults, TargetActors);
}

ActionRPG在c++中实现了UseOwner与UseEventData类型,在蓝图中实现了SphereTrace与LineTrace。

GASShooter中的用法

GASShooter在以下Asset中使用了TargetData与TargetActor

  • GA_RiflePrimaryInstant
  • GA_RocketLauncherPrimaryInstant
  • GA_RocketLauncherPrimaryInstant

GASShooter中实现了AGSGATA_LineTrace与AGSGATA_SphereTrace等AGameplayAbilityTargetActor(拥有共同的父类AGSGATA_Trace)。

步骤:
在AbilityActivate中

  1. 类内指定AGSGATA_LineTrace对象作为变量。
  2. 调用AGSGATA_LineTrace对象的ResetSpread(),初始化参数。
  3. 调用UGSAT_ServerWaitForClientTargetData,并将ValidData委托绑定HandleTargetData。
  4. 之后执行客户端逻辑。
  5. 调用AGSGATA_LineTrace对象的Configure(),来设置具体参数。
  6. 将AGSGATA_LineTrace对象传入UGSAT_WaitTargetDataUsingActor,并将ValidData委托绑定HandleTargetData。