经过考虑,决定使用C++写AI类以提升性能。
但对于C++的AI Task类没有经验,也没有一些优秀的样例学习参考,于是想到参考引擎内的实现。
参考蓝图类UBTTask_BlueprintBase的实现方式,可以新建C++类并复制内容。
注意,此处应当注释或删除FBTNodeBPImplementationHelper::AISpecific
,此为常量会影响运行。
复制之后,可以将此作为C++的AI基类。在子类的构造函数中启用相应的变量,即可启用对应的函数回调。
下面是个子类例子(基类代码放在最后面):
//.CPP File
//构造器中启用ReceiveExecuteAI、Tick的回调:
UBTTask_Follow::UBTTask_Follow(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
ReceiveExecuteImplementations = 1;
ReceiveTickImplementations = 1;
bNotifyTick =1;
}
//实现回调:
void UBTTask_Follow::ReceiveExecuteAI(AAIController* OwnerController, APawn* ControlledPawn)
{
}
void UBTTask_Follow::ReceiveTickAI(AAIController* OwnerController, APawn* ControlledPawn, float DeltaSeconds)
{
}
//.h File
UCLASS()
class PROJECTSPARK_API UBTTask_Follow : public UTaskNodeBase
{
GENERATED_BODY()
UBTTask_Follow(const FObjectInitializer& ObjectInitializer);
public:
protected:
virtual void ReceiveExecuteAI(AAIController* OwnerController, APawn* ControlledPawn) override;
virtual void ReceiveTickAI(AAIController* OwnerController, APawn* ControlledPawn, float DeltaSeconds) override;
};
下面是基类实现:
//.h File
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTTaskNode.h"
#include "TaskNodeBase.generated.h"
UCLASS()
class PROJECTSPARK_API UTaskNodeBase : public UBTTaskNode
{
GENERATED_BODY()
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual EBTNodeResult::Type AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
virtual FString GetStaticDescription() const override;
virtual void DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const override;
virtual void OnInstanceDestroyed(UBehaviorTreeComponent& OwnerComp) override;
virtual void OnTaskFinished(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTNodeResult::Type TaskResult) override;
virtual void InitializeFromAsset(UBehaviorTree& Asset) override;
virtual void SetOwner(AActor* ActorOwner) override;
#if WITH_EDITOR
virtual bool UsesBlueprint() const override;
#endif
public:
UTaskNodeBase(const FObjectInitializer& ObjectInitializer);
protected:
/** Cached AIController owner of BehaviorTreeComponent. */
UPROPERTY(Transient)
TObjectPtr<AAIController> AIOwner;
/** Cached actor owner of BehaviorTreeComponent. */
UPROPERTY(Transient)
TObjectPtr<AActor> ActorOwner;
/** If any of the Tick functions is implemented, how often should they be ticked.
* Values < 0 mean 'every tick'. */
UPROPERTY(EditAnywhere, Category = Task)
FIntervalCountdown TickInterval;
/** temporary variable for ReceiveExecute(Abort)-FinishExecute(Abort) chain */
mutable TEnumAsByte<EBTNodeResult::Type> CurrentCallResult;
/** properties that should be copied */
TArray<FProperty*> PropertyData;
#if WITH_EDITORONLY_DATA
UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = Description)
FString CustomDescription;
#endif // WITH_EDITORONLY_DATA
/** show detailed information about properties */
UPROPERTY(EditInstanceOnly, Category=Description)
uint32 bShowPropertyDetails : 1;
/** set if ReceiveTick is implemented by blueprint */
uint32 ReceiveTickImplementations : 2;
/** set if ReceiveExecute is implemented by blueprint */
uint32 ReceiveExecuteImplementations : 2;
/** set if ReceiveAbort is implemented by blueprint */
uint32 ReceiveAbortImplementations : 2;
/** set when task enters Aborting state */
uint32 bIsAborting : 1;
/** if set, execution is inside blueprint's ReceiveExecute(Abort) event
* FinishExecute(Abort) function should store their result in CurrentCallResult variable */
mutable uint32 bStoreFinishResult : 1;
/** entry point, task will stay active until FinishExecute is called.
* @Note that if both generic and AI event versions are implemented only the more
* suitable one will be called, meaning the AI version if called for AI, generic one otherwise */
UFUNCTION(BlueprintImplementableEvent)
void ReceiveExecute(AActor* OwnerActor);
/** if blueprint graph contains this event, task will stay active until FinishAbort is called
* @Note that if both generic and AI event versions are implemented only the more
* suitable one will be called, meaning the AI version if called for AI, generic one otherwise */
UFUNCTION(BlueprintImplementableEvent)
void ReceiveAbort(AActor* OwnerActor);
/** tick function
* @Note that if both generic and AI event versions are implemented only the more
* suitable one will be called, meaning the AI version if called for AI, generic one otherwise */
UFUNCTION(BlueprintImplementableEvent)
void ReceiveTick(AActor* OwnerActor, float DeltaSeconds);
/** Alternative AI version of ReceiveExecute
* @see ReceiveExecute for more details
* @Note that if both generic and AI event versions are implemented only the more
* suitable one will be called, meaning the AI version if called for AI, generic one otherwise */
virtual void ReceiveExecuteAI(AAIController* OwnerController, APawn* ControlledPawn);
/** Alternative AI version of ReceiveAbort
* @see ReceiveAbort for more details
* @Note that if both generic and AI event versions are implemented only the more
* suitable one will be called, meaning the AI version if called for AI, generic one otherwise */
UFUNCTION(BlueprintImplementableEvent, Category = AI)
void ReceiveAbortAI(AAIController* OwnerController, APawn* ControlledPawn);
/** Alternative AI version of tick function.
* @see ReceiveTick for more details
* @Note that if both generic and AI event versions are implemented only the more
* suitable one will be called, meaning the AI version if called for AI, generic one otherwise */
virtual void ReceiveTickAI(AAIController* OwnerController, APawn* ControlledPawn, float DeltaSeconds);
/** finishes task execution with Success or Fail result */
UFUNCTION(BlueprintCallable, Category="AI|BehaviorTree")
void FinishExecute(bool bSuccess);
/** aborts task execution */
UFUNCTION(BlueprintCallable, Category="AI|BehaviorTree")
void FinishAbort();
/** task execution will be finished (with result 'Success') after receiving specified message */
UFUNCTION(BlueprintCallable, Category="AI|BehaviorTree")
void SetFinishOnMessage(FName MessageName);
/** task execution will be finished (with result 'Success') after receiving specified message with indicated ID */
UFUNCTION(BlueprintCallable, Category="AI|BehaviorTree")
void SetFinishOnMessageWithId(FName MessageName, int32 RequestID = -1);
/** check if task is currently being executed */
UFUNCTION(BlueprintCallable, Category="AI|BehaviorTree")
bool IsTaskExecuting() const;
/** check if task is currently being aborted */
UFUNCTION(BlueprintCallable, Category = "AI|BehaviorTree")
bool IsTaskAborting() const;
/** ticks this task */
virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};
//.cpp File
// Fill out your copyright notice in the Description page of Project Settings.
#include "AIModule/TaskNodeBase.h"
#include "AIController.h"
#include "BehaviorTree/BTFunctionLibrary.h"
#include "BlueprintNodeHelpers.h"
#include "BehaviorTree/BehaviorTree.h"
UTaskNodeBase::UTaskNodeBase(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{
UClass* StopAtClass = UTaskNodeBase::StaticClass();
ReceiveTickImplementations = FBTNodeBPImplementationHelper::CheckEventImplementationVersion(TEXT("ReceiveTick"), TEXT("ReceiveTickAI"), *this, *StopAtClass);
ReceiveExecuteImplementations = FBTNodeBPImplementationHelper::CheckEventImplementationVersion(TEXT("ReceiveExecute"), TEXT("ReceiveExecuteAI"), *this, *StopAtClass);
ReceiveAbortImplementations = FBTNodeBPImplementationHelper::CheckEventImplementationVersion(TEXT("ReceiveAbort"), TEXT("ReceiveAbortAI"), *this, *StopAtClass);
bNotifyTick = ReceiveTickImplementations != FBTNodeBPImplementationHelper::NoImplementation;
bNotifyTaskFinished = true;
bShowPropertyDetails = true;
bIsAborting = false;
// all blueprint based nodes must create instances
bCreateNodeInstance = true;
if (HasAnyFlags(RF_ClassDefaultObject))
{
BlueprintNodeHelpers::CollectPropertyData(this, StopAtClass, PropertyData);
}
}
void UTaskNodeBase::InitializeFromAsset(UBehaviorTree& Asset)
{
Super::InitializeFromAsset(Asset);
if (Asset.BlackboardAsset)
{
BlueprintNodeHelpers::ResolveBlackboardSelectors(*this, *StaticClass(), *Asset.BlackboardAsset);
}
}
void UTaskNodeBase::SetOwner(AActor* InActorOwner)
{
ActorOwner = InActorOwner;
AIOwner = Cast<AAIController>(InActorOwner);
}
EBTNodeResult::Type UTaskNodeBase::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
// fail when task doesn't react to execution (start or tick)
CurrentCallResult = (ReceiveExecuteImplementations != 0 || ReceiveTickImplementations != 0) ? EBTNodeResult::InProgress : EBTNodeResult::Failed;
bIsAborting = false;
if (ReceiveExecuteImplementations != FBTNodeBPImplementationHelper::NoImplementation)
{
bStoreFinishResult = true;
if (AIOwner != nullptr && (ReceiveExecuteImplementations/*& FBTNodeBPImplementationHelper::AISpecific*/))
{
ReceiveExecuteAI(AIOwner, AIOwner->GetPawn());
}
else if (ReceiveExecuteImplementations & FBTNodeBPImplementationHelper::Generic)
{
ReceiveExecute(ActorOwner);
}
bStoreFinishResult = false;
}
return CurrentCallResult;
}
EBTNodeResult::Type UTaskNodeBase::AbortTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
// force dropping all pending latent actions associated with this blueprint
// we can't have those resuming activity when node is/was aborted
BlueprintNodeHelpers::AbortLatentActions(OwnerComp, *this);
CurrentCallResult = ReceiveAbortImplementations != 0 ? EBTNodeResult::InProgress : EBTNodeResult::Aborted;
bIsAborting = true;
if (ReceiveAbortImplementations != FBTNodeBPImplementationHelper::NoImplementation)
{
bStoreFinishResult = true;
if (AIOwner != nullptr && (ReceiveAbortImplementations & FBTNodeBPImplementationHelper::AISpecific))
{
ReceiveAbortAI(AIOwner, AIOwner->GetPawn());
}
else if (ReceiveAbortImplementations & FBTNodeBPImplementationHelper::Generic)
{
ReceiveAbort(ActorOwner);
}
bStoreFinishResult = false;
}
return CurrentCallResult;
}
void UTaskNodeBase::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
if (TickInterval.Tick(DeltaSeconds))
{
DeltaSeconds = TickInterval.GetElapsedTimeWithFallback(DeltaSeconds);
if (AIOwner != nullptr && (ReceiveTickImplementations /*& FBTNodeBPImplementationHelper::AISpecific*/))
{
ReceiveTickAI(AIOwner, AIOwner->GetPawn(), DeltaSeconds);
}
else if (ReceiveTickImplementations & FBTNodeBPImplementationHelper::Generic)
{
ReceiveTick(ActorOwner, DeltaSeconds);
}
TickInterval.Reset();
}
}
void UTaskNodeBase::OnTaskFinished(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTNodeResult::Type TaskResult)
{
Super::OnTaskFinished(OwnerComp, NodeMemory, TaskResult);
TickInterval.Set(0); // so that we tick as soon as enabled back
if (TaskResult != EBTNodeResult::InProgress)
{
BlueprintNodeHelpers::AbortLatentActions(OwnerComp, *this);
}
}
void UTaskNodeBase::FinishExecute(bool bSuccess)
{
UBehaviorTreeComponent* OwnerComp = Cast<UBehaviorTreeComponent>(GetOuter());
EBTNodeResult::Type NodeResult(bSuccess ? EBTNodeResult::Succeeded : EBTNodeResult::Failed);
if (bStoreFinishResult)
{
CurrentCallResult = NodeResult;
}
else if (OwnerComp && !bIsAborting)
{
FinishLatentTask(*OwnerComp, NodeResult);
}
}
void UTaskNodeBase::FinishAbort()
{
UBehaviorTreeComponent* OwnerComp = Cast<UBehaviorTreeComponent>(GetOuter());
EBTNodeResult::Type NodeResult(EBTNodeResult::Aborted);
if (bStoreFinishResult)
{
CurrentCallResult = NodeResult;
}
else if (OwnerComp && bIsAborting)
{
FinishLatentAbort(*OwnerComp);
}
}
bool UTaskNodeBase::IsTaskExecuting() const
{
UBehaviorTreeComponent* OwnerComp = Cast<UBehaviorTreeComponent>(GetOuter());
EBTTaskStatus::Type TaskStatus = OwnerComp->GetTaskStatus(this);
return (TaskStatus == EBTTaskStatus::Active);
}
bool UTaskNodeBase::IsTaskAborting() const
{
// use already cached data
return bIsAborting;
}
void UTaskNodeBase::SetFinishOnMessage(FName MessageName)
{
UBehaviorTreeComponent* OwnerComp = Cast<UBehaviorTreeComponent>(GetOuter());
if (OwnerComp)
{
OwnerComp->RegisterMessageObserver(this, MessageName);
}
}
void UTaskNodeBase::SetFinishOnMessageWithId(FName MessageName, int32 RequestID)
{
UBehaviorTreeComponent* OwnerComp = Cast<UBehaviorTreeComponent>(GetOuter());
if (OwnerComp)
{
OwnerComp->RegisterMessageObserver(this, MessageName, RequestID);
}
}
FString UTaskNodeBase::GetStaticDescription() const
{
FString ReturnDesc =
#if WITH_EDITORONLY_DATA
CustomDescription.Len() ? CustomDescription :
#endif // WITH_EDITORONLY_DATA
Super::GetStaticDescription();
UTaskNodeBase* CDO = (UTaskNodeBase*)(GetClass()->GetDefaultObject());
if (bShowPropertyDetails && CDO)
{
UClass* StopAtClass = UTaskNodeBase::StaticClass();
FString PropertyDesc = BlueprintNodeHelpers::CollectPropertyDescription(this, StopAtClass, CDO->PropertyData);
if (PropertyDesc.Len())
{
ReturnDesc += TEXT(":\n\n");
ReturnDesc += PropertyDesc;
}
}
return ReturnDesc;
}
void UTaskNodeBase::DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const
{
UTaskNodeBase* CDO = (UTaskNodeBase*)(GetClass()->GetDefaultObject());
if (CDO && CDO->PropertyData.Num())
{
BlueprintNodeHelpers::DescribeRuntimeValues(this, CDO->PropertyData, Values);
}
}
void UTaskNodeBase::OnInstanceDestroyed(UBehaviorTreeComponent& OwnerComp)
{
// force dropping all pending latent actions associated with this blueprint
BlueprintNodeHelpers::AbortLatentActions(OwnerComp, *this);
}
#if WITH_EDITOR
bool UTaskNodeBase::UsesBlueprint() const
{
return true;
}
#endif // WITH_EDITOR
void UTaskNodeBase::ReceiveExecuteAI(AAIController* OwnerController, APawn* ControlledPawn)
{
}
void UTaskNodeBase::ReceiveTickAI(AAIController* OwnerController, APawn* ControlledPawn, float DeltaSeconds)
{
}
近期评论