GAS 系统深度学习指南UE 5.4 / 5.5

07 AbilityTasks 异步任务

目标:理解为什么 GAS 需要自己的异步任务系统,掌握内置 Task 的使用、自定义 Task 的完整创建流程(静态工厂/输出委托/Activate/OnDestroy/回调)、bSimulatedTask/bTickingTask 的含义、Root Motion Source 任务、Tick Task 使用场景,以及网络感知 Task 的权限分配。


7.1 为什么需要 AbilityTask

GA 本身只执行一帧。 这是理解 AbilityTask 存在意义的关键。

想象"蓄力射击":按下按钮开始蓄力动画,蓄力 2 秒后松开发射。如果只靠 GA,你无法"等待"2 秒——GA 在一帧内就执行完了。

AbilityTask(AT) 就是为此设计的延迟动作(Latent Action):能在多帧中持续运行,等待外部事件或条件满足后再继续。

类比:如果把 GA 比作一个函数调用,AbilityTask 就像 async/await:

普通函数(GA):开始 → 执行一帧逻辑 → 结束
异步函数(GA+AT):开始 → 创建异步任务 → 等待... → 任务完成/回调 → 继续 → 结束

底层由 GameplayTasks 模块的 UGameplayTasksComponent 驱动。Task 挂在 GA 上,随 GA 生命周期运行;可在特定权限(服务端/客户端)运行;能安全处理"技能被打断 → 所有子任务自动清理"。

关键结论:使用 AbilityTask 的技能,实例化策略必须为 InstancedPerExecution 或 InstancedPerActor(NonInstanced 不能使用 Task)。

7.2 内置 AbilityTask 列表

类别 AbilityTask 用途
移动 AbilityTask_MoveToLocation Root Motion Source 移动到目标位置
移动 AbilityTask_RotateToTarget 旋转朝向目标
动画 PlayMontageAndWait 播放蒙太奇并等待完成
属性监听 WaitAttributeChange / WaitAttributeChangeThreshold 等待属性变化/超阈值
GE 监听 WaitGameplayEffectApplied 等待 GE 被应用
输入响应 WaitInputPress / WaitInputRelease 等待输入按下/释放
事件 WaitGameplayEvent 等待 GameplayEvent
目标选择 WaitTargetData 等待玩家选择目标
延迟 WaitDelay 等待指定时间
碰撞 WaitGameplayOverlap 等待碰撞重叠
确认/取消 WaitConfirmCancel 等待确认或取消输入
网络同步 WaitNetSync 等待客户端/服务器同步
生成 SpawnActor 生成指定 Actor(投射物)

全局最大并发数

关键:UAbilityTask 构造函数强制执行一个硬编码的全局最大 1000 个并发 AbilityTask。设计大量角色的游戏(RTS)时注意此限制。

7.3 标准用法模板(每步都重要)

void UGA_Fireball::ActivateAbility(...)
{
    // 1. 创建 Task(静态工厂函数)
    UAbilityTask_WaitDelay* WaitTask = UAbilityTask_WaitDelay::CreateWaitDelay(this, CastTime);

    // 2. 绑定完成委托
    WaitTask->OnFinish.AddDynamic(this, &UGA_Fireball::OnCastFinished);

    // 3. 关键:ReadyForActivation() 真正启动 Task
    WaitTask->ReadyForActivation();
}

最常见 Bug:忘记调 ReadyForActivation(),Task 永远不会开始。

Blueprint 中:直接使用对应节点,不需要手动调用 ReadyForActivation()——它由 K2Node_LatentGameplayTaskCall(GameplayTasksEditor)自动调用(还会自动调 BeginSpawningActor/FinishSpawningActor)。

关键:这些自动魔法只在 Blueprint 中有效。在 C++ 中必须手动调用 ReadyForActivation()、BeginSpawningActor()、FinishSpawningActor()。

手动取消:EndTask()(C++)或 Async Task Proxy 的 EndTask 节点(BP)。

7.4 完整示例:近战连击(Montage + 等待输入)

UCLASS()
class UGA_ComboAttack : public UGameplayAbility
{
    GENERATED_BODY()
public:
    virtual void ActivateAbility(...) override;

    UPROPERTY(EditDefaultsOnly) TArray<TObjectPtr<UAnimMontage>> ComboMontages;

    UFUNCTION() void OnMontageCompleted();
    UFUNCTION() void OnMontageInterrupted();
    UFUNCTION() void OnInputPressed();
};

void UGA_ComboAttack::ActivateAbility(...)
{
    if (!CommitAbility(...)) { EndAbility(...); return; }

    const int32 ComboIndex = FMath::Min(CurrentComboIndex, ComboMontages.Num() - 1);
    UAbilityTask_PlayMontageAndWait* MontageTask =
        UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(
            this, NAME_None, ComboMontages[ComboIndex]);
    MontageTask->OnCompleted.AddDynamic(this, &UGA_ComboAttack::OnMontageCompleted);
    MontageTask->OnInterrupted.AddDynamic(this, &UGA_ComboAttack::OnMontageInterrupted);
    MontageTask->ReadyForActivation();
}

void UGA_ComboAttack::OnMontageCompleted()
{
    // 等待"攻击输入"进入下一段连招(窗口期)
    UAbilityTask_WaitInputPress* InputTask =
        UAbilityTask_WaitInputPress::CreateWaitInputPress(this, true);
    InputTask->OnPress.AddDynamic(this, &UGA_ComboAttack::OnInputPressed);
    InputTask->ReadyForActivation();
}

7.5 自定义 AbilityTask 的完整组成

┌─────────────────────────────────────────────────┐
│              Custom AbilityTask                  │
├─────────────────────────────────────────────────┤
│ 1. 静态创建函数(BlueprintCallable 工厂)         │
│ 2. 输出委托(BlueprintAssignable)                │
│ 3. Activate() —— 开始主要工作、绑定外部委托       │
│ 4. OnDestroy() —— 清理工作、解绑外部委托          │
│ 5. 回调函数 —— 处理绑定的外部委托                 │
│ 6. 成员变量和内部辅助函数                         │
└─────────────────────────────────────────────────┘

输出委托限制

常见陷阱:AbilityTask 只能声明一种类型的输出委托。所有输出委托必须是同一类型,不用的参数传默认值。

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
    FMyTaskDelegate, FGameplayTag, EventTag, FGameplayEventData, EventData);

UPROPERTY(BlueprintAssignable) FMyTaskDelegate OnSuccess;
UPROPERTY(BlueprintAssignable) FMyTaskDelegate OnFailure;
// 即使 OnFailure 不需要 EventData,也必须传默认值

bSimulatedTask(模拟客户端支持)

默认 Task 只在运行拥有 GA 的客户端或服务器上执行。要在模拟代理上运行需启用:

UMyAbilityTask::UMyAbilityTask(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    bSimulatedTask = true;
}
// 必须重写:
virtual void InitSimulatedTask(UGameplayTasksComponent& InGameplayTasksComponent) override
{
    Super::InitSimulatedTask(InGameplayTasksComponent);
    // 初始化模拟所需成员变量
}
UPROPERTY(Replicated) float SimulatedValue;   // 成员变量需要复制

使用场景:移动相关 Task(AbilityTask_MoveToLocation);不想复制每一个移动变化而是模拟整个任务。所有 Root Motion Source Task 都这样。

bTickingTask(帧更新支持)

UMyAbilityTask::UMyAbilityTask(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
    bTickingTask = true;
}

virtual void TickTask(float DeltaTime) override
{
    Super::TickTask(DeltaTime);
    CurrentAlpha = FMath::FInterpTo(CurrentAlpha, TargetAlpha, DeltaTime, InterpSpeed);
    if (FMath::IsNearlyEqual(CurrentAlpha, TargetAlpha))
    {
        if (ShouldBroadcastAbilityTaskDelegates())
            OnTargetReached.Broadcast(FGameplayTag(), FGameplayEventData());
        EndTask();
    }
}

7.6 实战级自定义 Task 示例:PlayMontageAndWaitForEvent

这是 GASDocumentation 中最经典的自定义 Task——把 PlayMontageAndWait 和 WaitGameplayEvent 合二为一,让蒙太奇通过 AnimNotify 发送 GameplayEvent 回到 GA。

头文件

// GDAT_PlayMontageAndWaitForEvent.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
    FGDPlayMontageAndWaitForEventDelegate,
    FGameplayTag, EventTag, FGameplayEventData, EventData);

UCLASS()
class UGDAT_PlayMontageAndWaitForEvent : public UAbilityTask
{
    GENERATED_BODY()
public:
    UGDAT_PlayMontageAndWaitForEvent(const FObjectInitializer& ObjectInitializer);

    virtual void Activate() override;
    virtual void ExternalCancel() override;
    virtual FString GetDebugString() const override;
    virtual void OnDestroy(bool AbilityEnded) override;

    // ---- 输出委托(同一类型) ----
    UPROPERTY(BlueprintAssignable) FGDPlayMontageAndWaitForEventDelegate OnCompleted;
    UPROPERTY(BlueprintAssignable) FGDPlayMontageAndWaitForEventDelegate OnBlendOut;
    UPROPERTY(BlueprintAssignable) FGDPlayMontageAndWaitForEventDelegate OnInterrupted;
    UPROPERTY(BlueprintAssignable) FGDPlayMontageAndWaitForEventDelegate OnCancelled;
    UPROPERTY(BlueprintAssignable) FGDPlayMontageAndWaitForEventDelegate EventReceived;

    // ---- 静态创建函数 ----
    UFUNCTION(BlueprintCallable, Category = "Ability|Tasks",
        meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility",
                BlueprintInternalUseOnly = "TRUE"))
    static UGDAT_PlayMontageAndWaitForEvent* PlayMontageAndWaitForEvent(
        UGameplayAbility* OwningAbility,
        FName TaskInstanceName,
        UAnimMontage* MontageToPlay,
        FGameplayTagContainer EventTags,
        float Rate = 1.f,
        FName StartSection = NAME_None,
        bool bStopWhenAbilityEnds = true,
        float AnimRootMotionTranslationScale = 1.f);

private:
    UPROPERTY() UAnimMontage* MontageToPlay;
    UPROPERTY() FGameplayTagContainer EventTags;
    UPROPERTY() float Rate;
    UPROPERTY() FName StartSection;
    UPROPERTY() float AnimRootMotionTranslationScale;
    UPROPERTY() bool bStopWhenAbilityEnds;

    bool StopPlayingMontage();
    void OnMontageBlendingOut(UAnimMontage* Montage, bool bInterrupted);
    void OnAbilityCancelled();
    void OnMontageEnded(UAnimMontage* Montage, bool bInterrupted);
    void OnGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload);

    FOnMontageBlendingOutStarted BlendingOutDelegate;
    FOnMontageEnded MontageEndedDelegate;
    FDelegateHandle CancelledHandle;
    FDelegateHandle EventHandle;
};

实现文件(关键模式)

// 静态创建函数:用 NewAbilityTask 创建并赋值
UGDAT_PlayMontageAndWaitForEvent* UGDAT_PlayMontageAndWaitForEvent::PlayMontageAndWaitForEvent(
    UGameplayAbility* OwningAbility, FName TaskInstanceName, UAnimMontage* InMontageToPlay,
    FGameplayTagContainer InEventTags, float InRate, FName InStartSection,
    bool InbStopWhenAbilityEnds, float InAnimRootMotionTranslationScale)
{
    UAbilitySystemGlobals::NonShipping_ApplyGlobalAbilityScaler_Rate(InRate);
    UGDAT_PlayMontageAndWaitForEvent* MyObj =
        NewAbilityTask<UGDAT_PlayMontageAndWaitForEvent>(OwningAbility, TaskInstanceName);
    MyObj->MontageToPlay = InMontageToPlay;
    MyObj->EventTags = InEventTags;
    MyObj->Rate = InRate;
    MyObj->StartSection = InStartSection;
    MyObj->AnimRootMotionTranslationScale = InAnimRootMotionTranslationScale;
    MyObj->bStopWhenAbilityEnds = InbStopWhenAbilityEnds;
    return MyObj;
}

// Activate:绑定事件 → 播放蒙太奇 → 绑定取消/混合输出/结束回调
void UGDAT_PlayMontageAndWaitForEvent::Activate()
{
    if (Ability == nullptr) return;
    bool bPlayedMontage = false;
    if (AbilitySystemComponent.IsValid())
    {
        UAnimInstance* AnimInstance = Ability->GetCurrentActorInfo()->GetAnimInstance();
        if (AnimInstance != nullptr)
        {
            EventHandle = AbilitySystemComponent->AddGameplayEventTagContainerDelegate(
                EventTags, FGameplayEventTagMulticastDelegate::FDelegate::CreateUObject(
                    this, &UGDAT_PlayMontageAndWaitForEvent::OnGameplayEvent));

            if (AbilitySystemComponent->PlayMontage(
                    Ability, Ability->GetCurrentActivationInfo(), MontageToPlay, Rate, StartSection) > 0.f)
            {
                if (ShouldBroadcastAbilityTaskDelegates() == false) return;

                CancelledHandle = Ability->OnGameplayAbilityCancelled.AddUObject(
                    this, &UGDAT_PlayMontageAndWaitForEvent::OnAbilityCancelled);
                BlendingOutDelegate.BindUObject(this, &UGDAT_PlayMontageAndWaitForEvent::OnMontageBlendingOut);
                AnimInstance->Montage_SetBlendingOutDelegate(BlendingOutDelegate, MontageToPlay);
                MontageEndedDelegate.BindUObject(this, &UGDAT_PlayMontageAndWaitForEvent::OnMontageEnded);
                AnimInstance->Montage_SetEndDelegate(MontageEndedDelegate, MontageToPlay);

                ACharacter* Character = Cast<ACharacter>(GetAvatarActor());
                if (Character && (Character->GetLocalRole() == ROLE_Authority ||
                    (Character->GetLocalRole() == ROLE_AutonomousProxy &&
                     Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted)))
                {
                    Character->SetAnimRootMotionTranslationScale(AnimRootMotionTranslationScale);
                }
                bPlayedMontage = true;
            }
        }
    }
    if (!bPlayedMontage)
    {
        if (ShouldBroadcastAbilityTaskDelegates())
            OnCancelled.Broadcast(FGameplayTag(), FGameplayEventData());
    }
    SetWaitingOnAvatar();
}

// 回调:BlendingOut → 广播 OnBlendOut / OnInterrupted
// OnMontageEnded(非打断)→ OnCompleted.Broadcast + EndTask()
// OnGameplayEvent → EventReceived.Broadcast(复制 Payload 并更新 EventTag)

// OnDestroy:解绑取消委托;AbilityEnded 且 bStopWhenAbilityEnds 时停蒙太奇;
//            移除事件容器委托 → Super::OnDestroy
void UGDAT_PlayMontageAndWaitForEvent::OnDestroy(bool AbilityEnded)
{
    if (Ability)
    {
        Ability->OnGameplayAbilityCancelled.Remove(CancelledHandle);
        if (AbilityEnded && bStopWhenAbilityEnds) StopPlayingMontage();
    }
    if (AbilitySystemComponent.IsValid())
        AbilitySystemComponent->RemoveGameplayEventTagContainerDelegate(EventTags, EventHandle);
    Super::OnDestroy(AbilityEnded);
}

模式总结:Activate() 绑定外部委托 + 启动工作;OnDestroy() 解绑所有委托 + 清理。绑定/解绑对称是自定义 Task 的资源管理铁律。

使用(在 GA 中)

UGDAT_PlayMontageAndWaitForEvent* Task =
    UGDAT_PlayMontageAndWaitForEvent::PlayMontageAndWaitForEvent(
        this, NAME_None, MontageToPlay, FGameplayTagContainer(), 1.0f, NAME_None, false, 1.0f);
Task->OnBlendOut.AddDynamic(this, &UGDGA_FireGun::OnCompleted);
Task->OnCompleted.AddDynamic(this, &UGDGA_FireGun::OnCompleted);
Task->OnInterrupted.AddDynamic(this, &UGDGA_FireGun::OnCancelled);
Task->OnCancelled.AddDynamic(this, &UGDGA_FireGun::OnCancelled);
Task->EventReceived.AddDynamic(this, &UGDGA_FireGun::EventReceived);
Task->ReadyForActivation();

7.7 另一个示例:WaitReceiveDamage(监听 ASC 自定义委托)

演示如何监听 ASC 上的自定义委托(伤害事件),Activate 绑定 / OnDestroy 解绑:

DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(
    FWaitReceiveDamageDelegate,
    class UAbilitySystemComponent*, SourceASC,
    float, UnmitigatedDamage,
    float, MitigatedDamage);

UCLASS()
class UGDAT_WaitReceiveDamage : public UAbilityTask
{
    GENERATED_BODY()
public:
    UPROPERTY(BlueprintAssignable) FWaitReceiveDamageDelegate OnDamage;

    UFUNCTION(BlueprintCallable, Category = "Ability|Tasks",
        meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility",
                BlueprintInternalUseOnly = "TRUE"))
    static UGDAT_WaitReceiveDamage* WaitReceiveDamage(UGameplayAbility* OwningAbility, bool TriggerOnce);

    virtual void Activate() override;
    virtual void OnDestroy(bool AbilityIsEnding) override;

    UFUNCTION() void OnDamageReceived(class UAbilitySystemComponent* SourceASC,
                                      float UnmitigatedDamage, float MitigatedDamage);
protected:
    bool TriggerOnce;
};

void UGDAT_WaitReceiveDamage::Activate()
{
    if (UMyAbilitySystemComponent* ASC = Cast<UMyAbilitySystemComponent>(AbilitySystemComponent))
        ASC->ReceivedDamage.AddDynamic(this, &UGDAT_WaitReceiveDamage::OnDamageReceived);
}

void UGDAT_WaitReceiveDamage::OnDamageReceived(
    UAbilitySystemComponent* SourceASC, float UnmitigatedDamage, float MitigatedDamage)
{
    if (ShouldBroadcastAbilityTaskDelegates())
        OnDamage.Broadcast(SourceASC, UnmitigatedDamage, MitigatedDamage);
    if (TriggerOnce) EndTask();   // 只触发一次则结束
}

void UGDAT_WaitReceiveDamage::OnDestroy(bool AbilityIsEnding)
{
    if (UMyAbilitySystemComponent* ASC = Cast<UMyAbilitySystemComponent>(AbilitySystemComponent))
        ASC->ReceivedDamage.RemoveDynamic(this, &UGDAT_WaitReceiveDamage::OnDamageReceived);
    Super::OnDestroy(AbilityIsEnding);
}

7.8 Root Motion Source AbilityTasks

GAS 提供钩入 CharacterMovementComponent 的 Root Motion Source Task,实现各种移动效果:

效果 说明
击退(Knockback) 被攻击后向后飞退
复杂跳跃(Complex Jump) 带弧线/多段跳跃
拉拽(Pull) 被敌人拉向目标
冲刺(Dash) 快速前冲

预测支持版本差异:

  • UE 4.19 和 4.25+:预测正常 ✅;
  • UE 4.20–4.24:预测有 bug ⚠️(任务仍执行功能,多人有轻微网络修正,单人完全正常)。可从 4.25 cherry-pick 预测修复。

7.9 Tick Task 的使用场景

场景 说明
移动到目标位置 平滑移动角色到坐标
缩放 FOV 平滑改变摄像机视野
渐变透明度 平滑改变材质透明度
蓄力进度 每帧更新蓄力值
追踪目标 每帧更新追踪方向

MoveToLocation 式 Tick Task 骨架:

UCLASS()
class UAT_MoveToLocation : public UAbilityTask
{
    GENERATED_BODY()
public:
    UAT_MoveToLocation(const FObjectInitializer& ObjectInitializer)
        : Super(ObjectInitializer) { bTickingTask = true; }

    virtual void Activate() override
    { StartLocation = GetAvatarActor()->GetActorLocation(); CurrentAlpha = 0.f; }

    virtual void TickTask(float DeltaTime) override
    {
        Super::TickTask(DeltaTime);
        CurrentAlpha = FMath::Clamp(CurrentAlpha + DeltaTime / Duration, 0.f, 1.f);
        GetAvatarActor()->SetActorLocation(
            FMath::Lerp(StartLocation, TargetLocation, CurrentAlpha));
        if (CurrentAlpha >= 1.f)
        {
            if (ShouldBroadcastAbilityTaskDelegates())
                OnTargetReached.Broadcast(FGameplayTag(), FGameplayEventData());
            EndTask();
        }
    }

    UPROPERTY(BlueprintAssignable) FMyTaskDelegate OnTargetReached;
    FVector StartLocation, TargetLocation;
    float Duration, CurrentAlpha;
};

最佳实践:不需要每帧更新时,优先用事件驱动 Task(WaitGameplayEvent、WaitAttributeChange),比 Tick 高效。

7.10 Task 的网络权限

Task 继承自 UGameplayTask,可声明在哪个权限上运行:

权限类型 说明 示例
默认 在创建它的权限上运行 —
SimulatedClientOnly 仅模拟客户端运行 客户端表现类 Task
AuthorityOnly 仅服务端运行 伤害判定、生成投射物(权威)
ServerInitiated 服务端发起 服务端驱动技能

投射物的经典分工:拥有者客户端预测生成"客户端表现体",服务端生成"权威命中判定体",用预测键关联——细节在第 11 章。

7.11 常见技能形态设计(使用场景)

动画驱动能力(近战):

1. 激活 → 2. CommitAbility → 3. PlayMontageAndWaitForEvent 播挥砍动画
4. AnimNotify "Hit" 触发 → EventReceived → 命中帧做伤害检测/施加 GE
5. AnimNotify "EndAbility" → EndAbility()

持续施法(引导技能):

1. 激活 → 2. CommitCost → 3. PlayMontageAndWait(施法循环动画)
4. WaitInputRelease(等待松键)→ 5. 每秒 WaitDelay 循环应用治疗
6. 松键 → 7. CommitCooldown → 8. EndAbility

充能能力(蓄力射击):

1. 按下激活 → 2. PlayMontageAndWait 拉弓 → 3. Tick Task 监控蓄力进度更新 UI
4. 等待:松键(WaitInputRelease)→ 以当前蓄力值射击 / 蓄满自动射
5. CommitAbility → 6. 按蓄力值算倍率 → 7. 生成箭矢 → 8. EndAbility

7.12 Task 生命周期与 GC

  • Task 由 GameplayTasksComponent 管理;GA 结束时其未完成任务默认被清理;
  • bStopWhenAbilityEnds 控制蒙太奇在技能结束时是否停止(组合技/被动通常关掉);
  • Task 是 UObject,注意 TObjectPtr/WeakPtr 引用,避免悬垂;
  • ShouldBroadcastAbilityTaskDelegates() 用于广播前检查(防任务销毁后广播悬垂)。

7.13 本章验证

  • [ ] 用 PlayMontageAndWait + WaitInputPress 实现两段式连招;
  • [ ] 写一个自定义 bTickingTask 的延迟伤害 Task,验证 3 秒后对目标结算;
  • [ ] 实现一个 PlayMontageAndWaitForEvent 式组合任务,用 AnimNotify 触发伤害帧;
  • [ ] 故意忘记 ReadyForActivation() / EndTask(),观察技能卡住现象;
  • [ ] 理解 bSimulatedTask 何时需要(移动/根运动任务);
  • [ ] 能画出自定义 Task 的 Activate/回调/OnDestroy 绑定解绑对称关系。

下一章:08 GameplayCues 表现层