196 lines
11 KiB
Markdown
196 lines
11 KiB
Markdown
|
## 前言
|
|||
|
TargetActor是GAS用于获取场景中物体、空间、位移等数据的机制,同时也可以用于制作可视化debug工具。,所以非常有必要掌握它。
|
|||
|
|
|||
|
一般流程为:使用WaitTargetData_AbilityTask生成TargetActor,之后通过TargetActor的内部函数或者射线获取场景信息,最后通过委托传递携带这些信息构建的FGameplayAbilityTargetDataHandle。
|
|||
|
|
|||
|
本文部分描述摘自GASDocumentation_Chinese,翻译的还不错,也请大家给此项目点赞。
|
|||
|
## TargetData
|
|||
|
TargetData也就是FGameplayAbilityTargetData是用于通过网络传输定位数据的通用结构体。它主要用于存储目标数据(一般是TArray<TWeakObjectPtr<AActor> >)、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<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相关虚函数
|
|||
|
```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<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:
|
|||
|
```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<FGSGameplayEffectContext*>(Spec.GetContext().Get());
|
|||
|
```
|
|||
|
在GameplayEffectSpec中获取EffectContext:
|
|||
|
```c++
|
|||
|
FGameplayEffectSpec* MutableSpec = ExecutionParams.GetOwningSpecForPreExecuteMod();
|
|||
|
FGSGameplayEffectContext* ContextHandle = static_cast<FGSGameplayEffectContext*>(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<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。
|