行為樹Composite節點說明
Selector
//BTComposite_Selector.cpp
/**
* 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.
*/
int32 UBTComposite_Selector::GetNextChildHandler(FBehaviorTreeSearchData& SearchData, int32 PrevChild, EBTNodeResult::Type LastResult) const
{
// success = quit
int32 NextChildIdx = BTSpecialChild::ReturnToParent;
if (PrevChild == BTSpecialChild::NotInitialized)
{
// newly activated: start from first
NextChildIdx = 0;
}
else if (LastResult == EBTNodeResult::Failed && (PrevChild + 1) < GetChildrenNum())
{
// failed = choose next child
NextChildIdx = PrevChild + 1;
}
return NextChildIdx;
}
- 如Selector有節點A(BTTask_Patrol)跟節點B(BTTask_MoveRandomLocation),如果節點A返回Succeeded,則Selector會馬上返回Succeeded,不會執行節點B的行為
- 換言之就是在第一個子節點返回Succeeded時,Selector也返回,否則會一直執行,直到所有節點都返回Failed時,自身(Selector)返回Failed
Sequence
//BTComposite_Sequence.cpp
/**
* 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.
*/
int32 UBTComposite_Sequence::GetNextChildHandler(FBehaviorTreeSearchData& SearchData, int32 PrevChild, EBTNodeResult::Type LastResult) const
{
// failure = quit
int32 NextChildIdx = BTSpecialChild::ReturnToParent;
if (PrevChild == BTSpecialChild::NotInitialized)
{
// newly activated: start from first
NextChildIdx = 0;
}
else if (LastResult == EBTNodeResult::Succeeded && (PrevChild + 1) < GetChildrenNum())
{
// success = choose next child
NextChildIdx = PrevChild + 1;
}
return NextChildIdx;
}
- 基本是Selector的相反
- 如Sequence有上述三個節點,從左至右分別為(A、B、C),如果節點A返回Failed,則Sequence會馬上返回Failed,不會執行節點B及C的行為
- 換言之就是在第一個子節點返回Failed時,Sequence也返回,否則會一直執行,直到所有節點都返回Succeeded時,自身(Sequence)返回Succeeded
Simple Parallel
- 主任務必須為一個單獨的Task節點
- 副任務則可以是一個Sequence或者其他組合節點
- FinishMode
- Immediate => 當Main Task執行完成,馬上打斷background tree並返回
- Delayed => Main Task執行完成,等待background tree也執行完成才返回
//BTComposite_SimpleParallel.cpp
/**
* 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.
*/
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())
{
// if main task is running - always pick background tree
// unless search is from abort from background tree - return to parent (and break from search when node gets deactivated)
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))
{
// retrying the same branch again within the same search - possible infinite loop
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)
{
// can't register if task is currently being aborted (latent abort returns in progress)
MyMemory->bMainTaskIsActive = true;
MyMemory->bForceBackgroundTree = false;
OwnerComp.RegisterParallelTask(Children[EBTParallelChild::MainTask].ChildTask);
RequestDelayedExecution(OwnerComp, EBTNodeResult::Succeeded);
}
}
else if (MyMemory->bMainTaskIsActive)
{
MyMemory->bMainTaskIsActive = false;
// notify decorators on main task, ignore observers updates in FakeSearchData - they are not allowed by parallel composite
FBehaviorTreeSearchData FakeSearchData(OwnerComp);
NotifyDecoratorsOnDeactivation(FakeSearchData, ChildIdx, NodeResult, true /*bIsRequestInSameInstance*/);
const int32 MyInstanceIdx = OwnerComp.FindInstanceContainingNode(this);
// use check() here not IntCastChecked() as MyInstanceIdx can be INDEX_NONE!
check(MyInstanceIdx < MAX_uint16);
OwnerComp.UnregisterParallelTask(Children[EBTParallelChild::MainTask].ChildTask, static_cast<uint16>(MyInstanceIdx));
if (NodeResult != EBTNodeResult::Aborted && !MyMemory->bRepeatMainTask)
{
// check if subtree should be aborted when task finished with success/failed result
if (FinishMode == EBTParallelMode::AbortBackground)
{
OwnerComp.RequestExecution((UBTCompositeNode*)this, MyInstanceIdx,
Children[EBTParallelChild::MainTask].ChildTask, EBTParallelChild::MainTask,
NodeResult);
}
}
}
else if (NodeResult == EBTNodeResult::Succeeded && FinishMode == EBTParallelMode::WaitForBackground)
{
// special case: if main task finished instantly, but composite is supposed to wait for background tree,
// request single run of background tree anyway
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();
// modify node result if main task has finished
if (!MyMemory->bMainTaskIsActive)
{
NodeResult = MyMemory->MainTaskResult;
}
// remove main task
if (Children.IsValidIndex(EBTParallelChild::MainTask))
{
SearchData.AddUniqueUpdate(FBehaviorTreeSearchUpdate(Children[EBTParallelChild::MainTask].ChildTask, ActiveInstanceIdx, EBTNodeUpdateMode::Remove));
}
}
- 首次進入Simple Parallel時,會選中Main Task節點並開始執行;而當Main Task開始執行後,Simple Parallel就會持續選中background tree的節點並執行
- Main Task執行完成後,Simple Parallel節點會收到通知,然後Simple Parallel會判斷:
- Main Task執行途中被打斷
- 標記mainTaskIsActive(main task is running)為false
- 把Main Task從并行執行列表中取消注冊
- 如果Finish Mode為 AbortBackground(立即打斷),馬上發出一次執行Main Task節點的請求,完成後會重新嘗試GetNextChild,但是由於mainTaskIsActive和forceBackgroundTree都為false,因此整支節點直接返回
- Main Task執行成功且Finish Mode設為Delayed(FinishMode == WaitForBackground)
- 標記需要等forceBackgroundTree(是否延遲完成)為true,使Parallel在GetNextChildHandler時,即使Main Task已經執行完畢,仍然會獲取到Background Task作為下個執行的節點
- 最後返回Main Task的返回結果
- Main Task執行途中被打斷
- Main Task執行完成後,Simple Parallel節點會收到通知,然後Simple Parallel會判斷:
- 目前官方更推薦使用Services節點取代Simple Parallel來做并行