Behavior Tree
FSM과 같은 AI모델 중 하나로 AI의 행동을 트리의 형태로 정의하는 모델이다.
루트에서 깊이 우선 탐색을 시작하여 상황에 맞는 행동을 선택하여 실행하는 형태이다.
상태를 전이하며 실행되는 개념이 아니기 떄문에 FSM의 상태가 많아지면 구조가 복잡해진다는 단점을 보완한 모델이다
언리얼에서는 BT를 기본적으로 제공하고 있다. Pawn의 AIController를 설정하여 BT를 활용한 AI를 사용할 수 있다.
UBehaviorTree
노드를 기반으로 행동이 정의되는 언리얼의 BehaviorTree에셋이다.
에셋을 열어보면 블루 프린트에 ROOT노드와 Details에 Blackboard Asset을 확인할 수 있다.
Behavior는 트리 형태로 구현되기 때문에 모든 구성 노드들은 ROOT노드의 하위 노드로 설정된다.
Blackboard Asset은 BT에서 사용되는 변수들의 저장소인 Blackboard의 언리얼 에셋이다.
자료형은 UBlackboardData이고 BT생성 경로와 동일한 곳에서 생성할 수 있다.
AIController
이름 그대로 AI의 Controller클래스이다. AIController를 상속받아 새롭게 정의할 수 있다.
클래스 생성 후 AI를 사용하는 Pawn클래스에서 AIControllerClass와 AutoPocessAI를 설정해준다.
AutoPossessAI는 Controller가 빙의되는 타입이다. 네 가지 타입이 있다.
- Disabled : 빙의되지 않음
- PlacedInWorld : 월드에 배치된 경우에만 빙의
- Spawned : 월드가 로드된 후 생성된 경우에만 빙의
- PlacedInWorldOrSpawned : 월드에 배치되었거나 생성된 경우 빙의
생선한 AIController클래스에는 몇가지 함수와 변수를 선언한다.
UCLASS()
class PROJECT_SOULLIKE_API ASLAIController : public AAIController
{
GENERATED_BODY()
public:
ASLAIController();
void RunAI(); //AI시작
void StopAI(); //AI정지
protected:
void OnPossess(APawn* InPawn) override; //Pawn에 빙의되었을 때 실행
private:
UPROPERTY()
TObjectPtr<class UBehaviorTree> BTAsset; //BehaviorTree 에셋
UPROPERTY()
TObjectPtr<class UBlackboardData> BBAsset; //Blackboard 에셋
};
#include "AI/SLAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"
ASLAIController::ASLAIController()
{
ConstructorHelpers::FObjectFinder<UBehaviorTree> BTAssetRef
(TEXT(""));
if (BTAssetRef.Object)
{
BTAsset = BTAssetRef.Object;
}
ConstructorHelpers::FObjectFinder<UBlackboardData> BBAssetRef
(TEXT(""));
if (BBAssetRef.Object)
{
BBAsset = BBAssetRef.Object;
}
}
void ASLAIController::RunAI()
{
UBlackboardComponent* BlackboardPtr = Blackboard.Get();
if (UseBlackboard(BBAsset, BlackboardPtr)) //블랙보드 설정
{
bool RunResult = RunBehaviorTree(BTAsset); //BT시작
ensure(RunResult);
}
}
void ASLAIController::StopAI()
{
UBehaviorTreeComponent* BTComponent = Cast<UBehaviorTreeComponent>(BrainComponent);
if (BTComponent)
{
BTComponent->StopTree(); //BT정지
}
}
void ASLAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
RunAI();
}
행동 구현하기
간단한 정찰 행동을 구현해보자
구현 전 AI가 움직여야 하기 떄문에 Nav Mesh Bounds Volume을 배치한다.
정찰 행동을 구분해보면 대기 -> 이동 위치 설정 -> 이동으로 나눌 수 있다.
언리얼에서 대기와 이동에 대한 노드는 기본적으로 구현되어 있지만 이동 위치 설정은 구현되어 있지 않기 떄문에 직업 구현해야 한다. 구현 전에는 AI와 관련된 모듈("NavigationSystem", "AIModule", "GameplayTasks")들을 추가해야 한다.
새로운 행동 노드를 구현하기 위해서는 BTTastNode클래스를 상속받는 클래스를 생성해야 한다.
생성 후 헤더에서 ExecuteTask(UBehaviorTreeComponent& OwnerCompo, uint8* NodeMemory)함수를 재정의하여 실행할 행동을 구현하면 된다.
UCLASS()
class PROJECT_SOULLIKE_API UBTTaskNode_FindPatrolPos : public UBTTaskNode
{
GENERATED_BODY()
public:
UBTTaskNode_FindPatrolPos();
//행동이 실행되는 함수
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerCompo, uint8* NodeMemory) override;
};
#include "AI/Task/BTTaskNode_FindPatrolPos.h"
#include "BTTaskNode_FindPatrolPos.h"
#include "AIController.h"
#include "NavigationSystem.h"
#include "BehaviorTree/BlackboardComponent.h"
UBTTaskNode_FindPatrolPos::UBTTaskNode_FindPatrolPos()
{
}
EBTNodeResult::Type UBTTaskNode_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerCompo, uint8* NodeMemory)
{
Super::ExecuteTask(OwnerCompo, NodeMemory);
APawn* ControllingPawn = OwnerCompo.GetAIOwner()->GetPawn();
if (!ControllingPawn)
{
return EBTNodeResult::Failed;
}
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn->GetWorld());
if (!NavSystem)
{
return EBTNodeResult::Failed;
}
FVector Origin = OwnerCompo.GetBlackboardComponent()->GetValueAsVector(TEXT("HomePos"));
float PatrolRadius = 800.0f;
FNavLocation NextPatrolPos;
//생성된 위치를 중심으로 하여 이동할 위치 구하기
if (NavSystem->GetRandomPointInNavigableRadius(Origin, PatrolRadius, NextPatrolPos))
{
OwnerCompo.GetBlackboardComponent()->SetValueAsVector("PatrolPos", NextPatrolPos.Location);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
여기서 구현에 사용된 변수("HomePos", "PatrolPos")는 BT의 변수를 저장하는 블랙보드를 참조하여 설정하였다.
블랙보드에 변수를 설정하기 위해서 블랙보드 에셋을 열어보면 아래와 같은 화면을 볼 수 있다.
여기서 왼쪽 상단의 AddKey버튼을 클릭하고 변수의 타입을 선택하면 변수를 생성할 수 있다.
코드에서 변수를 참조하려면 Details의 Entry Name값을 사용하면 된다.
이렇게 변수를 설정하고 BT를 만들어주면 된다.
Wait은 대기, FindPatrolPos는 정찰 위치 설정, Move To는 이동을 실행하는 노드이다.
Wait, MoveTo는 언리얼에서 기본적으로 제공하며 FindPatrolPos는 방금 만든 노드이다.
이렇게 BT를 만들고 Move To노드의 Blackboard Key에 목표 지점인 PatrolPos를 설정한다.
결과물
'UE5' 카테고리의 다른 글
[UE5] 콜리전 감지 (0) | 2024.06.22 |
---|---|
[UE5] 컴포넌트 (0) | 2024.06.09 |
[UE5] Animation Retargeting (0) | 2024.06.03 |
[UE5] Enhanced Input (0) | 2024.06.03 |
[UE5] Motion Matching (0) | 2024.05.26 |