跳到主要内容
信息
· 文章中可能会出现一些错误,希望大佬们可以在评论区指出;

06 - GAS中的Gameplay Tags

本文主要说明了UE5中Gameplay技能系统(Gameplay Ability System, GAS)中GameplayTags的相关内容。

概述​

Gameplay Tag 是 UE5 提供的一套用来表达游戏语义的通用标记系统,可用于区分输入事件、能力、属性、伤害类型、Buff/Debuff、消息与数据等多种信息。一个 Gameplay Tag 使用 FGameplayTag 类型存储,其内部核心为 FName 类型的 TagName。所有 Tag 均由 Gameplay Tag Manager 统一注册和管理,并具有层级结构。多个 Tag 可以通过 FGameplayTagContainer 进行组合和查询。

在 GAS 中,主要通过实现了 IGameplayTagAssetInterface 的对象(如 Ability System Component)进行 Tag 的查询与操作。此外,其他 GAS 类型也与 Gameplay Tag 有紧密联系,例如 Gameplay Effect 可以向目标的 ASC 添加或移除 Tag,而 Gameplay Ability 则可以根据目标是否拥有某些 Tag 来决定是否能够被激活或应用。

这里将记载一些我在学习Aura项目中,GAS和Gameplay Tag相结合的一些操作用法。

常用操作​

通过GE给ASC添加Gameplay Tag​

可以通过GE对目标Actor的ASC添加Gameplay Tag。这需要在非立即生效的GE中添加Grant Tags to Target Actor组件,该组件可以为目标Actor的ASC添加或移除Gameplay Tag,需要配置的项目如下:

  • Add to Inherited:要添加的Gameplay Tag;
  • Remove from Inherited:要移除的Gameplay Tag;
  • Combined Tags:Gameplay Tag的最终计算结果(继承的Tag - 移除的Tag + 添加的Tag);

此外,对于支持堆叠的GE,添加的Tag是唯一的;对于不支持堆叠的GE,添加的Tag是可重复的。

Grant Tags to Target Actor组件

和增强输入配合激活GA​

可以通过Gameplay Tag和增强输入配合激活特定GA,Lyra似乎也是这样做的(还没学过,TODO),这里来谈谈我在Aura项目中实现的相关思路(如有更优秀的方法还请大佬们指出):

  1. 新建一个基于UEnhancedInputComponent的子类UAuraInputComponent,在该子类中实现绑定按键操作的相关逻辑:

    AuraInputComponent.h点击展开/折叠代码
    // 存储输入逻辑函数指针的结构体
    template <class UserClass>
    struct FInputActionCallbacks
    {
    // UE5提供了成员函数指针模板: TMemFunPtrType<是否是const函数, 类, 类的成员函数签名>
    using CallbackFuncType = TMemFunPtrType<false, UserClass, void(FGameplayTag)>::Type;

    CallbackFuncType OnPressed = nullptr;
    CallbackFuncType OnReleased = nullptr;
    CallbackFuncType OnHeld = nullptr;
    };


    UCLASS()
    class AURA_API UAuraInputComponent : public UEnhancedInputComponent
    {
    GENERATED_BODY()

    public:
    // 给UserClass类的实例ToBindObject绑定相关输入回调
    template <class UserClass>
    void BindAbilityInputAction(const UAuraInputConfig* InputConfig, UserClass* ToBindObject,
    const FInputActionCallbacks<UserClass>& Callbacks);
    };

    template <class UserClass>
    void UAuraInputComponent::BindAbilityInputAction(const UAuraInputConfig* InputConfig, UserClass* ToBindObject,
    const FInputActionCallbacks<UserClass>& Callbacks)
    {
    // InputConfig是用于确认InputAction和它对应Gameplay Tag的DataAsset类
    checkf(IsValid(InputConfig), TEXT("[%hs]: InputConfig is nullptr, plz check!"), __FUNCTION__);

    for (const FAuraInputAction& IA : InputConfig->AbilityInputActions)
    {
    if (!IsValid(IA.InputAction) || !IA.Tag.IsValid())
    {
    continue;
    }

    if (Callbacks.OnPressed)
    {
    BindAction(IA.InputAction, ETriggerEvent::Started, ToBindObject, Callbacks.OnPressed, IA.Tag);
    }

    if (Callbacks.OnReleased)
    {
    BindAction(IA.InputAction, ETriggerEvent::Completed, ToBindObject, Callbacks.OnReleased, IA.Tag);
    }

    if (Callbacks.OnHeld)
    {
    BindAction(IA.InputAction, ETriggerEvent::Triggered, ToBindObject, Callbacks.OnHeld, IA.Tag);
    }
    }
    }

  2. 在PlayerController类中实现各按键回调和相关绑定逻辑:

    AuraPlayerController.cpp点击展开/折叠代码
    // ...
    void AAuraPlayerController::SetupInputComponent()
    {
    Super::SetupInputComponent();

    // 绑定Input Action
    UAuraInputComponent* AuraInputComponent = CastChecked<UAuraInputComponent>(InputComponent);
    AuraInputComponent->BindAction(IA_MoveAction, ETriggerEvent::Triggered, this, &AAuraPlayerController::Move);
    AuraInputComponent->BindAbilityInputAction(InputConfig, this,
    {
    &ThisClass::AbilityInputTagOnPressed,
    &ThisClass::AbilityInputTagOnReleased,
    &ThisClass::AbilityInputTagOnHeld
    });
    }

    void AAuraPlayerController::AbilityInputTagOnPressed(FGameplayTag InputTag)
    {
    // todo...
    }

    void AAuraPlayerController::AbilityInputTagOnReleased(FGameplayTag InputTag)
    {
    AuraASC->AbilityInputTagOnReleased(InputTag);
    }

    void AAuraPlayerController::AbilityInputTagOnHeld(FGameplayTag InputTag)
    {
    AuraASC->AbilityInputTagOnHeld(InputTag);
    }

    // ...

    GA的激活逻辑和PlayerController解耦,在AbilitySystemComponent中实现:

    AuraAbilitySystemComponent.cpp点击展开/折叠代码
    void UAuraAbilitySystemComponent::AbilityInputTagOnHeld(const FGameplayTag& InputTag)
    {
    if (!InputTag.IsValid())
    {
    return;
    }

    for (TArray<FGameplayAbilitySpec> ActivatableGASpecs = GetActivatableAbilities();
    FGameplayAbilitySpec& GASpec : ActivatableGASpecs)
    {
    // 寻找该按键输入能触发的GA
    if (GASpec.DynamicAbilityTags.HasTagExact(InputTag))
    {
    // 标识该GA的输入触发为: 已按下
    AbilitySpecInputPressed(GASpec);
    // 如果该GA仍未激活, 激活它
    if (!GASpec.IsActive())
    {
    TryActivateAbility(GASpec.Handle);
    }
    }
    }
    }

    void UAuraAbilitySystemComponent::AbilityInputTagOnReleased(const FGameplayTag& InputTag)
    {
    if (!InputTag.IsValid())
    {
    return;
    }

    for (TArray<FGameplayAbilitySpec> ActivatableGASpecs = GetActivatableAbilities();
    FGameplayAbilitySpec& GASpec : ActivatableGASpecs)
    {
    if (GASpec.DynamicAbilityTags.HasTagExact(InputTag))
    {
    AbilitySpecInputReleased(GASpec);
    }
    }
    }
  3. 在编辑器中补全相关逻辑/组件,例如:

    1. 在项目设置里的输入中修改默认增强输入组件为该子类;
    2. 补充相关Input Action,将其映射到Input Mapping Context中;
    3. 为GA设置它的Input Tag等;

上述操作将玩家输入和直接触发GA的操作解耦:对应的输入会触发对应的Gameplay Tag,之后才会根据Gameplay Tag触发对应的GA。这样做的好处就是将输入系统变得 数据驱动,可以通过不改变代码的方式修改触发GA的按键,单Tag对应多设备输入等。

参与游戏逻辑事件机制​

Gameplay Tag还可参与到游戏逻辑的事件机制中,可实现通过某个Gameplay Tag触发一段游戏逻辑的效果。具体步骤如下:

  1. 通过SendGameplayEventToActor函数/蓝图节点发送GameplayTag:

    上图是一个专用于蒙太奇的动画通知,在某一关键帧使用该动画通知,将相关Gameplay Tag通过SendGameplayEventToActor蓝图节点发送到动画所有者Actor中。

  2. 在对应Actor中调用WaitGameplayEvent函数/蓝图节点接受Gameplay Tag:

    在接到步骤1动画通知的Gameplay Tag后,对应Actor将会执行Event Received引脚后续内容。

参考资料​