https://zhuanlan.zhihu.com/p/608864183
获取当前的 BehaviorTreeManager,每个 UWorld 中都有一个 AISystem 属性,用于 AI 管理,而 AISystem 中存储着 BehaviorTreeManager
UBehaviorTreeManager* UBehaviorTreeManager::GetCurrent(UWorld* World)
{
UAISystem* AISys = UAISystem::GetCurrentSafe(World);
return AISys ? AISys->GetBehaviorTreeManager() : nullptr;
}
在 UBehaviorTreeComponent::PushInstance 中会通过 BTManager 来加载行为树
const bool bLoaded = BTManager->LoadTree(TreeAsset, RootNode, InstanceMemorySize);
通过 LoadTree 函数或者该行为树实例所占内存大小 InstanceMemorySize 和根节点 Root
通过 UBehaviorTreeManager::LoadedTemplates 缓存加载过的 UBehaviorTree 资产信息,如果加载过直接从缓存中获取 InstanceMemorySize 和 Root
如果没有加载过该 UBehaviorTree 资产,则开始加载
由于一个
World的UBehaviorTreeManager是唯一的,所以理论上来说每个资产只会被加载一次
首先通过 InitializeNodeHelper 来缓存所有节点信息
TArray<FBehaviorTreeNodeInitializationData> InitList;
uint16 ExecutionIndex = 0;
InitializeNodeHelper(NULL, TemplateInfo.Template, 0, ExecutionIndex, InitList, Asset, this);
TemplateInfo.Template就是 根节点 的复制体
然后计算每个节点占用的内存大小,计算并设置数据内存偏移
for (int32 Index = 0; Index < InitList.Num(); Index++)
{
InitList[Index].Node->InitializeNode(InitList[Index].ParentNode, InitList[Index].ExecutionIndex, InitList[Index].SpecialDataSize + MemoryOffset, InitList[Index].TreeDepth);
MemoryOffset += InitList[Index].DataSize;
}
在运行时,会将每个节点的数据都保存在一个连续内存中,通过各自节点的 MemoryOffset 直接从连续内存中获取对应的数据
虽然函数名字是 InitializeNode,其实是 复制 + 初始化,因为函数中充斥着 StaticDuplicateObject
首先,根节点一定是 UBTCompositeNode,从上图就可以看到 Root 下只能连接且只能连接一个 UBTCompositeNode
Composite: 组合,混合
作为 UBTCompositeNode 自然保存着 子节点 和挂载在自己身上的 Serve 节点
UCLASS(Abstract, MinimalAPI)
class UBTCompositeNode : public UBTNode
{
TArray<FBTCompositeChild> Children;
TArray<TObjectPtr<UBTService>> Services;
// some function else ...
}
struct FBTCompositeChild
{
TObjectPtr<UBTCompositeNode> ChildComposite = nullptr;
TObjectPtr<UBTTaskNode> ChildTask = nullptr;
TArray<TObjectPtr<UBTDecorator>> Decorators;
TArray<FBTDecoratorLogic> DecoratorOps;
};
FBTCompositeChild代表子节点,根据ChildComposite和ChildTask的属性是否有效来判断是CompositeNode还是TaskNode
由于根节点是 UBTCompositeNode,它不能也不应该存在 UBTDecorator,所以在上图中,根节点的 UBTDecorator 是深蓝色的,表示无效
回到 InitializeNodeHelper 函数中
FBehaviorTreeInstance 用于管理一个行为树实例
通过 BTManager->LoadTree 得到 RootNode 和 InstanceMemorySize,既行为树的根节点和数据所占内存大小
FBehaviorTreeInstance& NewInstance = InstanceStack.AddDefaulted_GetRef();
NewInstance.InstanceIdIndex = UpdateInstanceId(&TreeAsset, ActiveNode, InstanceStack.Num() - 1);
NewInstance.RootNode = RootNode;
NewInstance.ActiveNode = NULL;
NewInstance.ActiveNodeType = EBTActiveNode::Composite;
// initialize memory and node instances
FBehaviorTreeInstanceId& InstanceInfo = KnownInstances[NewInstance.InstanceIdIndex];
int32 NodeInstanceIndex = InstanceInfo.FirstNodeInstance;
const bool bFirstTime = (InstanceInfo.InstanceMemory.Num() != InstanceMemorySize);
if (bFirstTime)
{
InstanceInfo.InstanceMemory.AddZeroed(InstanceMemorySize);
InstanceInfo.RootNode = RootNode;
}
NewInstance.SetInstanceMemory(InstanceInfo.InstanceMemory);
NewInstance.Initialize(*this, *RootNode, NodeInstanceIndex, bFirstTime ? EBTMemoryInit::Initialize : EBTMemoryInit::RestoreSubtree);
InstanceIdIndex是为了解决嵌套行为树执行时,方便实例识别
| 参数类型 | 属性 | 作用 |
|---|---|---|
| UBTCompositeNode* | RootNode | 行为树根节点 |
| UBTNode* | ActiveNode | 当前正在执行的节点 |
| TArray | ActiveAuxNodes | 存储当前激活的辅助节点() |
| TArray | ParallelTasks | 并行节点的多个任务 |
| TArray | InstanceMemory | 节点运行实例的数据 |
| uint8 | InstanceIdIndex | 行为树实例的ID,对应 KnowInstances 数组的唯一实例 |
| FBTInstanceDeactivation | DeactivationNotify | 当前活动节点的类型 |
| FBTInstanceDeactivation | DeactivationNotify | 子树停用时的回调委托 |
| 枚举值 | 含义 |
|---|---|
| Succeeded | 运行成功 |
| Failed | 运行失败 |
| Aborted | 被打断 |
| InProgress | 正在运行中 |
UBTTask_BlueprintBase 是最经常使用的行为树 Task 节点,用于蓝图中实现任务节点功能
在 UBTTask_BlueprintBase::ExecuteTask 函数中,会判断是否存在 AIOwner,以此判断执行 ReceiveExecuteAI 还是 ReceiveExecute
EBTNodeResult::Type UBTTask_BlueprintBase::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
CurrentCallResult = (ReceiveExecuteImplementations != 0 || ReceiveTickImplementations != 0) ? EBTNodeResult::InProgress : EBTNodeResult::Failed;
bIsAborting = false;
bStoreFinishResult = true;
// 执行 ReceiveExecuteAI 或者 ReceiveExecute
bStoreFinishResult = false;
return CurrentCallResult;
}
ExecutTask 返回的是 CurrentCallResult,这个值通常来说默认是 InProgress
如果在 ReceiveExecuteAI 接口中调用了 FinishExecute 接口,则会根据输入的 bSuccess 来重新设置 CurrentCallResult 为 Succeeded 或者 Failed
在执行节点逻辑之前,会设置 bStoreFinishResult 为 true,这样如果 Task 是同步执行结束的,就直接修改 CurrentCallResult 的值即可,直接返回运行结果
如果 Task 的执行内容是异步的,那么 ExecutTask 默认返回 InProgress,等 FinishExecute 的时候再通过 FinishLatentTask 通知 UBTComonent
void UBTTask_BlueprintBase::FinishExecute(bool bSuccess)
{
UBehaviorTreeComponent* OwnerComp = Cast<UBehaviorTreeComponent>(GetOuter());
const EBTNodeResult::Type NodeResult(bSuccess ? EBTNodeResult::Succeeded : EBTNodeResult::Failed);
if (bStoreFinishResult)
{
CurrentCallResult = NodeResult;
}
else if (OwnerComp && !bIsAborting)
{
FinishLatentTask(*OwnerComp, NodeResult);
}
}
在执行 ReceiveExecuteAI 之前 bStoreFinishResult 值为 true,也就是说如果 ReceiveExecuteAI 函数中调用了 FinishExecute 会修改 CurrentCallResult 的值,并直接作为 ExecuteTask 函数返回值返回回去
当 UBTTaskNode 执行完毕之后,通常会调用 FinishLatentTask,通知 UBTComponent 该节点运行完毕,该去查找下一个可用节点了
void UBTTaskNode::FinishLatentTask(UBehaviorTreeComponent& OwnerComp, EBTNodeResult::Type TaskResult) const
{
UBTTaskNode* TemplateNode = (UBTTaskNode*)OwnerComp.FindTemplateNode(this);
OwnerComp.OnTaskFinished(TemplateNode, TaskResult);
}