获取目标
思考一下, 我们释放技能的时候需要知道目标是谁, 最好需要有HitResult
数据方便我们知道打击位置播放特效等效果
那么各类技能或者普通攻击获取这个信息的方式都可能不一样,所以我们就自定义一个类专门来用于获取目标数据
1 2 3 4 5 6 7 8 9 10 11
| UCLASS(Blueprintable, meta = (ShowWorldContextPin)) class SUPERROAD_API USRTargetType : public UObject { GENERATED_BODY()
public: USRTargetType() {}
UFUNCTION(BlueprintNativeEvent) void GetTargets(ASRCharacterBase* TargetingCharacter, AActor* TargetingActor, FGameplayEventData EventData, TArray<FHitResult>& OutHitResults, TArray<AActor*>& OutActors) const; };
|
这个类就专门提供给蓝图重写, 默认不需要实现什么内容
- TargetingCharacter: 释放技能的角色
- TargetingActor: 释放技能的对象, 可能是角色,也可能是武器或者投掷物等
- EventData: 即
FGameplayEventData
,用于扩展参数
如上图, 我们做一个最简单的box检测, 把找到的目标返回出去.
至于怎么用, 我们后面将
创建新的数据结构
前言说了, 我们播放动画然后需要收到事件信息, 理论上我们可以如下图这样做
但是这样太笨拙, 而且WaitGameplayEvent
返回的参数也不是很方便我们执行GE
,所以我们把相关数据结构整合一下然后后面再创建一个新的整合版的异步事件
FSRGameplayEffectContainer
这个数据会作为TMap
的值放到GA
的配置中,保存了一个SRTargetType
类,即前面讲的获取目标↑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| USTRUCT(BlueprintType) struct SUPERROAD_API FSRGameplayEffectContainer { GENERATED_BODY()
public: FSRGameplayEffectContainer() {}
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = GameplayEffectContainer) TSubclassOf<USRTargetType> TargetType;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = GameplayEffectContainer) TArray<TSubclassOf<UGameplayEffect>> TargetGameplayEffectClasses; };
|
然后我们到我们的GA
基类SRGameplayAbilityBase
中声明变量
1 2 3
| UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = GameplayEffects) TMap<FGameplayTag, FSRGameplayEffectContainer> EffectContainerMap;
|
FSRGameplayEffectContainerSpec
用来处理SRGameplayEffectContainer
数据传递的结构体,参考GE和GA等都会有类似的Spec类
1 2 3 4 5 6 7
| UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = GameplayEffectContainer) FGameplayAbilityTargetDataHandle TargetData;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = GameplayEffectContainer) TArray<FGameplayEffectSpecHandle> TargetGameplayEffectSpecs;
|
几个函数有必要看一下, 因为后面马上我们会用到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| bool FSRGameplayEffectContainerSpec::HasValidEffects() const { return TargetGameplayEffectSpecs.Num() > 0; }
bool FSRGameplayEffectContainerSpec::HasValidTargets() const { return TargetData.Num() > 0; }
void FSRGameplayEffectContainerSpec::AddTargets(const TArray<FHitResult>& HitResults, const TArray<AActor*>& TargetActors) { for (const FHitResult& HitResult : HitResults) { FGameplayAbilityTargetData_SingleTargetHit* NewData = new FGameplayAbilityTargetData_SingleTargetHit(HitResult); TargetData.Add(NewData); }
if (TargetActors.Num() > 0) { FGameplayAbilityTargetData_ActorArray* NewData = new FGameplayAbilityTargetData_ActorArray(); NewData->TargetActorArray.Append(TargetActors); TargetData.Add(NewData); } }
|
异步事件
前言大概提了一下GAS
中已经封装了很多继承自AbilityTask
的异步事件, 我们找到UAbilityTask_PlayMontageAndWait
类, 在此基础上扩展一些参数就可以了
创建类UAbilityTask_PlayMontage : public UAbilityTask
申明如下代理
1
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FSRPlayMontageWaitEventDlg, FGameplayTag, EventTag, FGameplayEventData, EventData);
|
这个代理决定我们异步事件回调函数的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| UPROPERTY(BlueprintAssignable) FSRPlayMontageWaitEventDlg OnCompleted; UPROPERTY(BlueprintAssignable) FSRPlayMontageWaitEventDlg OnBlendOut; UPROPERTY(BlueprintAssignable) FSRPlayMontageWaitEventDlg OnInterrupted;
UPROPERTY(BlueprintAssignable) FSRPlayMontageWaitEventDlg OnCancelled;
UPROPERTY(BlueprintAssignable) FSRPlayMontageWaitEventDlg EventReceived;
|
上面参数用了我们自己的代理, 除了最后一个其余是模仿UAbilityTask_PlayMontageAndWait
的
然后一路模仿, 实现关于事件监听和广播
1 2 3
| FDelegateHandle EventHandle; EventHandle = ASC->AddGameplayEventTagContainerDelegate(EventTags, FGameplayEventTagMulticastDelegate::FDelegate::CreateUObject(this, &UAbilityTask_PlayMontage::OnGameplayEvent));
|
1 2 3 4 5 6 7 8 9 10
| void UAbilityTask_PlayMontage::OnGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload) { if (ShouldBroadcastAbilityTaskDelegates()) { FGameplayEventData TempData = *Payload; TempData.EventTag = EventTag;
EventReceived.Broadcast(EventTag, TempData); } }
|
完成!
封装激活GE事件
我们测试一下, 我们通过动画通知到角色, 然后角色调用SendGameplayEventToActor
后成功走到了我们测试的GA
但是蓝图中用这个返回参数来应用GE
还是够呛
那么接下来就是封装可以利用这两个返回参数能执行相应GE
效果的函数
1 2 3 4 5 6 7 8 9 10 11
| TArray<FActiveGameplayEffectHandle> USRGameplayAbilityBase::ApplyEffectContainerSpec(const FSRGameplayEffectContainerSpec& ContainerSpec) { TArray<FActiveGameplayEffectHandle> AllEffects;
for (const FGameplayEffectSpecHandle& SpecHandle : ContainerSpec.TargetGameplayEffectSpecs) { AllEffects.Append(K2_ApplyGameplayEffectSpecToTarget(SpecHandle, ContainerSpec.TargetData)); } return AllEffects; }
|
前面我们已经申明了自己的数据结构,通过这个FSRGameplayEffectContainerSpec
用K2_ApplyGameplayEffectSpecToTarget
执行GE
, 那我们还需要一个构建这个Spec
参数
MakeEffectContainerSpecFromContainer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| FSRGameplayEffectContainerSpec USRGameplayAbilityBase::MakeEffectContainerSpecFromContainer(const FSRGameplayEffectContainer& Container, const FGameplayEventData& EventData, int32 OverrideGameplayLevel ) { FSRGameplayEffectContainerSpec resultSpec; AActor* OwningActor = GetOwningActorFromActorInfo(); ASRCharacterBase* OwningChar = Cast<ASRCharacterBase>(OwningActor); USRAbilitySystemComponent* OwningASC = Cast<USRAbilitySystemComponent>(GetAbilitySystemComponentFromActorInfo());
if (OwningASC) { if (Container.TargetType.Get()) { TArray<FHitResult> HitResults; TArray<AActor*> TargetActors; const USRTargetType* TargetTypeCDO = Container.TargetType.GetDefaultObject(); AActor* AvatarActor = GetAvatarActorFromActorInfo(); TargetTypeCDO->GetTargets(OwningChar, AvatarActor, EventData, HitResults, TargetActors); resultSpec.AddTargets(HitResults, TargetActors); } if (OverrideGameplayLevel == INDEX_NONE) { OverrideGameplayLevel = GetAbilityLevel(); }
for (const TSubclassOf<UGameplayEffect>& c : Container.TargetGameplayEffectClasses) { resultSpec.TargetGameplayEffectSpecs.Add(MakeOutgoingGameplayEffectSpec(c, OverrideGameplayLevel)); } } return resultSpec; }
|
这个方法目标是从SRTargetType
类得到目标类和Hit
数据, 通过FSRGameplayEffectContainerSpec::AddTargets
()添加到TargetData
数据中
但是这个函数是通过FSRGameplayEffectContainer
和EventData
创建Spec
, 还不是特别的方便
接下来我们补充一个通过Tag
来构建Spec
的辅助函数
1 2 3 4 5 6 7 8 9 10
| FSRGameplayEffectContainerSpec USRGameplayAbilityBase::MakeEffectContainerSpec(FGameplayTag ContainerTag, const FGameplayEventData& EventData, int32 OverrideGameplayLevel ) { FSRGameplayEffectContainer* FoundContainer = EffectContainerMap.Find(ContainerTag);
if (FoundContainer) { return MakeEffectContainerSpecFromContainer(*FoundContainer, EventData, OverrideGameplayLevel); } return FSRGameplayEffectContainerSpec(); }
|
关键就是从我们GA
配置的Map
变量去找到对应的值, 那这个函数就是我们比较方便使用的
那如果我们希望通过异步事件的返回参数直接应用GE
, 我们每次要执行两个函数, 比较麻烦,接下来把两个函数合到一起
1 2 3 4 5
| TArray<FActiveGameplayEffectHandle> USRGameplayAbilityBase::ApplyEffectContainer(FGameplayTag ContainerTag, const FGameplayEventData& EventData, int32 OverrideGameplayLevel ) { FSRGameplayEffectContainerSpec Spec = MakeEffectContainerSpec(ContainerTag, EventData, OverrideGameplayLevel); return ApplyEffectContainerSpec(Spec); }
|
测试
我们的测试GE
就扣一点血,如下图
成功应用
补充若干重要的结构体数据
FGameplayEventData
传递数据的一个结构体,包含了诸多信息, 也有两个专门针对GA
的Tag
栏的标记(SourceTag
,TargetTag
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) FGameplayTag EventTag;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) const AActor* Instigator;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) const AActor* Target;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) const UObject* OptionalObject;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) const UObject* OptionalObject2;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) FGameplayEffectContextHandle ContextHandle;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) FGameplayTagContainer InstigatorTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) FGameplayTagContainer TargetTags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) float EventMagnitude;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = GameplayAbilityTriggerPayload) FGameplayAbilityTargetDataHandle TargetData;
|
目标数据/FGameplayAbilityTargetData
这是一个基类,仅提供了一些虚函数
目的是传递发起者和目标的基本信息
FGameplayAbilityTargetDataHandle
处理前者数据的类,一般在传递数据时候使用
保存了FGameplayAbilityTargetData
的数组
FGameplayAbilityTargetData_LocationInfo
继承自FGameplayAbilityTargetData
,
保存了发起者和目标的位置信息,用FGameplayAbilityTargetingLocationInfo
保存
1 2 3 4
| UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting) FGameplayAbilityTargetingLocationInfo SourceLocation; UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting) FGameplayAbilityTargetingLocationInfo TargetLocation;
|
FGameplayAbilityTargetData_ActorArray
继承自FGameplayAbilityTargetData
,
保存了发起者和目标数组
1 2 3 4
| UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = Targeting) FGameplayAbilityTargetingLocationInfo SourceLocation; UPROPERTY(EditAnywhere, Category = Targeting) TArray<TWeakObjectPtr<AActor> > TargetActorArray;
|
FGameplayAbilityTargetData_SingleTargetHit
继承自FGameplayAbilityTargetData
,
保存了Hit信息
1 2 3 4
| UPROPERTY() FHitResult HitResult; UPROPERTY() bool bHitReplaced = false;
|
FGameplayAbilityTargetingLocationInfo
用不同的格式保存位置信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| namespace EGameplayAbilityTargetingLocationType { enum Type { LiteralTransform UMETA(DisplayName = "Literal Transform"),
ActorTransform UMETA(DisplayName = "Actor Transform"),
SocketTransform UMETA(DisplayName = "Socket Transform"), }; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting) TEnumAsByte<EGameplayAbilityTargetingLocationType::Type> LocationType;
UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting) FTransform LiteralTransform;
UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting) AActor* SourceActor;
UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting) UMeshComponent* SourceComponent;
UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting) UGameplayAbility* SourceAbility;
UPROPERTY(BlueprintReadWrite, meta = (ExposeOnSpawn = true), Category = Targeting) FName SourceSocketName;
|