// 版权所有 (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__)); \
}