## 前言 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类型,就需要视携带的数据类型实现以下虚函数: ```c++ virtual TArray> GetActors() const; virtual bool SetActors(TArray> 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相关虚函数 ```c++ /** Returns the serialization data, must always be overridden */ virtual UScriptStruct* GetScriptStruct() const { return FGameplayAbilityTargetData::StaticStruct(); } /** Returns a debug string representation */ virtual FString ToString() const; ``` 源代码在实现完类型后,还有附带下面这一段代码,看注释应该和网络同步序列化有关,反正依瓢画葫芦复制+替换类型名称即可。 ```c++ template<> struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 { 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: ```c++ /** 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之间传递任意数据. 其他案例: https://www.thegames.dev/?p=62 使用方法: 1. 继承FGameplayEffectContext。 2. 重写FGameplayEffectContext::GetScriptStruct()。 3. 重写FGameplayEffectContext::Duplicate()。 4. 如果新数据需要同步的话, 重写FGameplayEffectContext::NetSerialize()。 5. 对子结构体实现TStructOpsTypeTraits, 就像父结构体FGameplayEffectContext有的那样. 6. 在AbilitySystemGlobals类中重写AllocGameplayEffectContext()以返回一个新的子结构体对象。(AbilitySystemGlobals还需要注册,请看下节) ```c++ FGameplayEffectContext* UGSAbilitySystemGlobals::AllocGameplayEffectContext() const { return new FGSGameplayEffectContext(); } ``` 在ExecutionCalculation中,你可以通过FGameplayEffectCustomExecutionParameters获取FGameplayEffectContext: ```c++ const FGameplayEffectSpec& Spec = ExecutionParams.GetOwningSpec(); FGSGameplayEffectContext* ContextHandle = static_cast(Spec.GetContext().Get()); ``` 在GameplayEffectSpec中获取EffectContext: ```c++ FGameplayEffectSpec* MutableSpec = ExecutionParams.GetOwningSpecForPreExecuteMod(); FGSGameplayEffectContext* ContextHandle = static_cast(MutableSpec->GetContext().Get()); ``` 在ExecutionCalculation中修改GameplayEffectSpec时要小心.参看GetOwningSpecForPreExecuteMod()的注释. ```c++ /** 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的注册方式。 ```c++ UAbilitySystemGlobals::Get().InitGlobalData(); ``` 可以参考UGSEngineSubsystem。 >AbilitySystemGlobals类保存有关GAS的全局信息. 大多数变量可以在DefaultGame.ini中设置. 一般你不需要和该类互动, 但是应该知道它的存在. 如果你需要继承像GameplayCueManager或GameplayEffectContext这样的对象, 就必须通过AbilitySystemGlobals来做. >想要继承AbilitySystemGlobals, 需要在DefaultGame.ini中设置类名: ```ini [/Script/GameplayAbilities.AbilitySystemGlobals] AbilitySystemGlobalsClassName="/Script/ParagonAssets.PAAbilitySystemGlobals" ``` ## RPGTargetType 这是GameplayEffectContainer提供的一种方便的产生TargetData方法。但因为使用CDO(Class Default Object)运行,所以不支持用户输入与取消等功能,功能上也不如TargetActor多。在官方的ActionRPG可以看到演示。这些Target类型定义于URPGTargetType,你可以根据获取Target的方式进行拓展,调用TargetType获取目标的逻辑位于MakeEffectContainerSpecFromContainer: ```c++ if (Container.TargetType.Get()) { TArray HitResults; TArray 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。