BlueRoseNote/03-UnrealEngine/Gameplay/GAS/(12)GAS中的传递数据、解耦与其他技巧.md
2023-06-29 11:55:02 +08:00

187 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## GAS中的传递数据、解耦与其他技巧
本文大部分内容来着GASDocumentation与GASDocumentation_Chinese因为感觉很重要所以在此简单归纳一下。
https://github.com/tranek/GASDocumentation
https://github.com/BillEliot/GASDocumentation_Chinese
<!--more-->
## 响应GameplayTags的变化
这个没什么好说的直接上GASDocumentation_Chinese中的解释
>ASC提供了一个委托(Delegate)用于在GameplayTag添加或移除时触发, 其中EGameplayTagEventType参数可以明确是该GameplayTag添加/移除还是其TagMapCount发生变化时触发.
```
AbilitySystemComponent->RegisterGameplayTagEvent(FGameplayTag::RequestGameplayTag(FName("State.Debuff.Stun")), EGameplayTagEventType::NewOrRemoved).AddUObject(this, &AGDPlayerState::StunTagChanged);
```
回调函数拥有变化的GameplayTag参数和新的TagCount参数.
```
virtual void StunTagChanged(const FGameplayTag CallbackTag, int32 NewCount);
```
## 向GameplayAbility传递数据
一共有4中方法
- 通过设置Ability中的Trigger之后再在ActivateAbilityFromEvent事件中进行处理。
- 使用WaitGameplayEvent之类的AbilityTask对接收到的事件进行处理。
- 使用TargetData。
- 存储数据在OwnerActor或者AvatarActor中之后再通过ActorInfo中的Owner或者Avatar来获取数据。
前两种都可以通过往Event中添加Payload来传递额外信息。Payload可以使用自定义的UObject。TargetData涉及到FGameplayAbilityTargetData、AGameplayAbilityTargetActor等东西下一篇文章会说。
## 通过Event/Tag来激活GameplayAbility
Ability Trigger是一种不错的解耦方式,大致的使用步骤:
1. 在Ability中的设置Trigger中的Ability Trigger在Class Defaults中
2. 实现ActivateAbilityFromEvent事件通过Event中Payload获取自定义UObject中的信息。
3. Event的触发方式通过调用SendGameplayEventToActor()向对应拥有ASC的Actor/Character发送GameplayEvent来触发对应Ability。
4. Tag的触发方式通过GamplayEffect来添加对应Tag来触发对应Ability。AddLooseGameplayTag()我没试过,但估计也是可以的)
使用Tag的好处在于Owned Tag Present,即让Ability与Tag保持一样的生命周期,不过很可惜Tag的触发方式并不支持Ability的AbilityTags。注册过程位于ASC的OnGiveAbility()中主要是通过绑定MonitoredTagChanged事件来实现触发。
PS.本人使用这个功能实现了FightingStateHandle即进入战斗从背上拔出武器离开战斗则将武器放回背上。
### GASShooter中的另类用法
这里我还想说一下GASShooter中使用Ability Trigger机制的另类用法Interact项目中宝箱与按钮门都使用了它。
这个系统主要由GA_InteractPassive、GA_InteractActive以及IGSInteractable接口。
#### IGSInteractable
首先所有可以互动的物体都需要继承并且按照对应需求实现对应的几个接口函数:
- IsAvailableForInteraction
- GetInteractionDuration
- GetPreInteractSyncType
- GetPostInteractSyncType
- PreInteract
- PostInteract
- CancelInteraction
- RegisterInteracter
- UnregisterInteracter
- InteractableCancelInteraction
#### GA_InteractPassive与GA_InteractActive
GA_InteractPassive与GA_InteractActive会在游戏开始时在BP_HeroCharacter中注册。
GA_InteractPassive是一个GrantAbility它会在游戏开始就被激活。它会执行UGSAT_WaitInteractableTarget不断地进行射线判断。如果有可用的Interactable目标出现就会进行一些系列的判断在绑定的InputAction按下后最终给角色类ASC发送GameplayTask中设置的GameplayEventPayload中带有Interactable目标的TargetData
因为GA_InteractActive中设置了Ability Trigger所以它会接收到GameplayEvent并且处理。Interactable触发后的逻辑都在这里处理通过TargetData调用IGSInteractable接口函数
## GameplayEffect的GrantAbilities
在GameplayEffect也有类似Ability Trigger就是GrantAbilities,并且有多种移除Ability方式以供选择。但GameplayEffect需要符合一下要求
- StackingType需要为AggregateBySource/AggregateByTarget
- DurationType需要为HasDuration/Infinite
- 设置的Ability必须没有注册过(GiveAbility)
同时GameplayAbility类还需要实现OnAvatarSet()虚函数该函数主要用于处理GrantAbility
```
UCLASS()
class RPGGAMEPLAYABILITY_API URPGGameplayAbility : public UGameplayAbility
{
GENERATED_BODY()
public:
{
virtual void OnAvatarSet(const FGameplayAbilityActorInfo * ActorInfo, const FGameplayAbilitySpec & Spec) override;
protected:
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "RPGGameplayAbility")
bool ActivateAbilityOnGranted;
};
```
```
void URPGGameplayAbility::OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
if (ActivateAbilityOnGranted)
{
ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle, true);
}
}
```
其中ActivateAbilityOnGranted用于判断这个Ability是不是GrantAbility不然所有所有的Ability都会调用这个函数。
## 运行时创建动态GameplayEffect
```
void UGameplayAbilityRuntimeGE::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
if (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo))
{
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
{
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
}
// Create the GE at runtime.
UGameplayEffect* GameplayEffect = NewObject<UGameplayEffect>(GetTransientPackage(), TEXT("RuntimeInstantGE"));
GameplayEffect->DurationPolicy = EGameplayEffectDurationType::Instant; // Only instant works with runtime GE.
const int32 Idx = GameplayEffect->Modifiers.Num();
GameplayEffect->Modifiers.SetNum(Idx + 1);
FGameplayModifierInfo& ModifierInfo = GameplayEffect->Modifiers[Idx];
ModifierInfo.Attribute.SetUProperty(UMyAttributeSet::GetMyModifiedAttribute());
ModifierInfo.ModifierMagnitude = FScalableFloat(42.f);
ModifierInfo.ModifierOp = EGameplayModOp::Override;
// Apply the GE.
FGameplayEffectSpec* GESpec = new FGameplayEffectSpec(GameplayEffect, {}, 0.f); // "new", since lifetime is managed by a shared ptr within the handle
ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, FGameplayEffectSpecHandle(GESpec));
}
EndAbility(Handle, ActorInfo, ActivationInfo, false, false);
}
```
## 延长GE的持续时间
在GASDocument的4.5.16 中介绍了这个方法,直接上代码了:
```
bool UPAAbilitySystemComponent::SetGameplayEffectDurationHandle(FActiveGameplayEffectHandle Handle, float NewDuration)
{
if (!Handle.IsValid())
{
return false;
}
const FActiveGameplayEffect* ActiveGameplayEffect = GetActiveGameplayEffect(Handle);
if (!ActiveGameplayEffect)
{
return false;
}
FActiveGameplayEffect* AGE = const_cast<FActiveGameplayEffect*>(ActiveGameplayEffect);
if (NewDuration > 0)
{
AGE->Spec.Duration = NewDuration;
}
else
{
AGE->Spec.Duration = 0.01f;
}
AGE->StartServerWorldTime = ActiveGameplayEffects.GetServerWorldTime();
AGE->CachedStartServerWorldTime = AGE->StartServerWorldTime;
AGE->StartWorldTime = ActiveGameplayEffects.GetWorldTime();
ActiveGameplayEffects.MarkItemDirty(*AGE);
ActiveGameplayEffects.CheckDuration(Handle);
AGE->EventSet.OnTimeChanged.Broadcast(AGE->Handle, AGE->StartWorldTime, AGE->GetDuration());
OnGameplayEffectDurationChange(*AGE);
return true;
}
```
PS.本人使用使用这个技巧来刷新战斗状态的持续时间。
## 其他技巧
### 添加给ACS附加标签不通过GE与GA
这个方法不适合联机,只在本地起效果。可以作为一些用于本地效果控制。
```
FGameplayTag DeadTag = FGameplayTag::RequestGameplayTag(FName("Status.Dead"));
AbilitySystemComponent->AddLooseGameplayTag(DeadTag);
```
### 动态修改Ability等级
- 移除旧Ability之后再添加新Ability
- 增加GameplayAbilitySpec的等级在服务端上找到GameplayAbilitySpec, 增加它的等级, 并将其标记为Dirty以同步到所属(Owning)客户端。
### AbilityInputID绑定
GASDocument的项目定义了EGSAbilityInputID枚举用于定义所有Ability的输入绑定名字需要与InputAction一一对应并且在UGSGameplayAbility中设置AbilityInputID变量用于输入绑定。
```
for (TSubclassOf<UGSGameplayAbility>& StartupAbility : CharacterAbilities)
{
AbilitySystemComponent->GiveAbility(
FGameplayAbilitySpec(StartupAbility, GetAbilityLevel(StartupAbility.GetDefaultObject()->AbilityID), static_cast<int32>(StartupAbility.GetDefaultObject()->AbilityInputID), this));
}
```
这里还通过AbilityLevel来传递AbilityID。
PS.但本人感觉这个方法在一些动作游戏中这个方法可能会不太合适。