前言
TargetActor是GAS用于获取场景中物体、空间、位移等数据的机制,同时也可以用于制作可视化debug工具。,所以非常有必要掌握它。
一般流程为:使用WaitTargetData_AbilityTask生成TargetActor,之后通过TargetActor的内部函数或者射线获取场景信息,最后通过委托传递携带这些信息构建的FGameplayAbilityTargetDataHandle。
本文部分描述摘自GASDocumentation_Chinese,翻译的还不错,也请大家给此项目点赞。
TargetData
TargetData也就是FGameplayAbilityTargetData是用于通过网络传输定位数据的通用结构体。它主要用于存储目标数据(一般是TArray
TargetData一般由TargetActor或者手动创建(较少), 供AbilityTask或者GameplayEffect通过EffectContext使用. 因为其位于EffectContext中,所以Execution,MMC,GameplayCue和AttributeSet的后处理函数都可以访问该TargetData.
GAS不会直接传递数据,而是会借助FGameplayAbilityTargetDataHandle来进行传递。具体过程是
- 创建TargetData,并且填充数据。
- 创建FGameplayAbilityTargetDataHandle对象(也可以使用带参的构造函数直接构建),并且调用Add()添加上面创建的TargetData。
- 进行传递。
源代码中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中的逻辑为:
- GA_RocketLauncherSecondaryAAset在Configure节点中指定为ReticleClass为BP_SingleTargetReticle。
- 在UGSAT_WaitTargetDataUsingActor::FinalizeTargetActor()中调用StartTargeting()
- 最后会在AGSGATA_Trace(GASShooter中定义的TargetActor的基类)的StartTargeting()中进行Spawn。
GASShooter中的实现了BP_SingleTargetReticle,大致实现为:
- 在根组件下连接一个WidgetComponent
- 使用Widget3DPassThrough_Masked_OneSided材质,并且绑定一个UI_TargetReticle UMGAsset
GameplayEffectContext
可以认为这是一个传递数据用的结构体。GameplayEffectContext结构体存有关于GameplayEffectSpec创建者(Instigator)和TargetData的信息,可以向ModifierMagnitudeCalculation/GameplayEffectExecutionCalculation, AttributeSet和GameplayCue之间传递任意数据.
使用方法:
- 继承FGameplayEffectContext。
- 重写FGameplayEffectContext::GetScriptStruct()。
- 重写FGameplayEffectContext::Duplicate()。
- 如果新数据需要同步的话, 重写FGameplayEffectContext::NetSerialize()。
- 对子结构体实现TStructOpsTypeTraits, 就像父结构体FGameplayEffectContext有的那样.
- 在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中
- 类内指定AGSGATA_LineTrace对象作为变量。
- 调用AGSGATA_LineTrace对象的ResetSpread(),初始化参数。
- 调用UGSAT_ServerWaitForClientTargetData,并将ValidData委托绑定HandleTargetData。
- 之后执行客户端逻辑。
- 调用AGSGATA_LineTrace对象的Configure(),来设置具体参数。
- 将AGSGATA_LineTrace对象传入UGSAT_WaitTargetDataUsingActor,并将ValidData委托绑定HandleTargetData。