UE4 HTN插件源码解析

20 篇文章 13 订阅
本文详细阐述了UHTN节点、UHTNStandaloneNode、UHTNTask、UHTNService和UHTNDecorator在人工智能任务规划中的关键角色,包括节点初始化、最小代价算法应用、计划制定、执行流程和数据管理。通过堆优化算法实现高效路径搜索,展示了如何创建、扩展和执行计划,以及世界状态的管理和装饰器条件检查。
摘要由CSDN通过智能技术生成

文章目录

节点部分

UHTNNode

初始化:

// 节点名称
NodeName = TEXT("xxxx");
// 是否实例化
bCreateNodeInstance = true

静态描述:

virtual FString GetStaticDescription() const override;

NodeMemory

virtual uint16 GetInstanceMemorySize() const override;

FHTNService_XXXMemory* Memory = (FHTNService_XXXMemory*)NodeMemory;

初始化NodeMemory

virtual void InitializeMemory(UHTNComponent& OwnerComp, uint8* NodeMemory, const FHTNPlan& Plan, const FHTNPlanStepID& StepID) const override;

FHTNService_XXXMemory* Memory = (FHTNService_XXXMemory*)NodeMemory;
*Memory = {};
Memory->XXX = XXX

UHTNStandaloneNode:UHTNNode

可独立存在的节点基类

// The maximum number of times this task can be present in a single plan. 0 means no limit.
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Planning, Meta = (ClampMin = "0"))
int32 MaxRecursionLimit;

// Nodes that this node connects to with outgoing arrows.
UPROPERTY()
TArray<UHTNStandaloneNode*> NextNodes;

UPROPERTY()
TArray<class UHTNDecorator*> Decorators;

UPROPERTY()
TArray<class UHTNService*> Services;

UHTNTask:UHTNStandaloneNode

初始化:

UHTNTask_XXX(const FObjectInitializer& Initializer);

(const FObjectInitializer& Initializer): Super(Initializer)

// 添加Filter
BlackboardKey.AddObjectFilter(this, GET_MEMBER_NAME_CHECKED(UHTNTask_XXX, BlackboardKey), AActor::StaticClass());
BlackboardKey.AddVectorFilter(this, GET_MEMBER_NAME_CHECKED(UHTNTask_XXX, BlackboardKey));

创建计划步骤:

virtual void CreatePlanSteps(UHTNComponent& OwnerComp, UAITask_MakeHTNPlan& PlanningTask, const TSharedRef<const FBlackboardWorldState>& WorldState) const override;

// 创建worldstate的拷贝
const TSharedRef<FBlackboardWorldState> NewWorldState = WorldState->MakeNext();
// 修改worldstate
NewWorldState->SetValue<UBlackboardKeyType_Name>(TEXT("BBKeyName"), Value);
// 提交一个步骤
PlanningTask.SubmitPlanStep(this, WorldStateAfterTask, cost);
  • 调用链:
    void UAITask_MakeHTNPlan::FinishLatentCreatePlanSteps(const UHTNTask* Task) | void UAITask_MakeHTNPlan::Activate()
    void UAITask_MakeHTNPlan::DoPlanning()
    void UAITask_MakeHTNPlan::MakeExpansionsOfCurrentPlan(const TSharedPtr<FBlackboardWorldState>& WorldState, UHTNStandaloneNode* Node)
    void UHTNTask::CreatePlanSteps(UHTNComponent& OwnerComp, UAITask_MakeHTNPlan& PlanningTask, const TSharedRef<const FBlackboardWorldState>& WorldState) const
    

执行时调用:

virtual EHTNNodeResult ExecuteTask(UHTNComponent& OwnerComp, uint8* NodeMemory, const FHTNPlanStepID& PlanStepID) override;

// 延时结束 | 执行成功 | 执行失败
return EHTNNodeResult::InProgress | Succeeded | Failed;
  • 调用链:
    void UHTNComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
    void UHTNComponent::TickCurrentPlan(float DeltaTime)
    void UHTNComponent::StartTasksPendingExecution()
    EHTNNodeResult UHTNComponent::StartExecuteTask(const FHTNPlanStepID& PlanStepID)
    EHTNNodeResult UHTNTask::WrappedExecuteTask(UHTNComponent& OwnerComp, uint8* NodeMemory, const FHTNPlanStepID& PlanStepID) const
    EHTNNodeResult ExecuteTask(UHTNComponent& OwnerComp, uint8* NodeMemory, const FHTNPlanStepID& PlanStepID)
    

激活时Tick:

bNotifyTick = true;
virtual void TickTask(UHTNComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;

// 结束Tick:执行成功 | 执行失败 | 中止
FinishLatentTask(OwnerComp, EHTNNodeResult::Succeeded | Failed | Aborted);

任务结束时:

bNotifyTaskFinished = true
virtual void OnTaskFinished(UHTNComponent& OwnerComp, uint8* NodeMemory, EHTNNodeResult TaskResult) override;

// 可以判断是否被中止
if(TaskResult == EHTNNodeResult::Aborted)

任务中止时:

virtual EHTNNodeResult AbortTask(UHTNComponent& OwnerComp, uint8* NodeMemory) override;

// 延时中止 | 成功中止
return EHTNNodeResult::InProgress | Aborted;

重检:

virtual bool RecheckPlan(UHTNComponent& OwnerComp, uint8* NodeMemory, const FBlackboardWorldState& WorldState, const FHTNPlanStep& SubmittedPlanStep) override;

// 重检成功 | 失败
return true | false

UHTNService:UHTNNode

UPROPERTY(EditAnywhere, Category = Service, Meta = (ClampMin = "0.001"))
float TickInterval;

UPROPERTY(EditAnywhere, Category = Service, Meta = (ClampMin = "0.0"))
float TickIntervalRandomDeviation;

bool bNotifyExecutionStart : 1;
bool bNotifyTick : 1;
bool bNotifyExecutionFinish : 1;

virtual void OnExecutionStart(UHTNComponent& OwnerComp, uint8* NodeMemory) override;
virtual void TickNode(UHTNComponent& OwnerComp, uint8* NodeMemory, float DeltaTime) override;
virtual void OnExecutionFinish(UHTNComponent& OwnerComp, uint8* NodeMemory, EHTNNodeResult Result) override;

UHTNDecorator:UHTNNode

检测函数:

virtual bool CalculateRawConditionValue(UHTNComponent& OwnerComp, uint8* NodeMemory, EHTNDecoratorConditionCheckType CheckType) const override;

// 获取Worldstate
UWorldStateProxy* const WorldStateProxy = GetWorldStateProxy(OwnerComp, CheckType);
FName Name = WorldStateProxy->GetValueAsName(TEXT("Name"));
  • Recheck调用链
void UHTNComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
bool UHTNComponent::RecheckCurrentPlan()
bool UHTNTask::WrappedRecheckPlan(UHTNComponent& OwnerComp, uint8* NodeMemory, const FBlackboardWorldState& WorldState, const FHTNPlanStep& SubmittedPlanStep)
bool UHTNTask::RecheckPlan(UHTNComponent& OwnerComp, uint8* NodeMemory, const FBlackboardWorldState& WorldState, const FHTNPlanStep& SubmittedPlanStep)

Notify:

virtual void OnEnterPlan(UHTNComponent& OwnerComp, const FHTNPlan& Plan, const FHTNPlanStepID& StepID) const override;
virtual void OnExitPlan(UHTNComponent& OwnerComp, const FHTNPlan& Plan, const FHTNPlanStepID& StepID) const override;
virtual void OnExecutionStart(UHTNComponent& OwnerComp, uint8* NodeMemory) override;
virtual void TickNode(UHTNComponent& OwnerComp, uint8* NodeMemory, float DeltaTime) override;
virtual void OnExecutionFinish(UHTNComponent& OwnerComp, uint8* NodeMemory, EHTNNodeResult Result) override;

bool bNotifyOnEnterPlan : 1;
bool bNotifyOnExitPlan : 1;
bool bNotifyExecutionStart : 1;
bool bNotifyTick : 1;
bool bNotifyExecutionFinish : 1;

修改Cost:

virtual void ModifyStepCost(UHTNComponent& OwnerComp, FHTNPlanStep& Step) const override;

bool bModifyStepCost : 1;

Step.Cost = x;

检测

EHTNDecoratorTestResult UHTNDecorator::TestCondition(UHTNComponent& OwnerComp, uint8* NodeMemory, EHTNDecoratorConditionCheckType CheckType) const
{
	// 判断是否需要检测(PlanEnter、PlanExit、PlanRecheck、Tick是否打勾)
	if (!ShouldCheckCondition(OwnerComp, NodeMemory, CheckType))
	{
		return EHTNDecoratorTestResult::NotTested;
	}
	// 调用检测函数
	const bool bRawValue = CalculateRawConditionValue(OwnerComp, NodeMemory, CheckType);
	const bool bEffectiveValue = bInverseCondition ? !bRawValue : bRawValue;
	return bEffectiveValue ? EHTNDecoratorTestResult::Passed : EHTNDecoratorTestResult::Failed;
}

计划制定部分

总结

最小代价算法

UAITask_MakeHTNPlan内部有一个TArray<TSharedPtr<FHTNPlan>> Frontier,通过HeapPushHeapPop维护小顶堆,每次都能取出代价最小的Plan进行拓展。

ACM入门算法,学名堆优化BFS,需要保证步长(即代价)非负。

由于每次选择最小代价方案,且代价非负,当堆首方案成功完成时,一定是所有方案中代价最小的之一(可能有其它相同的),也就是DoPlanning中的Planning succeeded

拓展部分

  • 先获取OutPlanStepID,也就是从没有完成的level中选择一个最深的level往下,这个用来确认下一个需要拓展的节点是哪个
    在这里插入图片描述

  • 这里涉及到了一个Plan的架构

    • Plan有多个Level(前后层级),每个Level有多个Step,每个复合节点的每个有效分支都会创建一个新的Level
    • FHTNPlanStepID的LevelIndex就是所处Level的下标,而StepIndex就是这个Step在所处Level内的下标
  • 某个接口连接多个节点时,就会创建多个Plan,这些Plan存到Frontier内进行候选

  • 拓展过程中会有装饰器的EnterPlanCheck、ExitPlanCheck等

0 - 新建AITask:UAITask_MakeHTNPlan

bool UHTNBlueprintLibrary::RunHTN(AAIController* AIController, UHTN* HTNAsset)
{
	...
	HTNComponent->StartHTN(HTNAsset);
}

void UHTNComponent::StartHTN(UHTN* Asset)
{
	...
	StartPendingHTN();
}

void UHTNComponent::StartPendingHTN()
{
	...
	StartPlanningTask();
}

void UHTNComponent::StartPlanningTask(bool bDeferToNextFrame)
{
	...
	CurrentPlanningTask = UAITask::NewAITask<UAITask_MakeHTNPlan>(*AIOwner, *this, TEXT("Make HTN Plan"));
	CurrentPlanningTask->SetUp(this, CurrentHTNAsset);
	CurrentPlanningTask->ReadyForActivation();
}

1 - UAITask_MakeHTNPlan::Activate

void UAITask_MakeHTNPlan::Activate()

// 从BlackboardComponent创建worldstate
const TSharedRef<FBlackboardWorldState> WorldStateAtPlanStart = MakeShared<FBlackboardWorldState>(*BlackboardComponent);
// 和HTN一起,创建一个初始HTNPlan,塞到Frontier,这是一个堆,会自动将Cost小的Plan置顶
Frontier.HeapPush(MakeShared<FHTNPlan>(TopLevelHTN, WorldStateAtPlanStart), FComparePlanCosts());
// 开始制定计划
DoPlanning();

2- DoPlanning:开始制定计划

void UAITask_MakeHTNPlan::DoPlanning()
{
	SCOPE_CYCLE_COUNTER(STAT_AI_HTN_Planning);
	
	check(!FinishedPlan.IsValid());
	
	// 主循环,使用小顶堆,每次弹出代价最小的Plan进行拓展
	while (!bIsWaitingForTaskToProducePlanSteps)
	{
		if (!CurrentPlanToExpand.IsValid())
		{
			// 出队cost最小的那个
			CurrentPlanToExpand = DequeueCurrentBestPlan();
			if (!CurrentPlanToExpand.IsValid())
			{
				// Planning failed
				EndTask();
				return;
			}
			
			if (CurrentPlanToExpand->IsComplete())
			{
				// Planning succeeded
				FinishedPlan = CurrentPlanToExpand;
				EndTask();
				return;
			}
		}
		// 开始拓展
		MakeExpansionsOfCurrentPlan();
	}
}

2.1 - DequeueCurrentBestPlan:弹出代价最小的Plan

TSharedPtr<FHTNPlan> UAITask_MakeHTNPlan::DequeueCurrentBestPlan()
{
	if (Frontier.Num())
	{
		TSharedPtr<FHTNPlan> Plan;
		Frontier.HeapPop(Plan, FComparePlanCosts());
		check(Plan.IsValid());

		// TODO make the MaxPlanLength a config var on the htn component.
		if (GetTotalNumSteps(*Plan) > 100)
		{
			UE_VLOG(OwnerComponent->GetOwner(), LogHTN, Error, TEXT("Max plan length exceeded, planning failed"));
			return nullptr;
		}

		return Plan;
	}

	return nullptr;
}

3 - MakeExpansionsOfCurrentPlan():拓展入口

void UAITask_MakeHTNPlan::MakeExpansionsOfCurrentPlan()
{
	check(CurrentPlanToExpand.IsValid());

	if (CurrentPlanStepID == FHTNPlanStepID::None)
	{
		// 获取OutPlanStepID
		const bool bSuccess = CurrentPlanToExpand->FindStepToAddAfter(CurrentPlanStepID);
		check(bSuccess);
		check(NextNodesIndex == 0);
	}
	check(CurrentPlanToExpand->HasLevel(CurrentPlanStepID.LevelIndex));

	TSharedPtr<FBlackboardWorldState> WorldState;
	TArrayView<UHTNStandaloneNode*> NextNodes;
	// 获取下一步的WorldState和Nodes
	CurrentPlanToExpand->GetWorldStateAndNextNodes(CurrentPlanStepID, WorldState, NextNodes);
	check(WorldState.IsValid());
	
	check(NextNodes.IsValidIndex(NextNodesIndex) || NextNodesIndex == NextNodes.Num());
	for (; NextNodesIndex < NextNodes.Num(); ++NextNodesIndex)
	{
		UHTNStandaloneNode* const Node = NextNodes[NextNodesIndex];
		if (Node->MaxRecursionLimit > 0 && CurrentPlanToExpand->GetRecursionCount(Node) >= Node->MaxRecursionLimit)
		{
			continue;
		}
		// 使用下一层的节点,拓展Plan
		MakeExpansionsOfCurrentPlan(WorldState, Node);
		if (bIsWaitingForTaskToProducePlanSteps || FinishedPlan.IsValid())
		{
			break;
		}
	}

	if (!bIsWaitingForTaskToProducePlanSteps)
	{
		ClearIntermediateState();
	}
	else
	{
		UE_VLOG_UELOG(OwnerComponent->GetOwner(), LogHTN, VeryVerbose, TEXT("Planning task %s is waiting for task \"%s\" to produce plan steps.\nRecorded planspace traversal so far:\n(Note that results may be misleading if the visual logger wasn't recording for the entire duration of planning)\n%s"),
			*GetName(),
			CurrentTask ? *CurrentTask->GetNodeName() : TEXT("[missing task]"),
			*DebugInfo.ToString()
		);
	}
}

3.1 - FindStepToAddAfter:获取OutPlanStepID

bool FHTNPlan::FindStepToAddAfter(FHTNPlanStepID& OutPlanStepID) const
{
	bool bIncompleteLevelSkippedBecauseWorldStateNotSet = false;
	// 构造函数内创了一个:Levels { MakeShared<FHTNPlanLevel>(HTNAsset, WorldStateAtPlanStart) }
	// Find an incomplete level, starting with the deepest ones.
	// 从没有完成的level中选择一个最深的level往下
	for (int32 LevelIndex = Levels.Num() - 1; LevelIndex >= 0; --LevelIndex)
	{
		if (!IsLevelComplete(LevelIndex))
		{
			const FHTNPlanLevel& Level = *Levels[LevelIndex];
			if (Level.WorldStateAtLevelStart.IsValid())
			{
				OutPlanStepID = { LevelIndex, Level.Steps.Num() ? Level.Steps.Num() - 1 : INDEX_NONE };
				return true;
			}
			else
			{
				bIncompleteLevelSkippedBecauseWorldStateNotSet = true;
			}
		}
	}

	ensureMsgf(!bIncompleteLevelSkippedBecauseWorldStateNotSet, TEXT("The only remaining incomplete plan levels don't have a worldstate set!"));
	OutPlanStepID = FHTNPlanStepID::None;
	return false;
}

3.2 - GetWorldStateAndNextNodes:获取下一步的WorldState和Nodes

void FHTNPlan::GetWorldStateAndNextNodes(const FHTNPlanStepID& StepID, TSharedPtr<FBlackboardWorldState>& OutWorldState, TArrayView<UHTNStandaloneNode*>& OutNextNodes) const
{
	const FHTNPlanLevel& Level = *Levels[StepID.LevelIndex];

	// The beginning of a level
	if (StepID.StepIndex == INDEX_NONE)
	{
		check(Level.WorldStateAtLevelStart.IsValid());
		check(Level.HTNAsset.IsValid());

		OutWorldState = Level.WorldStateAtLevelStart;

		if (!Level.IsInlineLevel())
		{
			OutNextNodes = Level.HTNAsset->StartNodes;
		}
		else
		{
			const FHTNPlanStep& ParentPlanStep = GetStep(Level.ParentStepID);
			if (UHTNNode_TwoBranches* const TwoBranchesNode = Cast<UHTNNode_TwoBranches>(ParentPlanStep.Node))
			{
				const bool bIsPrimaryBranch = StepID.LevelIndex == ParentPlanStep.SubLevelIndex;
				const bool bEffectivePrimaryBranch = ParentPlanStep.bAnyOrderInversed ? !bIsPrimaryBranch : bIsPrimaryBranch;
				OutNextNodes = bEffectivePrimaryBranch ? TwoBranchesNode->GetPrimaryNextNodes() : TwoBranchesNode->GetSecondaryNextNodes();
			}
			else if (UHTNStandaloneNode* const StandaloneNode = Cast<UHTNStandaloneNode>(ParentPlanStep.Node))
			{
				// 单分支节点,直接获取NextNodes
				OutNextNodes = StandaloneNode->NextNodes;
			}
			else
			{
				checkNoEntry();
			}
		}
	}
	// If given a valid step, just return the WS and NextNodes of that step
	else
	{
		check(Level.Steps.IsValidIndex(StepID.StepIndex));
		const FHTNPlanStep& StepToAddAfter = Level.Steps[StepID.StepIndex];
		check(StepToAddAfter.Node.IsValid());
		check(StepToAddAfter.WorldState.IsValid());

		OutWorldState = StepToAddAfter.WorldState;
		OutNextNodes = StepToAddAfter.Node->NextNodes;
	}
}

4 - MakeExpansionsOfCurrentPlan(const TSharedPtr& WorldState, UHTNStandaloneNode* Node):使用下一层的节点,拓展Plan

void UAITask_MakeHTNPlan::MakeExpansionsOfCurrentPlan(const TSharedPtr<FBlackboardWorldState>& WorldState, UHTNStandaloneNode* Node)
{
	check(CurrentPlanToExpand.IsValid());
	check(Node);
	check(OwnerComponent);
	// Initialize the node with asset if hasn't been initialized with an asset already.
	// This is to make sure that blackboard keys are resolved etc before planning reaches the node.
	Node->InitializeFromAsset(*TopLevelHTN);

	SET_NODE_FAILURE_REASON(TEXT(""));
	
	// 判断能否通过装饰器
	const bool bDecoratorsPassed = EnterDecorators(*CurrentPlanToExpand, CurrentPlanStepID, *WorldState, Node, WorldStateAfterEnteredDecorators);
	// 装饰器不通过时,只有IF可以继续往下
	if (!WorldStateAfterEnteredDecorators.IsValid() || (!bDecoratorsPassed && !Node->IsA(UHTNNode_If::StaticClass())))
	{
		SAVE_PLANNING_STEP_FAILURE(CurrentPlanToExpand, Node, NodePlanningFailureReason);
		return;
	}
	// 是否产生了plan
	const auto DidProduceAnyPlans = [&, NumPlansBefore = Frontier.Num()]() { return Frontier.Num() > NumPlansBefore; };
			
	CurrentTask = Cast<UHTNTask>(Node);
	// Adding primitive task. Make as many new plans as there are possible ways to perform the task.
	// 原始任务
	if (CurrentTask)
	{
		PossibleStepsBuffer.Reset();
		check(!bIsWaitingForTaskToProducePlanSteps);
		CurrentTask->CreatePlanSteps(*OwnerComponent, *this, WorldStateAfterEnteredDecorators.ToSharedRef());
		if (!bIsWaitingForTaskToProducePlanSteps)
		{
			OnTaskFinishedProducingCandidateSteps(CurrentTask);
		}
	}
	// 复合任务
	else
	{
		FHTNPlanningContext PlanningContext(this, Node,
			CurrentPlanToExpand, CurrentPlanStepID, 
			WorldStateAfterEnteredDecorators, bDecoratorsPassed
		);
		// 拓展
		Node->MakePlanExpansions(PlanningContext);
	}

#if ENABLE_VISUAL_LOG
	if (!bIsWaitingForTaskToProducePlanSteps && !DidProduceAnyPlans())
	{
		SAVE_PLANNING_STEP_FAILURE(CurrentPlanToExpand, Node, NodePlanningFailureReason.IsEmpty() ? TEXT("Failed to produce any plan steps") : NodePlanningFailureReason);
	}
#endif
}

4.1 - EnterDecorators:判断能否通过装饰器

bool UAITask_MakeHTNPlan::EnterDecorators(const TArrayView<UHTNDecorator*>& Decorators, const FHTNPlan& Plan, const FHTNPlanStepID& StepID) const
{
	for (UHTNDecorator* const Decorator : Decorators)
	{
		if (ensure(Decorator))
		{
			// 使用TestCondition调用CalculateRawConditionValue(这里判断bCheckConditionOnPlanEnter是否勾选)
			const bool bPassedDecorator = Decorator->WrappedEnterPlan(*OwnerComponent, Plan, StepID);
			if (!bPassedDecorator)
			{
				SET_NODE_FAILURE_REASON(FString::Printf(TEXT("Failed decorator %s"), *Decorator->GetNodeName()));
				return false;
			}
		}
	}

	return true;
}

5 - CreatePlanSteps:提交一步

PlanningTask.SubmitPlanStep(this, NewWorldState, cost);

void UAITask_MakeHTNPlan::SubmitPlanStep(const UHTNTask* Task, TSharedPtr<FBlackboardWorldState> WorldState, int32 Cost)
{
	if (ensure(CurrentTask && Task == CurrentTask))
	{
		PossibleStepsBuffer.Emplace(CurrentTask, WorldState, Cost);
	}
}

6 - OnTaskFinishedProducingCandidateSteps(CurrentTask):将当前步加到Plan内提交

void UAITask_MakeHTNPlan::OnTaskFinishedProducingCandidateSteps(UHTNTask* Task)
{
	if (!ensure(Task == CurrentTask))
	{
		return;
	}
	// 遍历这个任务创建的所有FHTNPlanStep
	for (FHTNPlanStep& Step : PossibleStepsBuffer)
	{
		check(Step.Node == CurrentTask);
		check(Step.WorldState.IsValid());

		if (Step.Cost < 0)
		{
			UE_VLOG_UELOG(OwnerComponent->GetOwner(), LogHTN, Warning, TEXT("Plan step given by %s is %i. Negative costs aren't allowed, resetting to zero."), *Step.Node->GetNodeName(), Step.Cost);
			Step.Cost = 0;
		}
		
		Step.WorldStateAfterEnteringDecorators = WorldStateAfterEnteredDecorators;
		// 通过装饰器修改Cost
		ModifyStepCost(Step, Task->Decorators);

		// Make a new plan with this step added in the appropriate level.
		const TSharedRef<FHTNPlan> NewPlan = CurrentPlanToExpand->MakeCopy(CurrentPlanStepID.LevelIndex);
		FHTNPlanLevel& LevelInNewPlan = *NewPlan->Levels[CurrentPlanStepID.LevelIndex];
		LevelInNewPlan.Steps.Add(Step);
		LevelInNewPlan.Cost += Step.Cost;
		NewPlan->Cost += Step.Cost;
		if (Task->MaxRecursionLimit > 0)
		{
			NewPlan->IncrementRecursionCount(Task);
		}

		if (ExitDecoratorsAndPropagateWorldState(*NewPlan, {CurrentPlanStepID.LevelIndex, CurrentPlanStepID.StepIndex + 1}))
		{
			// 提交
			SubmitCandidatePlan(NewPlan, Task);
		}
	}

	PossibleStepsBuffer.Reset();
	CurrentTask = nullptr;
}

5 - MakePlanExpansions(FHTNPlanningContext& Context):编写拓展复合节点的规则

void UHTNNode_Scope::MakePlanExpansions(FHTNPlanningContext& Context)
{
	FHTNPlanStep* AddedStep = nullptr;
	FHTNPlanStepID AddedStepID;
	const TSharedRef<FHTNPlan> NewPlan = Context.MakePlanCopyWithAddedStep(AddedStep, AddedStepID);
	// 创建新Level
	AddedStep->SubLevelIndex = NextNodes.Num() ?
		Context.AddInlineLevel(*NewPlan, AddedStepID) :
		INDEX_NONE;
	Context.SubmitCandidatePlan(NewPlan);
}

5.1 - UHTNNode_If::MakePlanExpansions(FHTNPlanningContext& Context):IF节点规则

void UHTNNode_If::MakePlanExpansions(FHTNPlanningContext& Context)
{
	FHTNPlanStep* AddedStep = nullptr;
	FHTNPlanStepID AddedStepID;
	const TSharedRef<FHTNPlan> NewPlan = Context.MakePlanCopyWithAddedStep(AddedStep, AddedStepID);

	AddedStep->bIsIfNodeFalseBranch = !Context.bDecoratorsPassed;
	AddedStep->bCanConditionsInterruptFalseBranch = bCanConditionsInterruptFalseBranch;
	// 如果装饰器通过则为SubLevelIndex创建Level
	if (Context.bDecoratorsPassed)
	{
		AddedStep->SubLevelIndex = GetPrimaryNextNodes().Num() > 0 ?
			Context.AddInlineLevel(*NewPlan, AddedStepID) :
			INDEX_NONE;
	}
	// 否则为SecondarySubLevelIndex 创建Level
	else
	{
		AddedStep->SecondarySubLevelIndex = GetSecondaryNextNodes().Num() > 0 ?
			Context.AddInlineLevel(*NewPlan, AddedStepID) :
			INDEX_NONE;
	}
	
	Context.SubmitCandidatePlan(NewPlan);
}

6 - void FHTNPlanningContext::SubmitCandidatePlan(const TSharedRef& CandidatePlan) const:提交复合节点

void FHTNPlanningContext::SubmitCandidatePlan(const TSharedRef<FHTNPlan>& CandidatePlan) const
{
	if (AddingNode->MaxRecursionLimit > 0)
	{
		CandidatePlan->IncrementRecursionCount(AddingNode.Get());
	}

	const FHTNPlanStepID AddedStepID = { CurrentPlanStepID.LevelIndex, CurrentPlanStepID.StepIndex + 1 };

#if DO_CHECK
	const FHTNPlanLevel& Level = *CandidatePlan->Levels[AddedStepID.LevelIndex];
	check(Level.Steps.IsValidIndex(AddedStepID.StepIndex));
	check(Level.Steps.Num() - 1 == AddedStepID.StepIndex);
#endif
	
	FHTNPlanStep& AddedStep = CandidatePlan->GetStep(AddedStepID);
	check(AddedStep.Node == AddingNode);
	
	// If the node has no sublevels and no worldstate, set the worldstate to what it was after entering decorators.
	if (AddedStep.SubLevelIndex == INDEX_NONE && AddedStep.SecondarySubLevelIndex == INDEX_NONE && !AddedStep.WorldState.IsValid())
	{
		AddedStep.WorldState = AddedStep.WorldStateAfterEnteringDecorators;
	}
	// Decorators的ExitCheck
	if (!AddedStep.WorldState.IsValid() || PlanningTask->ExitDecoratorsAndPropagateWorldState(*CandidatePlan, AddedStepID))
	{
		PlanningTask->SubmitCandidatePlan(CandidatePlan, AddingNode.Get());
	}
}

7 - SubmitCandidatePlan(const TSharedRef& NewPlan, UHTNNode* AddedNode):加到堆里

void UAITask_MakeHTNPlan::SubmitCandidatePlan(const TSharedRef<FHTNPlan>& NewPlan, UHTNNode* AddedNode)
{
	// TODO this makes a new plan be first if the previous-best had the same cost. This screws up traversal order of branches based on y pos of nodes.
	Frontier.HeapPush(NewPlan, FComparePlanCosts());
	check(*Algo::MinElementBy(Frontier, [](const TSharedPtr<FHTNPlan>& Plan) { return Plan->Cost; }) == Frontier[0]);

	SAVE_PLANNING_STEP_SUCCESS(CurrentPlanToExpand, AddedNode, NewPlan);
}

计划执行部分

总结

链式结构,一个节点结束后使用GetNextPrimitiveSteps,将下一个任务加入到

1 - OnPlanningTaskFinished:MakePlan后执行

  • 使用StartPlanningTask(bForceDeferToNextFrame)在制定好计划后,EndTask
  • 然后从AITask的周期内自动调用void UHTNComponent::OnGameplayTaskDeactivated(UGameplayTask& Task),调用OnPlanningTaskFinished()
void UHTNComponent::OnPlanningTaskFinished()
{
	check(CurrentPlanningTask);

	if (CurrentPlanningTask->WasCancelled())
	{
		UE_VLOG(GetOwner(), LogHTN, Log, TEXT("planning task was cancelled"));
		CurrentPlanningTask->Clear();
		CurrentPlanningTask = nullptr;
		return;
	}
	// 清空CurrentPlanningTask
	const TSharedPtr<FHTNPlan> ProducedPlan = CurrentPlanningTask->GetFinishedPlan();
	CurrentPlanningTask->Clear();
	CurrentPlanningTask = nullptr;

	if (CurrentPlan.IsValid())
	{
		AbortCurrentPlan();
	}

	if (ProducedPlan.IsValid())
	{
		INC_DWORD_STAT(STAT_AI_HTN_NumProducedPlans);
		// 存到PendingPlanExecutionInfo里面
		PendingPlanExecutionInfo = {ProducedPlan};
		
		if (!bDeferredAbortPlan && !IsWaitingForAbortingTasks())
		{
			// 开始执行
			StartPendingPlanExecution();
		}
	}
	else
	{
		UE_VLOG(GetOwner(), LogHTN, Log, TEXT("failed to produce a new plan"));
	}
}

2 - StartPendingPlanExecution:开始执行

void UHTNComponent::StartPendingPlanExecution()
{
	if (!ensure(PendingPlanExecutionInfo.IsSet()))
	{
		return;
	}

	if (!ensure(CurrentHTNAsset))
	{
		PendingPlanExecutionInfo = {};
		return;
	}

	check(!HasActivePlan());
	check(!bDeferredAbortPlan && !IsWaitingForAbortingTasks());
	// 获取Plan并清空PendingPlanExecutionInfo
	CurrentPlan = PendingPlanExecutionInfo.NewPlan;
	PendingPlanExecutionInfo = {};

	UE_VLOG(GetOwner(), LogHTN, Log, TEXT("produced new plan with cost %d"), CurrentPlan->Cost);
	CurrentPlan->InitializeForExecution(*this, *CurrentHTNAsset, PlanMemory, InstancedNodes);
	// 获取计划中的第一步(通过PendingExecutionStepIDs获取)
	if (!CurrentPlan->GetNextPrimitiveSteps(*this, {0, INDEX_NONE}, /*OutStepIds=*/PendingExecutionStepIDs, /*bIsExecutingPlan=*/true))
	{
		UE_VLOG(GetOwner(), LogHTN, Warning, TEXT("produced plan was degenerate, having no primitive tasks. Check if you have any Compound Tasks with unassigned HTN assets."));
		ClearCurrentPlan();
		return;
	}
	// 广播
	FHTNDelegates::OnPlanExecutionStarted.Broadcast(*this, CurrentPlan);
}

2.1 - GetNextPrimitiveSteps:找到下一个原始步骤,就是Task的意思

int32 FHTNPlan::GetNextPrimitiveSteps(const UHTNComponent& OwnerComp, const FHTNPlanStepID& InStepID, TArray<FHTNPlanStepID>& OutStepIds, bool bIsExecutingPlan) const
{
	FHTNGetNextStepsContext Context(OwnerComp, *this, bIsExecutingPlan, OutStepIds);
	Context.AddNextPrimitiveStepsAfter(InStepID);
	return Context.GetNumSubmittedSteps();
}

int32 FHTNGetNextStepsContext::AddNextPrimitiveStepsAfter(const FHTNPlanStepID& InStepID)
{
	if (!Plan.HasLevel(InStepID.LevelIndex))
	{
		return 0;
	}
	// 当前level
	const FHTNPlanLevel& Level = *Plan.Levels[InStepID.LevelIndex];

	const auto GetNumStepsSubmittedNow = [OldNumSubmittedSteps = NumSubmittedSteps, this]()
	{
		return NumSubmittedSteps - OldNumSubmittedSteps;
	};
	// 下一步
	// Check steps of this plan and steps in their sublevels, recursively
	for (int32 StepIndex = InStepID.StepIndex + 1; StepIndex < Level.Steps.Num(); ++StepIndex)
	{
		const FHTNPlanStep& CandidateStep = Level.Steps[StepIndex];
		CandidateStep.Node->GetNextPrimitiveSteps(*this, {InStepID.LevelIndex, StepIndex});
		if (const int32 NumSubmittedNow = GetNumStepsSubmittedNow())
		{
			return NumSubmittedNow;
		}
	}
	// 当前level没有了就从父level的下一个位置开始找(递归)
	// Level finished, check steps of the parent level, recursively.
	if (Level.ParentStepID != FHTNPlanStepID::None)
	{
		const FHTNPlanStep& ParentStep = Plan.GetStep(Level.ParentStepID);
		ParentStep.Node->GetNextPrimitiveSteps(*this, Level.ParentStepID, InStepID.LevelIndex);
	}

	const int32 NumSubmittedNow = GetNumStepsSubmittedNow();
	return NumSubmittedNow;
}

2.2 - UHTNNode_If::GetNextPrimitiveSteps

重写此函数,自定义实际执行时的下一个任务的获取逻辑

void UHTNNode_If::GetNextPrimitiveSteps(FHTNGetNextStepsContext& Context, const FHTNPlanStepID& ThisStepID)
{
	const FHTNPlanStep& Step = Context.Plan.GetStep(ThisStepID);

	if (Step.SubLevelIndex != INDEX_NONE)
	{
		Context.AddFirstPrimitiveStepsInLevel(Step.SubLevelIndex);
	}
	else if (Step.SecondarySubLevelIndex != INDEX_NONE)
	{
		Context.AddFirstPrimitiveStepsInLevel(Step.SecondarySubLevelIndex);
	}
}

3 - TickCurrentPlan:Tick循环

void UHTNComponent::TickCurrentPlan(float DeltaTime)
{
	check(HasActivePlan());
	// 执行任务
	StartTasksPendingExecution();
	if (!HasActivePlan())
	{
		UE_VLOG(GetOwner(), LogHTN, Log, TEXT("finished plan"));
		ClearCurrentPlan();
		return;
	}

	// Make a copy since items might get removed from the CurrentlyExecutingStepIDs during this.
	TArray<FHTNPlanStepID, TInlineAllocator<8>> ExecutingStepIDs(CurrentlyExecutingStepIDs);
	ExecutingStepIDs.Append(CurrentlyAbortingStepIDs);
	for (const FHTNPlanStepID& ExecutingStepID : ExecutingStepIDs)
	{
		// Recheck,判断Plan的合法性
		if (!TickSubNodesOrRecheck(ExecutingStepID, DeltaTime))
		{
			// if wasn't already aborted from within the decorators.
			if (HasActivePlan())
			{
				AbortCurrentPlan();
				break;
			}
		}
		else
		{
			uint8* TaskMemory = nullptr;
			const UHTNTask& ExecutingTask = GetTaskInCurrentPlan(ExecutingStepID, TaskMemory);
			UE_VLOG(GetOwner(), LogHTN, VeryVerbose, TEXT("ticking %s."), *ExecutingTask.GetNodeName());
			// 执行Tick
			ExecutingTask.WrappedTickTask(*this, TaskMemory, DeltaTime);
		}

		if (CurrentlyExecutingStepIDs.Num() == 0)
		{
			break;
		}
	}
}

4 - StartTasksPendingExecution:执行任务

void UHTNComponent::StartTasksPendingExecution()
{
	// Note that step ids might get added to PendingExecutionStepIDs during the loop 
	// if a task completes instantly (e.g. Success, SetValue)
	TArray<FHTNPlanStepID, TInlineAllocator<8>> AlreadyStartedSteps;
	int32 I = 0;
	// 编译所有Step,一般都是只有一个
	for (; I < PendingExecutionStepIDs.Num() && !IsWaitingForAbortingTasks(); ++I)
	{
		const FHTNPlanStepID& AddedStepID = PendingExecutionStepIDs[I];

		// Stop if have to start the same step again
		// (such as when the secondary branch of a Parallel node is looping and completed instantly)
		if (AlreadyStartedSteps.Contains(AddedStepID))
		{
			break;
		}
		// 存一路上(level层面)的节点的StepID
		TArray<FHTNPlanStepID, TInlineAllocator<8>> EnteringStepIDs { AddedStepID };
		while (EnteringStepIDs.Top().StepIndex == 0 && EnteringStepIDs.Top().LevelIndex > 0)
		{
			EnteringStepIDs.Add(CurrentPlan->Levels[EnteringStepIDs.Top().LevelIndex]->ParentStepID);
		}
		
#if USE_HTN_DEBUGGER
		// Store debug steps of entering the plan steps that contain this level
		for (int32 StepIndex = EnteringStepIDs.Num() - 1; StepIndex >= 0; --StepIndex)
		{
			StoreDebugStep().ActivePlanStepIDs.Add(EnteringStepIDs[StepIndex]);
		}
#endif

		if (!CurrentPlan->IsSecondaryParallelStep(AddedStepID))
		{
			// 将装饰器的影响施加到黑板上
			for (int32 StepIndex = EnteringStepIDs.Num() - 1; StepIndex >= 0; --StepIndex)
			{
				const FHTNPlanStep& EnteringStep = CurrentPlan->GetStep(EnteringStepIDs[StepIndex]);
				EnteringStep.WorldStateAfterEnteringDecorators->ApplyChangedValues(*BlackboardComp);
			}
		}
		// 执行任务
		const EHTNNodeResult Result = StartExecuteTask(AddedStepID);
		if (bDeferredAbortPlan || bDeferredStopHTN)
		{
			break;
		}

		AlreadyStartedSteps.Add(AddedStepID);
	}
	PendingExecutionStepIDs.RemoveAt(0, FMath::Min(I, PendingExecutionStepIDs.Num()), /*bAllowShrinking=*/false);
}

5 - StartExecuteTask:执行任务

EHTNNodeResult UHTNComponent::StartExecuteTask(const FHTNPlanStepID& PlanStepID)
{
	SCOPE_CYCLE_COUNTER(STAT_AI_HTN_Execution);
	
	if (!ensure(HasActivePlan()))
	{
		return EHTNNodeResult::Failed;
	}

	check(!CurrentlyExecutingStepIDs.Contains(PlanStepID));
	CurrentlyExecutingStepIDs.Add(PlanStepID);
#if USE_HTN_DEBUGGER
	StoreDebugStep();
#endif
	
	const FHTNPlanStep& PlanStep = CurrentPlan->GetStep(PlanStepID);
	UHTNTask& Task = *CastChecked<UHTNTask>(PlanStep.Node);
	UE_VLOG(GetOwner(), LogHTN, Verbose, TEXT("starting task %s"), *Task.GetNodeName());
	
	StartSubNodesStartingAtPlanStep(PlanStepID);
	// 执行具体任务
	const EHTNNodeResult Result = Task.WrappedExecuteTask(*this, GetNodeMemory(PlanStep.NodeMemoryOffset), PlanStepID);
	if (Result != EHTNNodeResult::InProgress)
	{
		// 不是InProgress直接调用执行任务
		OnTaskFinished(&Task, Result);
	}
	return Result;
}

6 - OnTaskFinished:结束任务

在Tick内调用FinishLatentTask或者Excute时直接返回Success或者Failed

void UHTNTask::FinishLatentTask(UHTNComponent& OwnerComp, EHTNNodeResult TaskResult) const
{
	OwnerComp.OnTaskFinished(this, TaskResult);
}

void UHTNComponent::OnTaskFinished(const UHTNTask* Task, EHTNNodeResult Result)
{
	if (!Task || !CurrentPlan.IsValid())
	{
		return;
	}

	if (!ensureMsgf(Result != EHTNNodeResult::InProgress, TEXT("UHTNComponent::OnTaskFinished called with EHTNNodeResult::InProgress. Task %s"), *Task->GetNodeName()))
	{
		return;
	}

	uint8* TaskMemory = nullptr;
	FHTNPlanStepID FinishedStepID = FHTNPlanStepID::None;
	const EHTNTaskStatus TaskStatus = FindStepIDAndMemoryOfTask(Task, FinishedStepID, TaskMemory);
	if (TaskStatus == EHTNTaskStatus::Inactive)
	{
		return;
	}
	
	const UHTNTask* const TaskTemplate = StaticCast<const UHTNTask*>(Task->GetTemplateNode());
	// On Task Finished
	TaskTemplate->WrappedOnTaskFinished(*this, TaskMemory, Result);
	FinishSubNodesAtPlanStep(FinishedStepID, Result);
	if (Result == EHTNNodeResult::Succeeded)
	{
		AbortSecondaryParallelBranchesIfNeeded(FinishedStepID);
		check(CurrentPlan.IsValid());
	}
	CurrentlyExecutingStepIDs.RemoveSingle(FinishedStepID);

	check(BlackboardComp);
	check(CurrentPlan.IsValid());
	CurrentPlan->CheckIntegrity();
	
	if (Result == EHTNNodeResult::Succeeded)
	{
		UE_VLOG(GetOwner(), LogHTN, Verbose, TEXT("finished %s (plan level %i, step %i)"), 
			*Task->GetNodeName(), 
			FinishedStepID.LevelIndex, 
			FinishedStepID.StepIndex
		);

		// Apply worldstate changes of the finished step
		const FHTNPlanStep& FinishedStep = CurrentPlan->GetStep(FinishedStepID);
		// 将当前Task对WorldState的修改施加到黑板上
		FinishedStep.WorldState->ApplyChangedValues(*BlackboardComp);
		// 获取下一步
		// Pick next tasks
		CurrentPlan->GetNextPrimitiveSteps(*this, FinishedStepID, /*OutStepIds=*/PendingExecutionStepIDs, /*bIsExecutingPlan=*/true);
	}
	else if (Result == EHTNNodeResult::Aborted)
	{
		// 被中止
		UE_VLOG(GetOwner(), LogHTN, Verbose, TEXT("finished aborting %s (plan level %i, step %i)"),
			*Task->GetNodeName(),
			FinishedStepID.LevelIndex,
			FinishedStepID.StepIndex
		);

		CurrentlyAbortingStepIDs.RemoveSingle(FinishedStepID);

		if (!IsWaitingForAbortingTasks() && CurrentlyExecutingStepIDs.Num() == 0 && PendingExecutionStepIDs.Num() == 0)
		{
			// 不是中止中时,结束
			OnPlanAbortFinished();
		}
	}
	else
	{
		UE_VLOG(GetOwner(), LogHTN, Verbose, TEXT("failed %s (plan level %i, plan step %i)"), 
			*Task->GetNodeName(), 
			FinishedStepID.LevelIndex, 
			FinishedStepID.StepIndex
		);
		// 失败时中止当前计划
		AbortCurrentPlan();
	}
}

6.1 - ApplyChangedValues:将worldstate的影响作用到黑板键上

template<typename DestinationType>
static void ApplyChangedValues(const FBlackboardWorldState& WorldState, DestinationType& Destination)
{
	check(WorldState.BlackboardComponent.IsValid());
	check(WorldState.BlackboardAsset.IsValid());

	auto& DestinationMemory = GetValueMemory(Destination);
	const TArray<UBlackboardKeyType*>& DestinationKeyInstances = GetKeyInstances(Destination);
	UBlackboardComponent& BlackboardComponent = *WorldState.BlackboardComponent;
	
	for (FBlackboard::FKey KeyID = 0; KeyID < WorldState.ChangedFlags.Num(); ++KeyID)
	{
		if (WorldState.ChangedFlags[KeyID])
		{
			if (const FBlackboardEntry* const Entry = WorldState.BlackboardAsset->GetKey(KeyID))
			{
				if (UBlackboardKeyType* const KeyType = Entry->KeyType)
				{
					const bool bKeyHasInstance = KeyType->HasInstance();
					const uint16 MemoryOffset = bKeyHasInstance ? sizeof(FBlackboardInstancedKeyMemory) : 0;

					const uint8* const SourceValueMemory = GetKeyRawDataConst(WorldState.ValueMemory, BlackboardComponent, KeyID) + MemoryOffset;
					uint8* const DestinationValueMemory = GetKeyRawData(DestinationMemory, BlackboardComponent, KeyID) + MemoryOffset;

					UBlackboardKeyType* const SourceKey = bKeyHasInstance ? WorldState.KeyInstances[KeyID] : KeyType;
					UBlackboardKeyType* const DestinationKey = bKeyHasInstance ? DestinationKeyInstances[KeyID] : KeyType;
					if (DestinationKey->CompareValues(BlackboardComponent, SourceValueMemory, DestinationKey, DestinationValueMemory) != EBlackboardCompare::Equal)
					{
						UBlackboardKeyTypeHelper::CopyValuesHelper(DestinationKey, BlackboardComponent, DestinationValueMemory, SourceKey, SourceValueMemory);
						NotifyValueChanged(Destination, KeyID, *Entry, DestinationKey, MemoryOffset, DestinationValueMemory);
					}
				}
			}
		}
	}
}

7 - 结束计划

当刚开始执行计划时没找到下一步时,手动调用ClearCurrentPlan,设置CurrentPlan为空

if (!CurrentPlan->GetNextPrimitiveSteps(*this, {0, INDEX_NONE}, /*OutStepIds=*/PendingExecutionStepIDs, /*bIsExecutingPlan=*/true))
{
	UE_VLOG(GetOwner(), LogHTN, Warning, TEXT("produced plan was degenerate, having no primitive tasks. Check if you have any Compound Tasks with unassigned HTN assets."));
	ClearCurrentPlan();
	return;
}

或者是在执行过程中,TickCurrentPlan发现HasActivePlan(),也就是说找不到下一个执行的任务即PendingExecutionStepIDs为空

bool UHTNComponent::HasActivePlan() const
{
	return CurrentPlan.IsValid() && (CurrentlyExecutingStepIDs.Num() || PendingExecutionStepIDs.Num() || CurrentlyAbortingStepIDs.Num());
}

也会调用ClearCurrentPlan

void UHTNComponent::TickCurrentPlan(float DeltaTime)
{
	check(HasActivePlan());
	
	StartTasksPendingExecution();
	if (!HasActivePlan())
	{
		UE_VLOG(GetOwner(), LogHTN, Log, TEXT("finished plan"));
		ClearCurrentPlan();
		return;
	}
void UHTNComponent::ClearCurrentPlan()
{
	check(!IsWaitingForAbortingTasks());
	
	if (CurrentPlan.IsValid())
	{
		CurrentPlan->CleanupAfterExecution(*this);
		CurrentPlan.Reset();
#if USE_HTN_DEBUGGER
		StoreDebugStep(/*bIsEmpty=*/true);
#endif
	}
	
	CurrentlyExecutingStepIDs.Reset();
	PendingExecutionStepIDs.Reset();
	CurrentlyAbortingStepIDs.Reset();
	InstancedNodes.Reset();
	PlanMemory.Reset();
}

8 - 再次计划

在TickComponent内检测当前的计划!HasActivePlan(),并且CurrentPlanningTask为nullptr也就是说没有在制定计划中。说明之前的Plan已经结束,所以再次调用StartPlanningTask,使用存储的CurrentHTNAsset进行计划

void UHTNComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	......
	if (bDeferredStartPlanningTask || !HasActivePlan() && !CurrentPlanningTask)
	{
		StartPlanningTask();
	}
}

9 - Scope Service的Start、Tick、Finish时机

以UHTNComponent::FinishSubNodesAtPlanStep为例,查询哪些节点的哪些Service需要调用Finish函数,是通过FHTNPlan::GetSubNodesAtExecutingPlanStep判断的。

内部逻辑是(Finish为例),当前节点如果是当前Level的最后一个节点,才会往前通知Finish。Begin同理。

在这里插入图片描述

也就是说,上图中的SetRandomPoint如果是最后节点,那么在Finish的时候,才会往前找。把ChangeMaxSpeed这个Service记录,才能触发ReceiveExecutionFinish。

所以说,现在有一个bug,就是当某个任务Fail的时候,如果不是最后一个节点,不会调用Service的ReceiveExecutionFinish

数据相关部分

创建WorldState

const TSharedRef<FBlackboardWorldState> WorldStateAtPlanStart = MakeShared<FBlackboardWorldState>(*BlackboardComponent);
FBlackboardWorldState::FBlackboardWorldState(UBlackboardComponent& Blackboard) :
	BlackboardComponent(&Blackboard),
	BlackboardAsset(Blackboard.GetBlackboardAsset()),
	bIsInitialized(false)
{
	check(BlackboardComponent.IsValid());
	check(BlackboardAsset.IsValid());
	// 复制值
	FBlackboardWorldStateImpl::InitializeKeys(*this, Blackboard);
}
template<typename SourceType>
static void InitializeKeys(FBlackboardWorldState& WorldState, const SourceType& Source)
{
	check(WorldState.BlackboardComponent.IsValid());
	check(WorldState.BlackboardAsset.IsValid());
	check(!WorldState.bIsInitialized);
	// 总空间
	const auto& SourceMemory = GetValueMemory(Source);
	const TArray<UBlackboardKeyType*>& SourceKeyInstances = GetKeyInstances(Source);
	UBlackboardComponent& BlackboardComponent = *WorldState.BlackboardComponent;
	// 初始化
	WorldState.ValueMemory.AddZeroed(SourceMemory.Num());
	WorldState.KeyInstances.AddZeroed(SourceKeyInstances.Num());
	for (UBlackboardData* It = WorldState.BlackboardAsset.Get(); It; It = It->Parent)
	{
		// 遍历各个黑板键
		for (int32 KeyIndex = 0; KeyIndex < It->Keys.Num(); ++KeyIndex)
		{
			if (UBlackboardKeyType* const KeyType = It->Keys[KeyIndex].KeyType)
			{
				KeyType->PreInitialize(BlackboardComponent);
				
				const bool bKeyHasInstance = KeyType->HasInstance();
				// 如果实例化,使用地址偏移
				const uint16 MemoryOffset = bKeyHasInstance ? sizeof(FBlackboardInstancedKeyMemory) : 0;
				const FBlackboard::FKey KeyID = KeyIndex + It->GetFirstKeyID();

				const uint8* const SourceValueMemory = GetKeyRawDataConst(SourceMemory, BlackboardComponent, KeyID) + MemoryOffset;
				UBlackboardKeyType* const SourceKey = bKeyHasInstance ? SourceKeyInstances[KeyID] : KeyType;
				
				uint8* const DestinationRawMemory = GetKeyRawData(WorldState.ValueMemory, BlackboardComponent, KeyID);
				uint8* const DestinationValueMemory = DestinationRawMemory + MemoryOffset;
				UBlackboardKeyType* DestinationKey = KeyType;
				if (bKeyHasInstance)
				{
					DestinationKey = UBlackboardKeyTypeHelper::MakeInstance(SourceKey, BlackboardComponent);
					reinterpret_cast<FBlackboardInstancedKeyMemory*>(DestinationRawMemory)->KeyIdx = KeyID;
					WorldState.KeyInstances[KeyID] = DestinationKey;
				}
				UBlackboardKeyTypeHelper::InitializeMemoryHelper(DestinationKey, BlackboardComponent, DestinationValueMemory);

				UBlackboardKeyTypeHelper::CopyValuesHelper(DestinationKey, BlackboardComponent, DestinationValueMemory, SourceKey, SourceValueMemory);
			}
		}
	}

	WorldState.bIsInitialized = true;
}

拷贝WorldState

const TSharedRef<FBlackboardWorldState> NewWorldState = WorldState->MakeNext();

TSharedRef<FBlackboardWorldState> FBlackboardWorldState::MakeNext() const
{
	DECLARE_SCOPE_CYCLE_COUNTER(TEXT("FBlackboardWorldState::MakeNext"), STAT_AI_HTN_WorldStateMakeNext, STATGROUP_AI_HTN);
	
	check(BlackboardComponent.IsValid());
	check(BlackboardComponent->HasBeenInitialized());
	check(BlackboardAsset.IsValid());
	
	const TSharedRef<FBlackboardWorldState> NextWorldstate = MakeShared<FBlackboardWorldState>();
	NextWorldstate->BlackboardComponent = BlackboardComponent;
	NextWorldstate->BlackboardAsset = BlackboardAsset;
	FBlackboardWorldStateImpl::InitializeKeys(*NextWorldstate, *this);
	
	return NextWorldstate;
}

结束任务时,将当前Task对WorldState的修改施加到黑板上

void UHTNTask::FinishLatentTask(UHTNComponent& OwnerComp, EHTNNodeResult TaskResult) const
{
	OwnerComp.OnTaskFinished(this, TaskResult);
}

void UHTNComponent::OnTaskFinished(const UHTNTask* Task, EHTNNodeResult Result)
{
	if (!Task || !CurrentPlan.IsValid())
	{
		return;
	}

	if (!ensureMsgf(Result != EHTNNodeResult::InProgress, TEXT("UHTNComponent::OnTaskFinished called with EHTNNodeResult::InProgress. Task %s"), *Task->GetNodeName()))
	{
		return;
	}

	uint8* TaskMemory = nullptr;
	FHTNPlanStepID FinishedStepID = FHTNPlanStepID::None;
	const EHTNTaskStatus TaskStatus = FindStepIDAndMemoryOfTask(Task, FinishedStepID, TaskMemory);
	if (TaskStatus == EHTNTaskStatus::Inactive)
	{
		return;
	}
	
	const UHTNTask* const TaskTemplate = StaticCast<const UHTNTask*>(Task->GetTemplateNode());
	// On Task Finished
	TaskTemplate->WrappedOnTaskFinished(*this, TaskMemory, Result);
	FinishSubNodesAtPlanStep(FinishedStepID, Result);
	if (Result == EHTNNodeResult::Succeeded)
	{
		AbortSecondaryParallelBranchesIfNeeded(FinishedStepID);
		check(CurrentPlan.IsValid());
	}
	CurrentlyExecutingStepIDs.RemoveSingle(FinishedStepID);

	check(BlackboardComp);
	check(CurrentPlan.IsValid());
	CurrentPlan->CheckIntegrity();
	
	if (Result == EHTNNodeResult::Succeeded)
	{
		UE_VLOG(GetOwner(), LogHTN, Verbose, TEXT("finished %s (plan level %i, step %i)"), 
			*Task->GetNodeName(), 
			FinishedStepID.LevelIndex, 
			FinishedStepID.StepIndex
		);

		// Apply worldstate changes of the finished step
		const FHTNPlanStep& FinishedStep = CurrentPlan->GetStep(FinishedStepID);
		// 将当前Task对WorldState的修改施加到黑板上
		FinishedStep.WorldState->ApplyChangedValues(*BlackboardComp);
		// 获取下一步
		// Pick next tasks
		CurrentPlan->GetNextPrimitiveSteps(*this, FinishedStepID, /*OutStepIds=*/PendingExecutionStepIDs, /*bIsExecutingPlan=*/true);
	}
	else if (Result == EHTNNodeResult::Aborted)
	{
		// 被中止
		UE_VLOG(GetOwner(), LogHTN, Verbose, TEXT("finished aborting %s (plan level %i, step %i)"),
			*Task->GetNodeName(),
			FinishedStepID.LevelIndex,
			FinishedStepID.StepIndex
		);

		CurrentlyAbortingStepIDs.RemoveSingle(FinishedStepID);

		if (!IsWaitingForAbortingTasks() && CurrentlyExecutingStepIDs.Num() == 0 && PendingExecutionStepIDs.Num() == 0)
		{
			// 不是中止中时,结束
			OnPlanAbortFinished();
		}
	}
	else
	{
		UE_VLOG(GetOwner(), LogHTN, Verbose, TEXT("failed %s (plan level %i, plan step %i)"), 
			*Task->GetNodeName(), 
			FinishedStepID.LevelIndex, 
			FinishedStepID.StepIndex
		);
		// 失败时中止当前计划
		AbortCurrentPlan();
	}
}

节点数据

bCreateNodeInstance为false的节点使用NodeMemory进行运行数据存储,但是NodeMemory的分配在制定好计划后,所以装饰器在制定计划时(OnPlanEnter)NodeMemory为空,任务在CreatePlanSteps时也不能存储运行时数据。

并且,CreatePlanSteps时对于WorldState的影响需要在任务Finish之后才会施加到黑板上,所以在制定计划时定下的决定(例如随机数)在执行的时候是获取不到的,需要在之前的节点内将决定记录到黑板或者外部。

void FHTNPlan::InitializeForExecution(UHTNComponent& OwnerComponent, UHTN& HTNAsset, TArray<uint8>& OutPlanMemory, TArray<UHTNNode*>& OutNodeInstances)
{
	DECLARE_SCOPE_CYCLE_COUNTER(TEXT("FHTNPlan::InitializeForExecution"), STAT_AI_HTN_PlanInitializeForExecution, STATGROUP_AI_HTN);

	check(OutPlanMemory.Num() == 0);
	check(OutNodeInstances.Num() == 0);
	CheckIntegrity();
	
	struct Local
	{
		// Round to 4 bytes
		static int32 GetAlignedDataSize(int32 Size) { return (Size + 3) & ~3; };
	};

	struct FNodeInitInfo
	{
		UHTNNode* NodeTemplate;
		uint16 MemoryOffset;
		FHTNPlanStepID StepID;
	};
	
	uint16 TotalNumBytesNeeded = 0;
	TArray<FNodeInitInfo> InitList;
	const auto RecordNode = [&](UHTNNode& NodeTemplate, uint16& OutMemoryOffset, const FHTNPlanStepID& StepID)
	{
		const uint16 SpecialDataSize = Local::GetAlignedDataSize(NodeTemplate.GetSpecialMemorySize());
		OutMemoryOffset = TotalNumBytesNeeded + SpecialDataSize;

		const uint16 TotalDataSize = Local::GetAlignedDataSize(SpecialDataSize + NodeTemplate.GetInstanceMemorySize());
		TotalNumBytesNeeded += TotalDataSize;

		InitList.Add({ &NodeTemplate, OutMemoryOffset, StepID });
	};

	for (int32 LevelIndex = 0; LevelIndex < Levels.Num(); ++LevelIndex)
	{
		FHTNPlanLevel& Level = *Levels[LevelIndex];
		const FHTNPlanStepID RootStepID = Level.ParentStepID;
		
		// Do root decorators
		const TArrayView<UHTNDecorator*> DecoratorTemplates = Level.GetRootDecoratorTemplates();
		check(Level.RootDecoratorInfos.Num() == 0);
		Level.RootDecoratorInfos.Reserve(DecoratorTemplates.Num());
		for (UHTNDecorator* const Decorator : DecoratorTemplates)
		{
			if (Decorator)
			{
				RecordNode(*Decorator, Level.RootDecoratorInfos.Add_GetRef({ Decorator, 0 }).NodeMemoryOffset, RootStepID);
			}
		}

		// Do root services
		const TArrayView<UHTNService*> ServiceTemplates = Level.GetRootServiceTemplates();
		check(Level.RootServiceInfos.Num() == 0);
		Level.RootServiceInfos.Reserve(ServiceTemplates.Num());
		for (UHTNService* const Service : ServiceTemplates)
		{
			if (Service)
			{
				RecordNode(*Service, Level.RootServiceInfos.Add_GetRef({ Service, 0 }).NodeMemoryOffset, RootStepID);
			}
		}

		// Do steps
		for (int32 StepIndex = 0; StepIndex < Level.Steps.Num(); ++StepIndex)
		{
			const FHTNPlanStepID StepID { LevelIndex, StepIndex };
			FHTNPlanStep& Step = Level.Steps[StepIndex];
			
			check(Step.NodeMemoryOffset == 0);
			if (Step.Node.IsValid())
			{
				RecordNode(*Step.Node, Step.NodeMemoryOffset, StepID);
			}

			check(Step.DecoratorInfos.Num() == 0);
			Step.DecoratorInfos.Reserve(Step.Node->Decorators.Num());
			for (UHTNDecorator* const Decorator : Step.Node->Decorators)
			{
				RecordNode(*Decorator, Step.DecoratorInfos.Add_GetRef({Decorator, 0}).NodeMemoryOffset, StepID);
			}

			check(Step.ServiceInfos.Num() == 0);
			Step.ServiceInfos.Reserve(Step.Node->Services.Num());
			for (UHTNService* const Service : Step.Node->Services)
			{
				RecordNode(*Service, Step.ServiceInfos.Add_GetRef({Service, 0}).NodeMemoryOffset, StepID);
			}
		}
	}

	OutPlanMemory.SetNumZeroed(TotalNumBytesNeeded);
	for (const FNodeInitInfo& NodeInitInfo : InitList)
	{
		uint8* const RawNodeMemory = OutPlanMemory.GetData() + NodeInitInfo.MemoryOffset;
		NodeInitInfo.NodeTemplate->InitializeFromAsset(HTNAsset);
		NodeInitInfo.NodeTemplate->InitializeInPlan(OwnerComponent, RawNodeMemory, *this, NodeInitInfo.StepID, OutNodeInstances);
	}
}
void UHTNNode::InitializeInPlan(UHTNComponent& OwnerComp, uint8* NodeMemory, 
	const FHTNPlan& Plan, const FHTNPlanStepID& StepID, 
	TArray<UHTNNode*>& OutNodeInstances) const
{
	FHTNNodeSpecialMemory* const SpecialMemory = GetSpecialNodeMemory<FHTNNodeSpecialMemory>(NodeMemory);
	if (SpecialMemory)
	{
		SpecialMemory->NodeInstanceIndex = INDEX_NONE;
	}

	if (!bCreateNodeInstance)
	{
		InitializeMemory(OwnerComp, NodeMemory, Plan, StepID);
	}
	else
	{
		SCOPE_CYCLE_COUNTER(STAT_AI_HTN_NodeInstantiation);
		UHTNNode* const NodeInstance = DuplicateObject(this, &OwnerComp);
		INC_DWORD_STAT(STAT_AI_HTN_NumNodeInstances);

		check(HTNAsset);
		check(SpecialMemory);
		
		NodeInstance->TemplateNode = const_cast<UHTNNode*>(this);
		NodeInstance->InitializeFromAsset(*HTNAsset);
		NodeInstance->SetOwnerComponent(&OwnerComp);
		NodeInstance->InitializeMemory(OwnerComp, NodeMemory, Plan, StepID);

		SpecialMemory->NodeInstanceIndex = OutNodeInstances.Num();
		OutNodeInstances.Add(NodeInstance);
	}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值