2-11강 행동트리 모델의 이해

Ryan Ham·2024년 7월 9일
1

이득우 Unreal

목록 보기
17/23
post-thumbnail

강의 목표

  • 행동 트리 모델의 역사를 살펴보고 행동 트리 모델이 가진 장점을 이해
  • 행통 트리 모델의 구성 요소와 이를 활용한 설계 방법의 학습

Behavior Tree의 역사

  • 2004년 개발사 번지의 Halo 2에서 인공지능을 설계하는데 최초로 사용됨.
  • "Handling Complexity in the Halo 2 AI"를 발표
  • 우선순위와 트리 구조를 사용해 인공지능을 설계하는 기법

FSM VS BT

AI Control에 있어서 FSM이랑 Behavior tree가 가장 많이 쓰인다. 이 둘은 ModularityReactivity 측면에서 각각의 장단점이 존재한다. 일반적으로 BT는 Module면에서 강점을, FSM은 Reactive Behaviors를 구현하는데 강점을 가진다.

BT의 강점

왼쪽은 BT, 오른쪽은 FSM의 사진이다. BT는 트리 형태, FSM은 순서도의 모습을 하고 있는 것을 알 수 있다. 수정면에서 어떠한 노드를 삽입한다고 했을때 FSM의 경우보다 BT의 경우에 수정 사항이 확실히 덜 한 것을 알 수 있다(빨간색 영역이 각각 수정해야 할 부분들) .

FSM의 강점

Reactive designing의 측면에서 여러 노드간의 transition이 필요한 경우를 생각해보자. FSM의 경우 노드와 노드를 연결하면 그만이지만, BT는 기본적으로 DFS을 기반으로 한 트리 형태이기 때문에 이를 간단하게 구현하기가 쉽지 않다.


BT의 특징

  • DFS 기반
  • BlackBoard와 쌍으로 사용(BlackBoard에 변수를 등록)

BT의 구성 요소

Composite Node

  • 부모 노드에서 다수의 행동을 컨트롤 하는 것을 Composite이라고 한다.

  • Composite에는 Selector, Sequence, Parallel 3가지 종류가 있음.

    Selector : 자식 노드 중 성공한 노드가 나오면 종료.
    Sequence : 자식 노드 중 실패한 노드가 나올 때까지 진행.
    Parallel : 자식 노드들을 동시에 실행.

Composite Node에 부착하는 Module 종류

  • Decorator : Composite 노드가 실행되는 조건을 지정.
  • Service : Composite 노드가 실행되는동안 Tick마다 Service에 해당하는 부가 명령을 함께 실행.
  • Abort : Decorator 조건에 부합되면 Composite 내 활동을 모두 중단하고 루트에서부터 BT를 재시작(Decorator과 필히 함께 사용되어야 함).

노드가 반환하는 상태 값

BT 또한 tick에 의해서 update 되는데, tick마다 node는 밑의 4가지 상태 값 중 하나를 부모 노드에게 전달한다.

  • Succedded : 행동의 성공
  • Failed : 행동의 실패
  • Aborted : 외부 요인으로 인한 행동의 실패
  • InProgress : 행동 결과를 홀딩

BT Subtree 만들기

모든 경우에 대해서 하드코딩하여 BT를 구성할 수 있지만, 언리얼에서는 BT가 참고하는 변수 시스템인 BlackBoard를 통해 parametric subtree를 만들 수도 있다.

예시 그림은 parametric subtree인데 해당 flow를 설명해 보자면,

  1. 가장 부모 노드에서는 반복하는 decorator가 존재.
  2. 이후, 그 밑의 Sequence로 넘어가게 된다.
  3. Sequence 자식의 첫 번째 노드는 blackboard에 존재하는 queue 변수 LocationQueue를 참고하는데 이 queue에는 N개의 location 지점이 존재한다. 이 지점들은 뽑아서 AtLoc에 하나씩 넣으면서 BT가 작동하게 된다.

NPC 캐릭터 BT

위 그림은 우리가 구현할 NPC 캐릭터의 BT이다. BT에 대한 분석을 해보면,

  1. NPC는 정찰을 하거나 플레이어 공격을 하거나 둘 주 하나만을 수행함으로 Root 밑에는 Selector Composite를 배치.
  2. 공격에 대한 우선순위를 두기 위해서 정찰 Composite보다 공격 Composite를 좌측에 배치
  3. 침입자가 있을때에만 공격 Composite을 수행하도록 설정.
  4. 공격 Composite을 Selector로 만듦으로서 캐릭터와의 거리를 기준으로 공격 or 추격 중 하나의 액션만 수행.
  5. 정찰 Composite을 돌다가 캐릭터가 NPC의 가시반경 안에 들어오면 그 즉시 Abort하고 BT Root로 돌아가게 설정.

Controller와 AIController

우리가 캐릭터를 PlayerController로 조종하는 것처럼, NPC 캐릭터들도 일종의 Controller가 있어야 행동을 할 수 있다. 이 Controller를 AIController라고 한다.

AIController 사용하는 법

Step 1 : 에디터에서 BlackBoardBehavior Tree 만들기

언리얼 editor로 들어가 Artificial Intelligence 항목에 있는 BlackboardBehavior Tree를 각각 만들어줘야 한다.

Behavior TreeBlackBoard의 관계는 긴밀하다. BT에서 사용할 변수들을 BlackBoard에서 참고할 수 있는데, 이를 통해서 위에서 다루었던 것처럼 modular한 subtree를 생성할 수도 있다. 더 나아가 AI들이 level에 다양한 사물과 상호작용하고 이에 대한 값들을 BlackBoard에 저장하면, 이 값들을 참고해 BT 상에서 수행할 로직을 바꾸는 행동이 가능하다.

Step 2 : Custom AIController.cpp에 로직 구현

#include "AI/RyanAIController.h"
#include "BehaviorTree/BehaviorTree.h"
#include "BehaviorTree/BlackboardData.h"
#include "BehaviorTree/BlackboardComponent.h"

ARyanAIController::ARyanAIController()
{
	static ConstructorHelpers::FObjectFinder<UBlackboardData> BBAssetRef(TEXT("/Game/AI/BB_RyanCharacter.BB_RyanCharacter"));
	if (nullptr != BBAssetRef.Object)
	{
		BBAsset = BBAssetRef.Object;
	}

	static ConstructorHelpers::FObjectFinder<UBehaviorTree> BTAssetRef(TEXT("/Game/AI/BT_RyanCharacter.BT_RyanCharacter"));
	if (nullptr != BTAssetRef.Object)
	{
		BTAsset = BTAssetRef.Object;
	}
}

void ARyanAIController::RunAI()
{
	UBlackboardComponent* BlackboardPtr = Blackboard.Get();
	if (UseBlackboard(BBAsset, BlackboardPtr))
	{
		bool RunResult = RunBehaviorTree(BTAsset);
		ensure(RunResult);
	}
}

void ARyanAIController::StopAI()
{
	UBehaviorTreeComponent* BTComponent = Cast<UBehaviorTreeComponent>(BrainComponent);
	if (BTComponent)
	{
		BTComponent->StopTree();
	}
}

// Controller에서 제공하는 OnPossess 함수 override
void ARyanAIController::OnPossess(APawn* InPawn)
{
	Super::OnPossess(InPawn);

	RunAI();
}
  1. 생성자에서 BlackBoard랑 BehaviorTree 둘 다 reference로 가져오기

  2. Pawn이 possessed 되었는지를 체크하는 Onpossess를 override하고 RunAI 함수를 호출

  3. RunAI를 통해서 BT를 실행

  4. 종료시에는 StopAI 함수를 호출. AIController가 생성되면서 맴버 변수로 BrainComponent라는 것이 같이 생성되는데 AI에 관련된 다양한 명령은 이 BrainComponent를 통해서 가능하다. 지금 우리는 BT를 중지하고 싶은 것이므로, BrainComponent를 UBehaviorTreeComponent로 casting해서 가져온 다음, StopTree를 호출한다.

Step 3 : AIController가 빙의할 NPC Character에 등록

// RyanNPCCharacter.cpp

// AIController 설정 부분
AIControllerClass = ARyanAIController::StaticClass();

// Spawn된 Actor나 이미 level에 배치되었던 Actor 둘다에게 AIController를 빙의 
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;

AIController를 상속해서 만든 custom 클래스 ARyanAIController 정보를 가져온다. 어떻게 world에 생성된 pawn들에 대해서 AIController를 적용할지를 설정. 위 예시에서는 level에 배치된 것 + spawned되어서 생성된 것 둘 다에게 적용하도록 한다.


최종 화면

위 예시는 level이 재생되는 Spawned된 NPC 캐릭터에 대해 AIController를 Behavior Tree로 실시간 관찰하는 모습이다.

Selector 노드 밑에 Wait만 걸어주었기 때문에 아무런 동작을 하지 않는다.


Reference

https://roboticseabass.com/2021/05/08/introduction-to-behavior-trees/

profile
🏦KAIST EE | 🏦SNU AI(빅데이터 핀테크 전문가 과정) | 📙CryptoHipsters 저자

0개의 댓글