虚幻GameAbilitySystem源码与设计解析-AbilityTask的实现

// 版权所有 (c) Epic Games, Inc. 保留所有权利。
#pragma once

#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "Templates/SubclassOf.h"
#include "GameplayPrediction.h"
#include "GameplayAbilitySpec.h"
#include "Abilities/GameplayAbilityTypes.h"
#include "GameplayTask.h"
#include "Abilities/GameplayAbility.h"
#include "AbilityTask.generated.h"

class UAbilitySystemComponent;
class UGameplayTasksComponent;

/**
 * 技能任务是在执行技能时可以执行的小型、自包含的操作。
 * 它们本质上是潜伏/异步的。通常遵循“启动某项操作并等待其完成或被中断”的模式。
 * 
 * 我们在 K2Node_LatentAbilityCall 中有相关代码,以使在蓝图中使用这些任务更加流畅。熟悉技能任务的最佳方法是
 * 查看现有的任务,如 UAbilityTask_WaitOverlap(非常简单)和 UAbilityTask_WaitTargetData(复杂得多)。
 * 
 * 使用技能任务的基本要求如下:
 * 
 * 1) 在你的技能任务中定义动态多播、可在蓝图中分配的委托。这些是任务的输出。当这些委托触发时,
 *    执行将在调用的蓝图中继续。
 * 
 * 2) 你的输入由一个静态工厂函数定义,该函数将实例化你的任务的一个实例。此函数的参数定义了
 *    任务的输入。工厂函数应该只实例化你的任务并可能设置起始参数。它不应调用任何回调委托!
 * 
 * 3) 实现一个 Activate() 函数(在基类中定义)。此函数应实际启动/执行你的任务逻辑。在这里调用回调委托是安全的。
 * 
 * 
 * 这就是基本技能任务所需的全部内容。
 * 
 * 
 * 检查清单:
 *  - 重写 ::OnDestroy() 并注销任务注册的任何回调。也要调用 Super::EndTask!
 *  - 实现一个真正“启动”任务的 Activate 函数。不要在静态工厂函数中“启动”任务!
 * 
 * 
 * --------------------------------------
 * 
 * 我们为想要生成角色的技能任务提供了额外的支持。虽然这可以在 Activate() 函数中完成,但不可能传递动态的“生成时暴露”角色属性。
 * 这是蓝图的一个强大功能,为了支持这一点,你需要实现不同的步骤 3:
 * 
 * 不要使用 Activate() 函数,而是应该实现一个 BeginSpawningActor() 和 FinishSpawningActor() 函数。
 * 
 * BeginSpawningActor() 必须包含一个名为 'Class' 的 TSubclassOf<YourActorClassToSpawn> 类型的参数。它还必须有一个名为 SpawnedActor 的 
 * YourActorClassToSpawn*& 类型的输出引用参数。此函数可以决定是否要生成角色(如果希望根据网络权限来决定角色生成,这很有用)。
 * 
 * BeginSpawningActor() 可以使用 SpawnActorDeferred 来实例化一个角色。这很重要,否则 UCS 将在生成参数设置之前运行。
 * BeginSpawningActor() 还应该将 SpawnedActor 参数设置为它生成的角色。
 * 
 * [接下来,生成的字节码将把“生成时暴露”参数设置为用户设置的值]
 * 
 * 如果你生成了某个角色,FinishSpawningActor() 将被调用,并传入刚刚生成的同一个角色。你必须在这个角色上调用 ExecuteConstruction + PostActorConstruction!
 * 
 * 这有很多步骤,但总的来说,AbilityTask_SpawnActor() 给出了一个清晰、最小化的示例。
 * 
 */

/**
 * 潜伏任务正在等待某些事情。这是为了区分等待用户做某事和等待游戏做某事。
 * 任务开始时处于 WaitingOnGame 状态,并在适当的时候设置为 WaitingOnUser(参见 WaitTargetData、WaitInputPress 等)
 */
UENUM()
enum class EAbilityTaskWaitState : uint8
{
    /** 任务正在等待游戏做某事 */
    WaitingOnGame = 0x01,

    /** 等待用户做某事 */
    WaitingOnUser = 0x02,

    /** 等待化身(角色/ pawn / 角色)做某事(通常是世界中的物理动作,如着陆、移动等) */
    WaitingOnAvatar = 0x04
};

UCLASS(Abstract)
class GAMEPLAYABILITIES_API UAbilityTask : public UGameplayTask
{
    GENERATED_UCLASS_BODY()

    virtual void OnDestroy(bool bInOwnerFinished) override;
    virtual void BeginDestroy() override;

    /** 返回拥有此任务的技能的规格句柄 */
    FGameplayAbilitySpecHandle GetAbilitySpecHandle() const;

    void SetAbilitySystemComponent(UAbilitySystemComponent* InAbilitySystemComponent);

    /** 创建此任务的游戏玩法技能 */
    UPROPERTY()
    TObjectPtr<UGameplayAbility> Ability;

    UPROPERTY()
    TWeakObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;

    /** 如果技能是在客户端上运行的本地预测技能,则返回 true。通常这意味着我们需要告诉服务器某些事情。 */
    bool IsPredictingClient() const;

    /** 如果我们正在服务器上为非本地控制的客户端执行技能,则返回 true */
    bool IsForRemoteClient() const;

    /** 如果我们正在本地控制的客户端上执行技能,则返回 true */
    bool IsLocallyControlled() const;

    /** 返回拥有此任务的技能的激活预测键 */
    FPredictionKey GetActivationPredictionKey() const;

    /** 在将委托广播回技能图表之前应调用此函数。这确保技能仍然处于激活状态。 */
    bool ShouldBroadcastAbilityTaskDelegates() const;

    virtual void InitSimulatedTask(UGameplayTasksComponent& InGameplayTasksComponent) override;

    static void DebugRecordAbilityTaskCreatedByAbility(const UObject* Ability);

    /** 用于实例化和初始化新任务的辅助函数 */
    template <class T>
    static T* NewAbilityTask(UGameplayAbility* ThisAbility, FName InstanceName = FName())
    {
        check(ThisAbility);

        T* MyObj = NewObject<T>();
        MyObj->InitTask(*ThisAbility, ThisAbility->GetGameplayTaskDefaultPriority());

        UAbilityTask::DebugRecordAbilityTaskCreatedByAbility(ThisAbility);

        MyObj->InstanceName = InstanceName;
        return MyObj;
    }

    template<typename T>
    static bool DelayedFalse()
    {
        return false;
    }

    // 此函数已添加,以确保技能任务不使用此方法
    template <class T>
    FORCEINLINE static T* NewTask(UObject* WorldContextObject, FName InstanceName = FName())
    {
        static_assert(DelayedFalse<T>(), "UAbilityTask::NewTask 不应被使用。请使用 NewAbilityTask 代替");
        return nullptr;
    }

    /** 当技能任务正在等待远程玩家数据时调用。如果远程玩家过早结束技能,并且设置了此标志的任务仍在运行,则技能将被终止。 */
    void SetWaitingOnRemotePlayerData();
    void ClearWaitingOnRemotePlayerData();
    virtual bool IsWaitingOnRemotePlayerdata() const override;

    /** 与 RemotePlayerData 相同,但针对 ACharacter 类型的状态(移动状态等) */
    void SetWaitingOnAvatar();
    void ClearWaitingOnAvatar();
    virtual bool IsWaitingOnAvatar() const override;

    /** 我们正在等待的状态 */
    uint8 WaitStateBitMask;
    uint8 bWasSuccessfullyDestroyed : 1;

protected:
    /** 用于注册客户端复制回调的辅助方法 */
    bool CallOrAddReplicatedDelegate(EAbilityGenericReplicatedEvent::Type Event, FSimpleMulticastDelegate::FDelegate Delegate);
};

// 用于在技能任务实例列表中搜索
struct FAbilityInstanceNamePredicate
{
    FAbilityInstanceNamePredicate(FName DesiredInstanceName)
    {
        InstanceName = DesiredInstanceName;
    }

    bool operator()(const TWeakObjectPtr<UAbilityTask> A) const
    {
        return (A.IsValid() && !A.Get()->GetInstanceName().IsNone() && A.Get()->GetInstanceName().IsValid() && (A.Get()->GetInstanceName() == InstanceName));
    }

    FName InstanceName;
};

struct FAbilityInstanceClassPredicate
{
    FAbilityInstanceClassPredicate(TSubclassOf<UAbilityTask> Class)
    {
        TaskClass = Class;
    }

    bool operator()(const TWeakObjectPtr<UAbilityTask> A) const
    {
        return (A.IsValid() && (A.Get()->GetClass() == TaskClass));
    }

    TSubclassOf<UAbilityTask> TaskClass;
};

#define ABILITYTASK_MSG(Format, ...) \
    if (ENABLE_ABILITYTASK_DEBUGMSG) \
    { \
        if (Ability) \
            Ability->AddAbilityTaskDebugMessage(this, FString::Printf(TEXT(Format), ##__VA_ARGS__)); \
    }

 

posted @ 2025-02-23 23:41  mcwhirr  阅读(85)  评论(0)    收藏  举报