当前位置: 首页 > news >正文

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. 情况1:移动到玩家成功

    • 执行 MoveToPlayer → 成功
    • Selector 立即返回成功,不执行 FindWeaponPatrol
  2. 情况2:移动到玩家失败,寻找武器成功

    • 执行 MoveToPlayer → 失败 → 执行 FindWeapon → 成功
    • Selector 返回成功,不执行 Patrol
  3. 情况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. 情况1:完整序列成功

    • 执行 CheckLineOfSight → 成功
    • 执行 AimAtTarget → 成功
    • 执行 FireWeapon → 成功
    • Sequence 返回成功
  2. 情况2:中间节点失败

    • 执行 CheckLineOfSight → 成功
    • 执行 AimAtTarget → 失败(目标移动太快)
    • Sequence 立即返回失败,不执行 FireWeapon
  3. 情况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):可以是完整的子树

两种完成模式:

  1. AbortBackground (Immediate):

    • 当主任务完成时,立即中止背景树
    • 适用于:移动并播放动画,移动完成就停止动画
  2. 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;             // 重复主任务
};

重要特性:

  1. 并行执行:主任务和背景树同时运行
  2. 灵活的完成策略:两种模式适应不同需求
  3. 防止无限循环:通过 LastSearchId 检测
  4. 任务注册机制:通过 RegisterParallelTask 管理并行任务
  5. 延迟执行:通过 RequestDelayedExecution 实现真正的并行

SimpleParallel 适用于需要同时处理多个相关行为的复杂AI场景,如边移动边搜索、边攻击边播放特效等。

http://www.dtcms.com/a/574326.html

相关文章:

  • 做婚庆网站安徽元鼎建设工程网站
  • 建设银行注册网站首页php网站服务器搭建
  • 织梦网站如何转百度小程序同步网络营销策略和方法
  • UE5 C++ Slate 画曲线
  • 【机器学习15】强化学习入门、Q-Learning、贝尔曼方程
  • 解释seo网站推广北京十大科技公司
  • 基于电鱼 AI 工控机的塔吊与升降机安全监测方案——实时采集传感器数据,AI智能判断异常并报警
  • 如何看一个网站用什么程序做的南开做网站
  • 京东云双11活动-云产品特惠热卖中
  • 企业网站如何seowordpress touch
  • 怎样进行网站板块建设支付宝网站登录入口
  • Harbor 私有镜像仓库安装教程
  • 批发/贸易企业数字化转型:PHP开发的B2B订货系统
  • ACMMM2025 |TGSI+SATL:不改模型架构也能提升预测性能,破解几何结构评估与建模难题!
  • 【笔记】Windows系统安装SAM-2(Segment Anything Model 2)
  • 宁波网站制作报价主题设置wordpress
  • 嵌入式Linux安全启动全解析:从原理到实战
  • wordpress培训类网站模板厦门软件开发培训机构
  • 16000+字!Java集合笔记
  • 中国免费域名申请网站色彩搭配的网站
  • 传播网站建设龙泉驿最新消息
  • 2.2 python中带参数的装饰器与类装饰器
  • Java程序导致CPU打满如何排查
  • 建设网站天河区.net网站开发流程
  • 海外设计网站建设网站怎样运营
  • 两学一做微网站交流扬州网站建设费用
  • 网站开发 xmind长春建站网站模板
  • WebView 调试工具全解析,解决“看不见的移动端问题”
  • 论坛网站建设流程wordpress 页面压缩
  • 如果做京东优惠卷的网站七台河新闻联播视频