Behavior Tree를 Pause 할 때 현재 Active 되어 있는 Task를 abort 시키고 싶습니다.

안녕하세요

몬스터의 BT가 Pause가 될 경우 내부에서 동작 중인 Task를 Abort 시켜서 빠져나가고 싶습니다.

기존에는 StopTree를 했었는데 이렇게 했더니 내부에 존재하던 Cooldown Task조차 초기화 되는 바람에 Pause를 하고 Task를 Abort 시키기로 결정했습니다.

<br/>

해당 로직을 실행하면 정상적으로 Task가 abort 되지만 문제는 BT가 Pause가 풀리지 않고, 계속 일시정지 상태로 유지되고 있습니다.

<br/>

방법을 알려 주시면 감사하겠습니다.

재현 방법
BehaviorTreeComponent를 상속받아서 void UMDRBehaviorTreeComponent::PauseLogic(const FString& Reason)에 해당 코드를 추가 하였습니다.

for (int32 InstanceIndex = InstanceStack.Num() - 1; InstanceIndex >= 0; --InstanceIndex)

{

FBehaviorTreeInstance& InstanceInfo = InstanceStack[InstanceIndex];

for (int32 TaskIndex = InstanceInfo.ParallelTasks.Num() - 1; TaskIndex >= 0; --TaskIndex)

{

if (InstanceInfo.ParallelTasks.IsValidIndex(TaskIndex))

{

FBehaviorTreeParallelTask& ParallelTask = InstanceInfo.ParallelTasks[TaskIndex];

if (ParallelTask.Status == EBTTaskStatus::Active && !IsPauseSafeTask(ParallelTask.TaskNode))

{

ParallelTask.Status = EBTTaskStatus::Aborting;

uint8* NodeMemory = ParallelTask.TaskNode->GetNodeMemory<uint8>(InstanceInfo);

EBTNodeResult::Type TaskResult = ParallelTask.TaskNode->WrappedAbortTask(*this, NodeMemory);

if (ParallelTask.Status == EBTTaskStatus::Aborting)

{

InstanceInfo.ParallelTasks.RemoveAt(TaskIndex);

}

}

}

}

if (InstanceInfo.ActiveNodeType == EBTActiveNode::ActiveTask)

{

const UBTTaskNode* TaskNode = Cast<const UBTTaskNode>(InstanceInfo.ActiveNode);

if (TaskNode && !IsPauseSafeTask(TaskNode))

{

UnregisterMessageObserversFrom(TaskNode);

InstanceInfo.ActiveNodeType = EBTActiveNode::AbortingTask;

uint8* NodeMemory = TaskNode->GetNodeMemory<uint8>(InstanceInfo);

EBTNodeResult::Type TaskResult = TaskNode->WrappedAbortTask(*this, NodeMemory);

if (InstanceInfo.ActiveNodeType == EBTActiveNode::AbortingTask)

{

OnTaskFinished(TaskNode, TaskResult);

}

}

}

}

안녕하세요

관련하여 내용 확인 후 답변드리도록 하겠습니다

감사합니다

안녕하세요.

첨부해주신 코드를 통해 언리얼 엔진 5.6 버전에서 말씀하신 문제 현상을 재현할 수 있었습니다.

해당 이슈의 원인은 ParallelTask와 ActiveTask를 강제로 중단하는 과정에서,

Pause 후 Resume 시점에

PendingExecution 및 ExecutionRequest.ExecuteNode

둘 중 어느 쪽에도 유효한 재개 정보가 남지 않으면서, 정상적으로 재개되지 못하는 데에 있는 것으로 보입니다.

아래 예제 코드는

ParallelTask는 직접 Abort를 호출해 정리하고,

ActiveTask는 상위 브랜치에 RequestExecution을 통해 Aborted 결과를 전달하여

재검색 요청을 명확히 남긴 뒤,

마지막으로 AbortCurrentTask를 이용해 현재 실행 중인 Task를 정상적인 Abort 플로우로 종료하는

방식을 적용한 예제 코드입니다.

FBehaviorTreeInstance& LastInstance = InstanceStack.Last();
if (LastInstance.ActiveNodeType == EBTActiveNode::ActiveTask)
{
	const UBTTaskNode* TaskNode = Cast<const UBTTaskNode>(LastInstance.ActiveNode);
	if (TaskNode)
	{
		const UBTCompositeNode* Parent = TaskNode->GetParentNode();
		if (Parent)
		{
			const int32 ChildIdx = Parent->GetChildIndex(*TaskNode);
			RequestExecution(Parent, InstanceStack.Num() - 1, TaskNode, ChildIdx, EBTNodeResult::Aborted);
		}
		AbortCurrentTask();
	}
}
for (int32 i = LastInstance.ParallelTasks.Num() - 1; i >= 0; --i)
{
	FBehaviorTreeParallelTask& TaskInfo = LastInstance.ParallelTasks[i];
	if (TaskInfo.Status == EBTTaskStatus::Active)
	{
		uint8* NodeMemory = TaskInfo.TaskNode->GetNodeMemory<uint8>(LastInstance);
		const EBTNodeResult::Type Result = TaskInfo.TaskNode->WrappedAbortTask(*this, NodeMemory);
		if (Result == EBTNodeResult::InProgress)
		{
			TaskInfo.Status = EBTTaskStatus::Aborting;
			bWaitingForLatentAborts = true;
		}
        else
        {
    		OnTaskFinished(TaskInfo.TaskNode, EBTNodeResult::Aborted);
        }
	}
}
Super::PauseLogic(Reason);

추가적으로,

Latent Task를 사용하는 경우에는

EBTNodeResult::InProgress 반환 시 처리 분기와 FinishLatentAbort 호출 여부를 함께 점검해 보시면

보다 안정적으로 적용하실 수 있을 것 같습니다.

또한, 서브 트리가 여러 개 인스턴스로 쌓이는 구조라면,

최상단 인스턴스만 처리하는 것뿐만 아니라 InstanceStack 전체를 순회하면서

같은 패턴을 적용하시는 것도 고려해 보시길 추천드립니다.

사용하시는 프로젝트의 BT 구성이나 커스텀 Task 구현 방식에 따라

추가적인 보완이 필요할 수 있으므로,

적용 시 BT 디버거/로그를 활용해 Pause·Resume 전후 상태를 한 번씩 확인해 보시면 도움이 될 것 같습니다.

위 코드는 특정 환경에서 재현·검증한 예제 코드로, 가이드라인 수준의 예제로 참고해 주시면 좋을 것 같습니다.

감사합니다.