前言
这一篇单独创建几个非常有用的异步节点,如UAsyncTask_ListenAttributeChanged
可以实时监听任意属性的更改情况,在初期我们用于临时UMG中,方便我们查询和debug;
可以使用继承自UBlueprintAsyncActionBase
的基本异步类, 也可以使用GAS
框架内的UAbilityTask
来制作
异步事件:属性监听
这个节点相对比较简单, 原理就是通过ASC
中的GetGameplayAttributeValueChangeDelegate()
绑定对应属性的代理,然后简单的派发异步节点中的代理, 这是GASD
项目采用的方案,
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
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnAttributeChanged, FGameplayAttribute, Attribute, float, NewValue, float, OldValue);
UCLASS() class SUPERROAD_API UAsyncTask_ListenAttributeChanged : public UBlueprintAsyncActionBase { GENERATED_BODY() public: UPROPERTY(BlueprintAssignable) FOnAttributeChanged OnAttributeChanged;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) static UAsyncTask_ListenAttributeChanged* ListenForAttributeChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayAttribute Attribute);
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) static UAsyncTask_ListenAttributeChanged* ListenForAttributesChange(UAbilitySystemComponent* AbilitySystemComponent, TArray<FGameplayAttribute> Attributes);
UFUNCTION(BlueprintCallable) void EndTask();
protected: UPROPERTY() UAbilitySystemComponent* ASC;
FGameplayAttribute AttributeToListenFor; TArray<FGameplayAttribute> AttributesToListenFor;
void AttributeChanged(const FOnAttributeChangeData& Data); };
|
没什么特殊需要注意的, 提供了2个版本的监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| UAsyncTask_ListenAttributeChanged* UAsyncTask_ListenAttributeChanged::ListenForAttributeChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayAttribute Attribute) { UAsyncTask_ListenAttributeChanged* WaitForAttributeChangedTask = NewObject<UAsyncTask_ListenAttributeChanged>(); WaitForAttributeChangedTask->ASC = AbilitySystemComponent; WaitForAttributeChangedTask->AttributeToListenFor = Attribute;
if (!IsValid(AbilitySystemComponent) || !Attribute.IsValid()) { WaitForAttributeChangedTask->RemoveFromRoot(); return nullptr; }
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(WaitForAttributeChangedTask, &UAsyncTask_ListenAttributeChanged::AttributeChanged);
return WaitForAttributeChangedTask; }
|
构造一个异步节点类, 赋值,绑定ASC
事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| UAsyncTask_ListenAttributeChanged* UAsyncTask_ListenAttributeChanged::ListenForAttributesChange(UAbilitySystemComponent* AbilitySystemComponent, TArray<FGameplayAttribute> Attributes) { UAsyncTask_ListenAttributeChanged* WaitForAttributeChangedTask = NewObject<UAsyncTask_ListenAttributeChanged>(); WaitForAttributeChangedTask->ASC = AbilitySystemComponent; WaitForAttributeChangedTask->AttributesToListenFor = Attributes;
if (!IsValid(AbilitySystemComponent) || Attributes.Num() < 1) { WaitForAttributeChangedTask->RemoveFromRoot(); return nullptr; }
for (FGameplayAttribute Attribute : Attributes) { AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(WaitForAttributeChangedTask, &UAsyncTask_ListenAttributeChanged::AttributeChanged); }
return WaitForAttributeChangedTask; }
|
数组版本,区别不大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void UAsyncTask_ListenAttributeChanged::EndTask() { if (IsValid(ASC)) { ASC->GetGameplayAttributeValueChangeDelegate(AttributeToListenFor).RemoveAll(this);
for (FGameplayAttribute Attribute : AttributesToListenFor) { ASC->GetGameplayAttributeValueChangeDelegate(Attribute).RemoveAll(this); } }
SetReadyToDestroy(); MarkPendingKill(); }
|
EndTask
事件不能忘记执行,否则可能会导致引擎崩溃
在这里就是解除绑定
1 2 3 4
| void UAsyncTask_ListenAttributeChanged::AttributeChanged(const FOnAttributeChangeData& Data) { OnAttributeChanged.Broadcast(Data.Attribute, Data.NewValue, Data.OldValue); }
|
转发,派发代理
这里创建了一个UMG控件通过公开的属性变量来监听特定的属性
然后就放到一个DebugUI
中,监听所有属性
这是运行以后的
如果你想要自己定义一套属性响应机制也可以通过比如在AttributeSet
内广播代理事件来达到同样的目的, 我们模仿ASC
的那一套来实现一下
1 2 3 4 5 6
| DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnAttributeChangedMulDly, const FGameplayAttribute&,float,float); public: FOnAttributeChangedMulDly& GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute); protected: void BroadcastValueChanged(FGameplayAttribute Attribute); TMap<FGameplayAttribute, FOnAttributeChangedMulDly> AttributeValueChangeDelegates;
|
然后异步事件节点通过类似的方式绑定就可以了, 我们这里输出的是Current
和Base
值, 与前面方案有所不同
异步事件:冷却
冷却时间比前面的属性略微复杂一点点, 监听了两个时间,开始和结束,并没有中间过程, 查看ASC
以及GE
和GA
的代码发现并无相关的事件代理, 但是可以有方法找到当前技能或者GE
的RemainingTime
和Duration
.
无妨, 这个或许是因为消耗问题, 没有必要一直派发代理来告诉我们CD刷新了, 我们做测试或者到时候UI
显示的转圈圈效果也只需要做个本地的效果就可以了
1 2 3 4 5 6
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnCooldownChanged, FGameplayTag, CooldownTag, float, TimeRemaining, float, Duration);
UPROPERTY(BlueprintAssignable) FOnCooldownChanged OnCooldownBegin; UPROPERTY(BlueprintAssignable) FOnCooldownChanged OnCooldownEnd;
|
类似的创建静态事件返回自己
1 2
| UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) static UAsyncTask_ListenCooldownUpdated* ListenForCooldownChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayTagContainer InCooldownTags, bool InUseServerCooldown);
|
几个Protected
1 2 3 4 5 6 7 8
| protected: UPROPERTY() UAbilitySystemComponent* ASC; FGameplayTagContainer CooldownTags; bool UseServerCooldown; virtual void OnActiveGameplayEffectAddedCallback(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle); virtual void CooldownTagChanged(const FGameplayTag CooldownTag, int32 NewCount); bool GetCooldownRemainingForTag(FGameplayTagContainer InCooldownTags, float& TimeRemaining, float& CooldownDuration)
|
先看激活GE
的回调事件
先从Spec
中拿到需要的tag
,查询我们指定的冷却tag
是否包含在里面
如果存在, 那么拿到所需要的时间根据相关设置广播
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 40 41 42 43 44 45
| void UAsyncTask_ListenCooldownUpdated::OnActiveGameplayEffectAddedCallback(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle) { FGameplayTagContainer AssetTags; SpecApplied.GetAllAssetTags(AssetTags);
FGameplayTagContainer GrantedTags; SpecApplied.GetAllGrantedTags(GrantedTags);
TArray<FGameplayTag> CooldownTagArray; CooldownTags.GetGameplayTagArray(CooldownTagArray);
for (FGameplayTag CooldownTag : CooldownTagArray) { if (AssetTags.HasTagExact(CooldownTag) || GrantedTags.HasTagExact(CooldownTag)) { float TimeRemaining = 0.0f; float Duration = 0.0f; FGameplayTagContainer CooldownTagContainer(GrantedTags.GetByIndex(0)); GetCooldownRemainingForTag(CooldownTagContainer, TimeRemaining, Duration);
if (ASC->GetOwnerRole() == ROLE_Authority) { OnCooldownBegin.Broadcast(CooldownTag, TimeRemaining, Duration); } else if (!UseServerCooldown && SpecApplied.GetContext().GetAbilityInstance_NotReplicated()) { OnCooldownBegin.Broadcast(CooldownTag, TimeRemaining, Duration); } else if (UseServerCooldown && SpecApplied.GetContext().GetAbilityInstance_NotReplicated() == nullptr) { OnCooldownBegin.Broadcast(CooldownTag, TimeRemaining, Duration); } else if (UseServerCooldown && SpecApplied.GetContext().GetAbilityInstance_NotReplicated()) { OnCooldownBegin.Broadcast(CooldownTag, -1.0f, -1.0f); } } } }
|
通过tag
找到所需的时间, 这两个时间是保存在GE
中的, 也可以通过ASC
去查询得到.
下面方法是从数组中找到值最大的作为返回值
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
| bool UAsyncTask_ListenCooldownUpdated::GetCooldownRemainingForTag(FGameplayTagContainer InCooldownTags, float& TimeRemaining, float& CooldownDuration) { if (IsValid(ASC) && InCooldownTags.Num() > 0) { TimeRemaining = 0.f; CooldownDuration = 0.f;
FGameplayEffectQuery const Query = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(InCooldownTags); TArray< TPair<float, float> > DurationAndTimeRemaining = ASC->GetActiveEffectsTimeRemainingAndDuration(Query); if (DurationAndTimeRemaining.Num() > 0) { int32 BestIdx = 0; float LongestTime = DurationAndTimeRemaining[0].Key; for (int32 Idx = 1; Idx < DurationAndTimeRemaining.Num(); ++Idx) { if (DurationAndTimeRemaining[Idx].Key > LongestTime) { LongestTime = DurationAndTimeRemaining[Idx].Key; BestIdx = Idx; } }
TimeRemaining = DurationAndTimeRemaining[BestIdx].Key; CooldownDuration = DurationAndTimeRemaining[BestIdx].Value;
return true; } }
return false; }
|
下面再看静态方法, 这个也没什么大的难点, 绑定了GE
激活的代理, 然后根据tag
注册tag
对应的事件
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
| UAsyncTask_ListenCooldownUpdated* UAsyncTask_ListenCooldownUpdated::ListenForCooldownChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayTagContainer InCooldownTags, bool InUseServerCooldown) { UAsyncTask_ListenCooldownUpdated* task = NewObject<UAsyncTask_ListenCooldownUpdated>(); task->ASC = AbilitySystemComponent; task->CooldownTags = InCooldownTags; task->UseServerCooldown = InUseServerCooldown;
if (!IsValid(AbilitySystemComponent) || InCooldownTags.Num() < 1) { task->EndTask(); return nullptr; }
AbilitySystemComponent->OnActiveGameplayEffectAddedDelegateToSelf.AddUObject(task, &UAsyncTask_ListenCooldownUpdated::OnActiveGameplayEffectAddedCallback);
TArray<FGameplayTag> CooldownTagArray; InCooldownTags.GetGameplayTagArray(CooldownTagArray);
for (FGameplayTag CooldownTag : CooldownTagArray) { AbilitySystemComponent->RegisterGameplayTagEvent(CooldownTag, EGameplayTagEventType::NewOrRemoved).AddUObject(task, &UAsyncTask_ListenCooldownUpdated::CooldownTagChanged); }
return task; }
|
结束事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void UAsyncTask_ListenCooldownUpdated::EndTask() { if (IsValid(ASC)) { ASC->OnActiveGameplayEffectAddedDelegateToSelf.RemoveAll(this);
TArray<FGameplayTag> CooldownTagArray; CooldownTags.GetGameplayTagArray(CooldownTagArray);
for (FGameplayTag CooldownTag : CooldownTagArray) { ASC->RegisterGameplayTagEvent(CooldownTag, EGameplayTagEventType::NewOrRemoved).RemoveAll(this); } }
SetReadyToDestroy(); MarkPendingKill(); }
|
最后是tag
改变的事件
1 2 3 4 5 6 7
| void UAsyncTask_ListenCooldownUpdated::CooldownTagChanged(const FGameplayTag CooldownTag, int32 NewCount) { if (NewCount == 0) { OnCooldownEnd.Broadcast(CooldownTag, -1.0f, -1.0f); } }
|
异步事件:GE层数
这个节点用于监听可以叠加的GE
的层数
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
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnGameplayEffectStackChanged, FGameplayTag, EffectGameplayTag, FActiveGameplayEffectHandle, Handle, int32, NewStackCount, int32, OldStackCount);
UCLASS() class SUPERROAD_API UAsyncTask_ListenGEStackChanged : public UBlueprintAsyncActionBase { GENERATED_BODY()
UPROPERTY(BlueprintAssignable) FOnGameplayEffectStackChanged OnGameplayEffectStackChange;
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) static UAsyncTask_ListenGEStackChanged* ListenForGameplayEffectStackChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayTag InEffectGameplayTag);
UFUNCTION(BlueprintCallable) void EndTask();
protected: UPROPERTY() UAbilitySystemComponent* ASC;
FGameplayTag EffectGameplayTag;
virtual void OnActiveGameplayEffectAddedCallback(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle); virtual void OnRemoveGameplayEffectCallback(const FActiveGameplayEffect& EffectRemoved);
virtual void GameplayEffectStackChanged(FActiveGameplayEffectHandle EffectHandle, int32 NewStackCount, int32 PreviousStackCount); };
|
头文件跟之前的区别不大, 三个本地事件分别用于绑定 激活/移除GE
和层数变化
静态函数绑定了2个代理
OnActiveGameplayEffectAddedDelegateToSelf
和OnAnyGameplayEffectRemovedDelegate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| UAsyncTask_ListenGEStackChanged* UAsyncTask_ListenGEStackChanged::ListenForGameplayEffectStackChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayTag InEffectGameplayTag) {
UAsyncTask_ListenGEStackChanged* task = NewObject<UAsyncTask_ListenGEStackChanged>(); task->ASC = AbilitySystemComponent; task->EffectGameplayTag = InEffectGameplayTag;
if (!IsValid(AbilitySystemComponent) || !InEffectGameplayTag.IsValid()) { task->EndTask(); return nullptr; }
AbilitySystemComponent->OnActiveGameplayEffectAddedDelegateToSelf.AddUObject(task, &UAsyncTask_ListenGEStackChanged::OnActiveGameplayEffectAddedCallback); AbilitySystemComponent->OnAnyGameplayEffectRemovedDelegate().AddUObject(task, &UAsyncTask_ListenGEStackChanged::OnRemoveGameplayEffectCallback);
return task; }
|
然后结束任务的时候接触绑定
1 2 3 4 5 6 7 8 9 10 11
| void UAsyncTask_ListenGEStackChanged::EndTask() { if (IsValid(ASC)) { ASC->OnActiveGameplayEffectAddedDelegateToSelf.RemoveAll(this); ASC->OnAnyGameplayEffectRemovedDelegate().RemoveAll(this); }
SetReadyToDestroy(); MarkPendingKill(); }
|
激活GE
的时候做了一个tag
判断,如果复合就绑定OnGameplayEffectStackChangeDelegate
并且广播代理
同理在移除的时候也广播
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
| void UAsyncTask_ListenGEStackChanged::OnActiveGameplayEffectAddedCallback(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle) { FGameplayTagContainer AssetTags; SpecApplied.GetAllAssetTags(AssetTags);
FGameplayTagContainer GrantedTags; SpecApplied.GetAllGrantedTags(GrantedTags);
if (AssetTags.HasTagExact(EffectGameplayTag) || GrantedTags.HasTagExact(EffectGameplayTag)) { ASC->OnGameplayEffectStackChangeDelegate(ActiveHandle)->AddUObject(this, &UAsyncTask_ListenGEStackChanged::GameplayEffectStackChanged); OnGameplayEffectStackChange.Broadcast(EffectGameplayTag, ActiveHandle, 1, 0); } }
void UAsyncTask_ListenGEStackChanged::OnRemoveGameplayEffectCallback(const FActiveGameplayEffect& EffectRemoved) { FGameplayTagContainer AssetTags; EffectRemoved.Spec.GetAllAssetTags(AssetTags);
FGameplayTagContainer GrantedTags; EffectRemoved.Spec.GetAllGrantedTags(GrantedTags);
if (AssetTags.HasTagExact(EffectGameplayTag) || GrantedTags.HasTagExact(EffectGameplayTag)) { OnGameplayEffectStackChange.Broadcast(EffectGameplayTag, EffectRemoved.Handle, 0, 1); } }
void UAsyncTask_ListenGEStackChanged::GameplayEffectStackChanged(FActiveGameplayEffectHandle EffectHandle, int32 NewStackCount, int32 PreviousStackCount) { OnGameplayEffectStackChange.Broadcast(EffectGameplayTag, EffectHandle, NewStackCount, PreviousStackCount); }
|