UE5 AI行为树源码解析(Composites)
文章目录
- 一、BTComposite_Selector
- 头文件部分 (BTComposite_Selector.h)
- 源文件部分 (BTComposite_Selector.cpp)
- 功能详细说明
- Selector 节点的工作逻
- 具体例子说明:
- 代码执行流程分析:
- 二、BTComposite_Sequence
- 头文件部分 (BTComposite_Sequence.h)
- 源文件部分 (BTComposite_Sequence.cpp)
- 功能详细说明
- Sequence 节点的工作逻辑:
- 具体例子说明:
- 代码执行流程分析:
- 重要特性:CanAbortLowerPriority()
- 三、BTComposite_SimpleParallel
- 头文件部分 (BTComposite_SimpleParallel.h)
- 源文件部分 (BTComposite_SimpleParallel.cpp)
- 功能详细说明
- SimpleParallel 节点的工作逻辑:
- 两种完成模式:
- 具体例子说明:
- 代码执行流程分析:
- 内存管理:
- 重要特性:
一、BTComposite_Selector
头文件部分 (BTComposite_Selector.h)
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTCompositeNode.h"
#include "BTComposite_Selector.generated.h"/** * Selector composite node.* Selector Nodes execute their children from left to right, and will stop executing its children when one of their children succeeds.* If a Selector's child succeeds, the Selector succeeds. If all the Selector's children fail, the Selector fails.*/
UCLASS(MinimalAPI)
class UBTComposite_Selector: public UBTCompositeNode
{GENERATED_UCLASS_BODY()// 核心函数:决定下一个要执行的子节点AIMODULE_API virtual int32 GetNextChildHandler(struct FBehaviorTreeSearchData& SearchData, int32 PrevChild, EBTNodeResult::Type LastResult) const override;#if WITH_EDITOR// 编辑器相关:获取节点图标名称AIMODULE_API virtual FName GetNodeIconName() const override;
#endif
};
源文件部分 (BTComposite_Selector.cpp)
// Copyright Epic Games, Inc. All Rights Reserved.#include "BehaviorTree/Composites/BTComposite_Selector.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(BTComposite_Selector)// 构造函数:设置节点名称为"Selector"
UBTComposite_Selector::UBTComposite_Selector(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{NodeName = "Selector"; // 设置节点在编辑器中的显示名称
}// 核心函数实现:决定下一个要执行的子节点
int32 UBTComposite_Selector::GetNextChildHandler(FBehaviorTreeSearchData& SearchData, int32 PrevChild, EBTNodeResult::Type LastResult) const
{// 默认情况下返回BTSpecialChild::ReturnToParent,表示返回到父节点int32 NextChildIdx = BTSpecialChild::ReturnToParent;// 如果之前没有执行过任何子节点(首次执行)if (PrevChild == BTSpecialChild::NotInitialized){// 新激活的选择器:从第一个子节点开始执行NextChildIdx = 0;}// 如果上一个子节点执行失败,并且还有更多子节点可以执行else if (LastResult == EBTNodeResult::Failed && (PrevChild + 1) < GetChildrenNum()){// 失败时选择下一个子节点继续执行NextChildIdx = PrevChild + 1;}// 其他情况(成功或没有更多子节点)都会返回ReturnToParentreturn NextChildIdx;
}#if WITH_EDITOR
// 获取在行为树编辑器中显示的图标名称
FName UBTComposite_Selector::GetNodeIconName() const
{return FName("BTEditor.Graph.BTNode.Composite.Selector.Icon");
}
#endif
功能详细说明
Selector 节点的工作逻
Selector 节点按照 “短路逻辑” 执行:
- 从左到右依次执行子节点
- 一旦有子节点成功,立即停止执行后续子节点,整个 Selector 返回成功
- 只有所有子节点都失败时,Selector 才返回失败
具体例子说明:
假设有一个 AI 的行为树如下:
Selector
├── 移动到玩家 (MoveToPlayer)
├── 寻找武器 (FindWeapon)
└── 巡逻 (Patrol)
执行流程:
-
情况1:移动到玩家成功
- 执行
MoveToPlayer→ 成功 - Selector 立即返回成功,不执行
FindWeapon和Patrol
- 执行
-
情况2:移动到玩家失败,寻找武器成功
- 执行
MoveToPlayer→ 失败 → 执行FindWeapon→ 成功 - Selector 返回成功,不执行
Patrol
- 执行
-
情况3:所有子节点都失败
- 执行
MoveToPlayer→ 失败 → 执行FindWeapon→ 失败 → 执行Patrol→ 失败 - Selector 返回失败
- 执行
代码执行流程分析:
// 假设有3个子节点 [0, 1, 2]// 第一次调用:PrevChild = NotInitialized
GetNextChildHandler(SearchData, NotInitialized, 无上次结果)
→ 进入 if (PrevChild == BTSpecialChild::NotInitialized)
→ 返回 0 (执行第一个子节点)// 第二次调用:第一个子节点失败
GetNextChildHandler(SearchData, 0, EBTNodeResult::Failed)
→ 进入 else if (LastResult == Failed && (0+1) < 3)
→ 返回 1 (执行第二个子节点)// 第三次调用:第二个子节点成功
GetNextChildHandler(SearchData, 1, EBTNodeResult::Succeeded)
→ 不满足任何条件
→ 返回 ReturnToParent (停止执行,Selector成功)// 或者:第二个子节点也失败
GetNextChildHandler(SearchData, 1, EBTNodeResult::Failed)
→ 进入 else if (LastResult == Failed && (1+1) < 3)
→ 返回 2 (执行第三个子节点)
这种设计模式使得 Selector 成为行为树中实现 “优先级选择” 的核心组件,常用于决策逻辑如:“优先攻击,如果不能攻击就移动,如果都不能就待命”。
二、BTComposite_Sequence
头文件部分 (BTComposite_Sequence.h)
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTCompositeNode.h"
#include "BTComposite_Sequence.generated.h"/*** Sequence composite node.* Sequence Nodes execute their children from left to right, and will stop executing its children when one of their children fails.* If a child fails, then the Sequence fails. If all the Sequence's children succeed, then the Sequence succeeds.*/
UCLASS(MinimalAPI)
class UBTComposite_Sequence : public UBTCompositeNode
{GENERATED_UCLASS_BODY()// 核心函数:决定下一个要执行的子节点AIMODULE_API virtual int32 GetNextChildHandler(struct FBehaviorTreeSearchData& SearchData, int32 PrevChild, EBTNodeResult::Type LastResult) const override;#if WITH_EDITOR// 是否可以中止低优先级任务AIMODULE_API virtual bool CanAbortLowerPriority() const override;// 获取节点图标名称AIMODULE_API virtual FName GetNodeIconName() const override;
#endif
};
源文件部分 (BTComposite_Sequence.cpp)
// Copyright Epic Games, Inc. All Rights Reserved.#include "BehaviorTree/Composites/BTComposite_Sequence.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(BTComposite_Sequence)// 构造函数:设置节点名称为"Sequence"
UBTComposite_Sequence::UBTComposite_Sequence(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{NodeName = "Sequence"; // 设置节点在编辑器中的显示名称
}// 核心函数实现:决定下一个要执行的子节点
int32 UBTComposite_Sequence::GetNextChildHandler(FBehaviorTreeSearchData& SearchData, int32 PrevChild, EBTNodeResult::Type LastResult) const
{// 默认情况下返回BTSpecialChild::ReturnToParent,表示返回到父节点int32 NextChildIdx = BTSpecialChild::ReturnToParent;// 如果之前没有执行过任何子节点(首次执行)if (PrevChild == BTSpecialChild::NotInitialized){// 新激活的序列:从第一个子节点开始执行NextChildIdx = 0;}// 如果上一个子节点执行成功,并且还有更多子节点可以执行else if (LastResult == EBTNodeResult::Succeeded && (PrevChild + 1) < GetChildrenNum()){// 成功时选择下一个子节点继续执行NextChildIdx = PrevChild + 1;}// 其他情况(失败或没有更多子节点)都会返回ReturnToParentreturn NextChildIdx;
}#if WITH_EDITOR
// 是否可以中止低优先级任务 - 对于Sequence返回false
bool UBTComposite_Sequence::CanAbortLowerPriority() const
{// 不允许中止低优先级任务,因为这会破坏序列的执行顺序且没有意义return false;
}// 获取在行为树编辑器中显示的图标名称
FName UBTComposite_Sequence::GetNodeIconName() const
{return FName("BTEditor.Graph.BTNode.Composite.Sequence.Icon");
}
#endif
功能详细说明
Sequence 节点的工作逻辑:
Sequence 节点按照 “顺序执行” 逻辑:
- 从左到右依次执行子节点
- 一旦有子节点失败,立即停止执行后续子节点,整个 Sequence 返回失败
- 只有所有子节点都成功时,Sequence 才返回成功
具体例子说明:
假设有一个 AI 的攻击行为序列:
Sequence
├── 检查视野 (CheckLineOfSight)
├── 瞄准目标 (AimAtTarget)
└── 开火射击 (FireWeapon)
执行流程:
-
情况1:完整序列成功
- 执行
CheckLineOfSight→ 成功 - 执行
AimAtTarget→ 成功 - 执行
FireWeapon→ 成功 Sequence返回成功
- 执行
-
情况2:中间节点失败
- 执行
CheckLineOfSight→ 成功 - 执行
AimAtTarget→ 失败(目标移动太快) Sequence立即返回失败,不执行FireWeapon
- 执行
-
情况3:第一个节点就失败
- 执行
CheckLineOfSight→ 失败(目标不在视野内) Sequence立即返回失败,不执行后续节点
- 执行
代码执行流程分析:
// 假设有3个子节点 [0, 1, 2]// 第一次调用:PrevChild = NotInitialized
GetNextChildHandler(SearchData, NotInitialized, 无上次结果)
→ 进入 if (PrevChild == BTSpecialChild::NotInitialized)
→ 返回 0 (执行第一个子节点)// 第二次调用:第一个子节点成功
GetNextChildHandler(SearchData, 0, EBTNodeResult::Succeeded)
→ 进入 else if (LastResult == Succeeded && (0+1) < 3)
→ 返回 1 (执行第二个子节点)// 第三次调用:第二个子节点成功
GetNextChildHandler(SearchData, 1, EBTNodeResult::Succeeded)
→ 进入 else if (LastResult == Succeeded && (1+1) < 3)
→ 返回 2 (执行第三个子节点)// 第四次调用:第三个子节点成功
GetNextChildHandler(SearchData, 2, EBTNodeResult::Succeeded)
→ 不满足任何条件(2+1 = 3,不小于3)
→ 返回 ReturnToParent (Sequence成功完成)// 或者:第二个子节点失败
GetNextChildHandler(SearchData, 1, EBTNodeResult::Failed)
→ 不满足任何条件
→ 返回 ReturnToParent (立即停止,Sequence失败)
重要特性:CanAbortLowerPriority()
bool UBTComposite_Sequence::CanAbortLowerPriority() const
{return false; // 不允许中止低优先级任务
}
这个设置很重要,因为:
- Sequence 需要严格按照顺序执行子节点
- 如果允许中止低优先级任务,可能会破坏序列的逻辑完整性
- 例如:一个"开门-进入-关门"的序列,如果中途被中断,可能导致状态不一致。
三、BTComposite_SimpleParallel
头文件部分 (BTComposite_SimpleParallel.h)
// Copyright Epic Games, Inc. All Rights Reserved.#pragma once#include "CoreMinimal.h"
#include "UObject/ObjectMacros.h"
#include "BehaviorTree/BTCompositeNode.h"
#include "BTComposite_SimpleParallel.generated.h"// 定义并行节点的两个子节点类型
namespace EBTParallelChild
{enum Type{MainTask, // 主任务(必须是单个任务节点)BackgroundTree, // 背景树(可以是完整子树)};
}// 定义并行完成模式
UENUM()
namespace EBTParallelMode
{// keep in sync with DescribeFinishModeenum Type : int{AbortBackground UMETA(DisplayName="Immediate" , ToolTip="When main task finishes, immediately abort background tree."),WaitForBackground UMETA(DisplayName="Delayed" , ToolTip="When main task finishes, wait for background tree to finish."),};
}// 并行节点的内存结构
struct FBTParallelMemory : public FBTCompositeMemory
{/** last Id of search, detect infinite loops when there isn't any valid task in background tree */int32 LastSearchId;/** finish result of main task */TEnumAsByte<EBTNodeResult::Type> MainTaskResult;/** set when main task is running */uint8 bMainTaskIsActive : 1;/** try running background tree task even if main task has finished */uint8 bForceBackgroundTree : 1;/** set when main task needs to be repeated */uint8 bRepeatMainTask : 1;
};/*** Simple Parallel composite node.* Allows for running two children: one which must be a single task node (with optional decorators), and the other of which can be a complete subtree.*/
UCLASS(HideCategories=(Composite), MinimalAPI)
class UBTComposite_SimpleParallel : public UBTCompositeNode
{GENERATED_UCLASS_BODY()/** how background tree should be handled when main task finishes execution */UPROPERTY(EditInstanceOnly, Category = Parallel)TEnumAsByte<EBTParallelMode::Type> FinishMode;/** handle child updates */AIMODULE_API virtual int32 GetNextChildHandler(FBehaviorTreeSearchData& SearchData, int32 PrevChild, EBTNodeResult::Type LastResult) const override;AIMODULE_API virtual void NotifyChildExecution(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, int32 ChildIdx, EBTNodeResult::Type& NodeResult) const override;AIMODULE_API virtual void NotifyNodeDeactivation(FBehaviorTreeSearchData& SearchData, EBTNodeResult::Type& NodeResult) const override;AIMODULE_API virtual bool CanNotifyDecoratorsOnDeactivation(FBehaviorTreeSearchData& SearchData, int32 ChildIdx, EBTNodeResult::Type& NodeResult) const override;AIMODULE_API virtual bool CanPushSubtree(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, int32 ChildIdx) const override;AIMODULE_API virtual void SetChildOverride(FBehaviorTreeSearchData& SearchData, int8 Index) const override;AIMODULE_API virtual uint16 GetInstanceMemorySize() const override;AIMODULE_API virtual void InitializeMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryInit::Type InitType) const override;AIMODULE_API virtual void CleanupMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryClear::Type CleanupType) const override;AIMODULE_API virtual FString GetStaticDescription() const override;AIMODULE_API virtual void DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const override;/** helper for showing values of EBTParallelMode enum */static AIMODULE_API FString DescribeFinishMode(EBTParallelMode::Type Mode);#if WITH_EDITORAIMODULE_API virtual bool CanAbortLowerPriority() const override;AIMODULE_API virtual bool CanAbortSelf() const override;AIMODULE_API virtual FName GetNodeIconName() const override;
#endif // WITH_EDITOR
};
源文件部分 (BTComposite_SimpleParallel.cpp)
// Copyright Epic Games, Inc. All Rights Reserved.#include "BehaviorTree/Composites/BTComposite_SimpleParallel.h"
#include "GameFramework/Actor.h"
#include "VisualLogger/VisualLogger.h"
#include "BehaviorTree/BTTaskNode.h"
#include "BehaviorTree/BTAuxiliaryNode.h"#include UE_INLINE_GENERATED_CPP_BY_NAME(BTComposite_SimpleParallel)// 构造函数
UBTComposite_SimpleParallel::UBTComposite_SimpleParallel(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
{NodeName = "Simple Parallel";INIT_COMPOSITE_NODE_NOTIFY_FLAGS();bApplyDecoratorScope = true; // 应用装饰器作用域
}// 核心函数:决定下一个要执行的子节点
int32 UBTComposite_SimpleParallel::GetNextChildHandler(FBehaviorTreeSearchData& SearchData, int32 PrevChild, EBTNodeResult::Type LastResult) const
{FBTParallelMemory* MyMemory = GetNodeMemory<FBTParallelMemory>(SearchData);int32 NextChildIdx = BTSpecialChild::ReturnToParent;// 首次执行:从主任务开始if (PrevChild == BTSpecialChild::NotInitialized){NextChildIdx = EBTParallelChild::MainTask;MyMemory->MainTaskResult = EBTNodeResult::Failed;MyMemory->bRepeatMainTask = false;}// 如果主任务正在运行或需要强制运行背景树,则执行背景树else if ((MyMemory->bMainTaskIsActive || MyMemory->bForceBackgroundTree) && !SearchData.OwnerComp.IsRestartPending()){NextChildIdx = EBTParallelChild::BackgroundTree;MyMemory->bForceBackgroundTree = false;}// 如果需要重复主任务else if (MyMemory->bRepeatMainTask){UE_VLOG(SearchData.OwnerComp.GetOwner(), LogBehaviorTree, Verbose, TEXT("Repeating main task of %s"), *UBehaviorTreeTypes::DescribeNodeHelper(this));NextChildIdx = EBTParallelChild::MainTask;MyMemory->bRepeatMainTask = false;}// 检测无限循环:如果在同一搜索中重复执行同一分支if ((PrevChild == NextChildIdx) && (MyMemory->LastSearchId == SearchData.SearchId)){SearchData.bPostponeSearch = true; // 推迟搜索,避免无限循环}MyMemory->LastSearchId = SearchData.SearchId;return NextChildIdx;
}// 当子节点执行时被调用
void UBTComposite_SimpleParallel::NotifyChildExecution(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, int32 ChildIdx, EBTNodeResult::Type& NodeResult) const
{FBTParallelMemory* MyMemory = (FBTParallelMemory*)NodeMemory;// 处理主任务的执行结果if (ChildIdx == EBTParallelChild::MainTask){MyMemory->MainTaskResult = NodeResult;// 如果主任务正在执行中if (NodeResult == EBTNodeResult::InProgress){EBTTaskStatus::Type Status = OwnerComp.GetTaskStatus(Children[EBTParallelChild::MainTask].ChildTask);if (Status == EBTTaskStatus::Active){// 注册并行任务并请求延迟执行,以便同时运行背景树MyMemory->bMainTaskIsActive = true;MyMemory->bForceBackgroundTree = false;OwnerComp.RegisterParallelTask(Children[EBTParallelChild::MainTask].ChildTask);RequestDelayedExecution(OwnerComp, EBTNodeResult::Succeeded);}}// 如果主任务已完成(成功、失败或被中止)else if (MyMemory->bMainTaskIsActive){MyMemory->bMainTaskIsActive = false;// 通知装饰器主任务已停用FBehaviorTreeSearchData FakeSearchData(OwnerComp);NotifyDecoratorsOnDeactivation(FakeSearchData, ChildIdx, NodeResult, true);const int32 MyInstanceIdx = OwnerComp.FindInstanceContainingNode(this);check(MyInstanceIdx < MAX_uint16);// 注销并行任务OwnerComp.UnregisterParallelTask(Children[EBTParallelChild::MainTask].ChildTask, static_cast<uint16>(MyInstanceIdx));// 如果主任务完成且不需要重复,检查是否需要中止背景树if (NodeResult != EBTNodeResult::Aborted && !MyMemory->bRepeatMainTask){if (FinishMode == EBTParallelMode::AbortBackground){// 立即中止背景树OwnerComp.RequestExecution((UBTCompositeNode*)this, MyInstanceIdx,Children[EBTParallelChild::MainTask].ChildTask, EBTParallelChild::MainTask,NodeResult);}}}// 特殊情况:主任务立即完成,但需要等待背景树else if (NodeResult == EBTNodeResult::Succeeded && FinishMode == EBTParallelMode::WaitForBackground){// 强制运行一次背景树MyMemory->bForceBackgroundTree = true;RequestDelayedExecution(OwnerComp, EBTNodeResult::Succeeded);}}
}// 当节点停用时被调用
void UBTComposite_SimpleParallel::NotifyNodeDeactivation(FBehaviorTreeSearchData& SearchData, EBTNodeResult::Type& NodeResult) const
{FBTParallelMemory* MyMemory = GetNodeMemory<FBTParallelMemory>(SearchData);const uint16 ActiveInstanceIdx = SearchData.OwnerComp.GetActiveInstanceIdx();// 使用主任务的结果作为节点结果if (!MyMemory->bMainTaskIsActive){NodeResult = MyMemory->MainTaskResult;}// 移除主任务if (Children.IsValidIndex(EBTParallelChild::MainTask)){SearchData.AddUniqueUpdate(FBehaviorTreeSearchUpdate(Children[EBTParallelChild::MainTask].ChildTask, ActiveInstanceIdx, EBTNodeUpdateMode::Remove));}
}// 检查是否可以在停用时通知装饰器
bool UBTComposite_SimpleParallel::CanNotifyDecoratorsOnDeactivation(FBehaviorTreeSearchData& SearchData, int32 ChildIdx, EBTNodeResult::Type& NodeResult) const
{// 当并行节点只是切换到背景树时,不要通知装饰器if (ChildIdx == EBTParallelChild::MainTask){FBTParallelMemory* MyMemory = GetNodeMemory<FBTParallelMemory>(SearchData);if (MyMemory->bMainTaskIsActive){return false;}}return true;
}// 获取内存大小
uint16 UBTComposite_SimpleParallel::GetInstanceMemorySize() const
{return sizeof(FBTParallelMemory);
}// 初始化内存
void UBTComposite_SimpleParallel::InitializeMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryInit::Type InitType) const
{InitializeNodeMemory<FBTParallelMemory>(NodeMemory, InitType);
}// 清理内存
void UBTComposite_SimpleParallel::CleanupMemory(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTMemoryClear::Type CleanupType) const
{CleanupNodeMemory<FBTParallelMemory>(NodeMemory, CleanupType);
}// 检查是否可以推送子树(只有背景树可以)
bool UBTComposite_SimpleParallel::CanPushSubtree(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, int32 ChildIdx) const
{return (ChildIdx != EBTParallelChild::MainTask);
}// 设置子节点覆盖(用于重复主任务)
void UBTComposite_SimpleParallel::SetChildOverride(FBehaviorTreeSearchData& SearchData, int8 Index) const
{if (Index == EBTParallelChild::MainTask){FBTParallelMemory* MyMemory = GetNodeMemory<FBTParallelMemory>(SearchData);MyMemory->bRepeatMainTask = true;UE_VLOG(SearchData.OwnerComp.GetOwner(), LogBehaviorTree, Log, TEXT("Main task of %s will be repeated."), *UBehaviorTreeTypes::DescribeNodeHelper(this));}
}// 描述完成模式的辅助函数
FString UBTComposite_SimpleParallel::DescribeFinishMode(EBTParallelMode::Type Mode)
{static FString FinishDesc[] = { TEXT("AbortBackground"), TEXT("WaitForBackground") };return FinishDesc[Mode];
}// 获取静态描述
FString UBTComposite_SimpleParallel::GetStaticDescription() const
{static FString FinishDesc[] = { TEXT("finish with main task"), TEXT("wait for subtree") };return FString::Printf(TEXT("%s: %s"), *Super::GetStaticDescription(),FinishMode < 2 ? *FinishDesc[FinishMode] : TEXT(""));
}// 描述运行时值
void UBTComposite_SimpleParallel::DescribeRuntimeValues(const UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, EBTDescriptionVerbosity::Type Verbosity, TArray<FString>& Values) const
{Super::DescribeRuntimeValues(OwnerComp, NodeMemory, Verbosity, Values);if (Verbosity == EBTDescriptionVerbosity::Detailed){FBTParallelMemory* MyMemory = (FBTParallelMemory*)NodeMemory;Values.Add(FString::Printf(TEXT("main task: %s"), MyMemory->bMainTaskIsActive ? TEXT("active") : TEXT("not active")));if (MyMemory->bForceBackgroundTree){Values.Add(TEXT("force background tree"));}}
}#if WITH_EDITOR// 不允许中止低优先级任务
bool UBTComposite_SimpleParallel::CanAbortLowerPriority() const
{return false;
}// 不允许中止自身
bool UBTComposite_SimpleParallel::CanAbortSelf() const
{return false;
}// 获取节点图标名称
FName UBTComposite_SimpleParallel::GetNodeIconName() const
{return FName("BTEditor.Graph.BTNode.Composite.SimpleParallel.Icon");
}#endif // WITH_EDITOR
功能详细说明
SimpleParallel 节点的工作逻辑:
SimpleParallel 节点允许同时运行两个子节点:
- 主任务 (MainTask):必须是单个任务节点(可以有装饰器)
- 背景树 (BackgroundTree):可以是完整的子树
两种完成模式:
-
AbortBackground (Immediate):
- 当主任务完成时,立即中止背景树
- 适用于:移动并播放动画,移动完成就停止动画
-
WaitForBackground (Delayed):
- 当主任务完成时,等待背景树完成
- 适用于:攻击并播放特效,攻击完成后等待特效播放完毕
具体例子说明:
例子1:移动并播放动画(AbortBackground模式)
Simple Parallel (AbortBackground)
├── 移动到目标 (MoveToTarget) - 主任务
└── 播放移动动画 (PlayMoveAnimation) - 背景树
例子2:攻击并播放特效(WaitForBackground模式)
Simple Parallel (WaitForBackground)
├── 执行攻击 (PerformAttack) - 主任务
└── 播放攻击特效 (PlayAttackEffects) - 背景树
- AI 同时执行攻击和播放特效
- 攻击动作完成后,等待特效播放完毕
代码执行流程分析:
// 初始化阶段
GetNextChildHandler(SearchData, NotInitialized, 无上次结果)
→ 返回 MainTask (0)// 主任务开始执行(比如 MoveToTarget)
NotifyChildExecution(OwnerComp, NodeMemory, MainTask, InProgress)
→ 注册并行任务,请求延迟执行
→ bMainTaskIsActive = true// 背景树开始执行(比如 PlayMoveAnimation)
GetNextChildHandler(SearchData, MainTask, InProgress)
→ 返回 BackgroundTree (1)// 主任务完成(比如到达目标)
NotifyChildExecution(OwnerComp, NodeMemory, MainTask, Succeeded)
→ bMainTaskIsActive = false
→ 注销并行任务
→ 如果是AbortBackground模式,请求中止背景树// 节点停用
NotifyNodeDeactivation(SearchData, NodeResult)
→ 使用主任务结果作为最终结果
→ 移除主任务
内存管理:
struct FBTParallelMemory : public FBTCompositeMemory
{int32 LastSearchId; // 防止无限循环EBTNodeResult::Type MainTaskResult; // 主任务结果uint8 bMainTaskIsActive : 1; // 主任务是否活跃uint8 bForceBackgroundTree : 1; // 强制运行背景树uint8 bRepeatMainTask : 1; // 重复主任务
};
重要特性:
- 并行执行:主任务和背景树同时运行
- 灵活的完成策略:两种模式适应不同需求
- 防止无限循环:通过 LastSearchId 检测
- 任务注册机制:通过 RegisterParallelTask 管理并行任务
- 延迟执行:通过 RequestDelayedExecution 实现真正的并行
SimpleParallel 适用于需要同时处理多个相关行为的复杂AI场景,如边移动边搜索、边攻击边播放特效等。
