UE-AI模块
蓝图的使用
自定义Task
重载Receive Execute AI
事件,并且在Finish Execute
中勾选boolea为true,表示该Task执行成功
这里 Finish Execute
表示该Task任务执行完毕,其参数 Success
表示该Task执行是否成功,进而影响Sequence
或Selector
的执行方式
当该Task不执行 Finish Execute 时,Task会一直保持运行状态,行为树会卡在当前节点,知道执行 Finish Execute,所以当行为树卡住不往后执行时,查找一下是否是没有执行 Finish Execute
如果想要自定义在Task中直接获得blackboard
中的数据,可以定义参数 Blackboard Key Selector
,并且把右边可编辑的眼睛打开
随后就可以在行为树中的Task节点上编辑输入的黑板值
最后在Task中把设置的值转换成指定类型的参数即可
自定义Decorator
重载 PerformConditionCheckAI
函数,返回一个bool值即可
类之间的关系
AI的实体对象
- AI模块中几个类的继承关系
- UBTAuxiliaryNode:装饰器、Service
- UBTCompositeNode:Selector、Sequence、SimpleParallel
- UBTTaskNode:任务节点的基类
• AAIController::RunBehaviorTree中BehaviorTree
• BehaviorTree的子节点(Sequent、Selector、Simple Parallel)
• Blackboard
AI的组件
BehaviorTree
初始化流程
AIController
• RunBehaviorTree(UBehaviorTree* BTAsset)
bool AAIController::RunBehaviorTree(UBehaviorTree* BTAsset)
{
if (BTAsset == NULL)
{
UE_VLOG(this, LogBehaviorTree, Warning, TEXT("RunBehaviorTree: Unable to run NULL behavior tree"));
return false;
}
bool bSuccess = true;
UBlackboardComponent* BlackboardComp = Blackboard;
if (BTAsset->BlackboardAsset && (Blackboard == nullptr || Blackboard->IsCompatibleWith(BTAsset->BlackboardAsset) == false))
{
bSuccess = UseBlackboard(BTAsset->BlackboardAsset, BlackboardComp);
}
if (bSuccess)
{
UBehaviorTreeComponent* BTComp = Cast<UBehaviorTreeComponent>(BrainComponent);
if (BTComp == NULL)
{
UE_VLOG(this, LogBehaviorTree, Log, TEXT("RunBehaviorTree: spawning BehaviorTreeComponent.."));
BTComp = NewObject<UBehaviorTreeComponent>(this, TEXT("BTComponent"));
BTComp->RegisterComponent();
}
BrainComponent = BTComp;
check(BTComp != NULL);
BTComp->StartTree(*BTAsset, EBTExecutionMode::Looped);
}
return bSuccess;
}
• UseBlackboard(UBlackboardData* BlackboardAsset, UBlackboardComponent*& BlackboardComponent)
bool AAIController::UseBlackboard(UBlackboardData* BlackboardAsset, UBlackboardComponent*& BlackboardComponent)
{
if (BlackboardAsset == nullptr)
{
UE_VLOG(this, LogBehaviorTree, Log, TEXT("UseBlackboard: trying to use NULL Blackboard asset. Ignoring"));
return false;
}
bool bSuccess = true;
Blackboard = FindComponentByClass<UBlackboardComponent>();
if (Blackboard == nullptr)
{
Blackboard = NewObject<UBlackboardComponent>(this, TEXT("BlackboardComponent"));
REDIRECT_OBJECT_TO_VLOG(Blackboard, this);
if (Blackboard != nullptr)
{
InitializeBlackboard(*Blackboard, *BlackboardAsset);
Blackboard->RegisterComponent();
}
}
else if (Blackboard->GetBlackboardAsset() == nullptr)
{
InitializeBlackboard(*Blackboard, *BlackboardAsset);
}
else if (Blackboard->GetBlackboardAsset() != BlackboardAsset)
{
// @todo this behavior should be opt-out-able.
UE_VLOG(this, LogBehaviorTree, Log, TEXT("UseBlackboard: requested blackboard %s while already has %s instantiated. Forcing new BB.")
, *GetNameSafe(BlackboardAsset), *GetNameSafe(Blackboard->GetBlackboardAsset()));
InitializeBlackboard(*Blackboard, *BlackboardAsset);
}
BlackboardComponent = Blackboard;
return bSuccess;
}
• InitializeBlackboard(UBlackboardComponent& BlackboardComp, UBlackboardData& BlackboardAsset)
bool AAIController::InitializeBlackboard(UBlackboardComponent& BlackboardComp, UBlackboardData& BlackboardAsset)
{
check(BlackboardComp.GetOwner() == this);
if (BlackboardComp.InitializeBlackboard(BlackboardAsset))
{
// find the "self" key and set it to our pawn
const FBlackboard::FKey SelfKey = BlackboardAsset.GetKeyID(FBlackboard::KeySelf);
if (SelfKey != FBlackboard::InvalidKey)
{
BlackboardComp.SetValue<UBlackboardKeyType_Object>(SelfKey, GetPawn());
}
OnUsingBlackBoard(&BlackboardComp, &BlackboardAsset);
return true;
}
return false;
}
这里的代码很简单,就是调用UBlackboardComponent的InitializeBlackboard初始化函数,将传入的UBlackboardData作为初始内容
注意:这里抛出了一个OnUsingBlackBoard的BlueprintImplementableEvent类型函数,该函数可以通过蓝图重载去监听黑板被设置的事件
UBehaviorTreeComponent
• UBehaviorTreeComponent::StartTree(UBehaviorTree& Asset, EBTExecutionMode::Type ExecuteMode)
void UBehaviorTreeComponent::StartTree(UBehaviorTree& Asset, EBTExecutionMode::Type ExecuteMode /*= EBTExecutionMode::Looped*/)
{
// clear instance stack, start should always run new tree from root
UBehaviorTree* CurrentRoot = GetRootTree();
if (CurrentRoot == &Asset && TreeHasBeenStarted())
{
UE_VLOG(GetOwner(), LogBehaviorTree, Log, TEXT("Skipping behavior start request - it's already running"));
return;
}
else if (CurrentRoot)
{
UE_VLOG(GetOwner(), LogBehaviorTree, Log, TEXT("Abandoning behavior %s to start new one (%s)"),
*GetNameSafe(CurrentRoot), *Asset.GetName());
}
StopTree(EBTStopMode::Safe);
TreeStartInfo.Asset = &Asset;
TreeStartInfo.ExecuteMode = ExecuteMode;
TreeStartInfo.bPendingInitialize = true;
ProcessPendingInitialize();
}
- 判断当前准备Start的树就是当前正在运行的树,不做处理直接return
- 如果当前有正在运行的树,且与当前准备设置的树不同,则发出打印信息
- 设置TreeStartInfo属性(UBehaviorTree*、EBTExecutionMode::Type、uint8)
- 执行ProcessPendingInitialize方法
• UBehaviorTreeComponent::ProcessPendingInitialize()
void UBehaviorTreeComponent::ProcessPendingInitialize()
{
StopTree(EBTStopMode::Safe);
if (bWaitingForLatentAborts)
{
return;
}
// finish cleanup
RemoveAllInstances();
bLoopExecution = (TreeStartInfo.ExecuteMode == EBTExecutionMode::Looped);
bIsRunning = true;
#if USE_BEHAVIORTREE_DEBUGGER
DebuggerSteps.Reset();
#endif
UBehaviorTreeManager* BTManager = UBehaviorTreeManager::GetCurrent(GetWorld());
if (BTManager)
{
BTManager->AddActiveComponent(*this);
}
// push new instance
const bool bPushed = PushInstance(*TreeStartInfo.Asset);
TreeStartInfo.bPendingInitialize = false;
}
- 首先还是StopTree(EBTStopMode::Safe)停止当前行为树
- 然后就是清除所有实例RemoveAllInstances()
- 将自己注册到UBehaviorTreeManager* BTManager中BTManager->AddActiveComponent(*this);
- 添加BehaviorTree的InstanceUBehaviorTreeComponent::PushInstance
• UBehaviorTreeComponent::PushInstance(UBehaviorTree& TreeAsset)
bool UBehaviorTreeComponent::PushInstance(UBehaviorTree& TreeAsset)
{
// check if blackboard class match
if (TreeAsset.BlackboardAsset && BlackboardComp && !BlackboardComp->IsCompatibleWith(TreeAsset.BlackboardAsset))
{
UE_VLOG(GetOwner(), LogBehaviorTree, Warning, TEXT("Failed to execute tree %s: blackboard %s is not compatibile with current: %s!"),
*TreeAsset.GetName(), *GetNameSafe(TreeAsset.BlackboardAsset), *GetNameSafe(BlackboardComp->GetBlackboardAsset()));
return false;
}
UBehaviorTreeManager* BTManager = UBehaviorTreeManager::GetCurrent(GetWorld());
if (BTManager == NULL)
{
UE_VLOG(GetOwner(), LogBehaviorTree, Warning, TEXT("Failed to execute tree %s: behavior tree manager not found!"), *TreeAsset.GetName());
return false;
}
// check if parent node allows it
const UBTNode* ActiveNode = GetActiveNode();
const UBTCompositeNode* ActiveParent = ActiveNode ? ActiveNode->GetParentNode() : NULL;
if (ActiveParent)
{
uint8* ParentMemory = GetNodeMemory((UBTNode*)ActiveParent, InstanceStack.Num() - 1);
int32 ChildIdx = ActiveNode ? ActiveParent->GetChildIndex(*ActiveNode) : INDEX_NONE;
const bool bIsAllowed = ActiveParent->CanPushSubtree(*this, ParentMemory, ChildIdx);
if (!bIsAllowed)
{
UE_VLOG(GetOwner(), LogBehaviorTree, Warning, TEXT("Failed to execute tree %s: parent of active node does not allow it! (%s)"),
*TreeAsset.GetName(), *UBehaviorTreeTypes::DescribeNodeHelper(ActiveParent));
return false;
}
}
UBTCompositeNode* RootNode = NULL;
uint16 InstanceMemorySize = 0;
const bool bLoaded = BTManager->LoadTree(TreeAsset, RootNode, InstanceMemorySize);
if (bLoaded)
{
FBehaviorTreeInstance NewInstance;
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);
InstanceStack.Push(NewInstance);
ActiveInstanceIdx = InstanceStack.Num() - 1;
// start root level services now (they won't be removed on looping tree anyway)
for (int32 ServiceIndex = 0; ServiceIndex < RootNode->Services.Num(); ServiceIndex++)
{
UBTService* ServiceNode = RootNode->Services[ServiceIndex];
uint8* NodeMemory = (uint8*)ServiceNode->GetNodeMemory<uint8>(InstanceStack[ActiveInstanceIdx]);
// send initial on search start events in case someone is using them for init logic
ServiceNode->NotifyParentActivation(SearchData);
InstanceStack[ActiveInstanceIdx].AddToActiveAuxNodes(ServiceNode);
ServiceNode->WrappedOnBecomeRelevant(*this, NodeMemory);
}
FBehaviorTreeDelegates::OnTreeStarted.Broadcast(*this, TreeAsset);
// start new task
RequestExecution(RootNode, ActiveInstanceIdx, RootNode, 0, EBTNodeResult::InProgress);
return true;
}
return false;
}
- 前面看不懂,ActiveParent的作用是啥
- BTManager->LoadTree(TreeAsset, RootNode, InstanceMemorySize)加载行为树
TickComponent
从上述代码中可以发现:BrainComponen是一个指向UBehaviorTreeComponent的智能指针,所以行为树的真正执行只用查看UBehaviorTreeComponent::TickComponent的代码即可