Make Unreal REAL.
article thumbnail
이득우의 언리얼 C++ 게임 개발의 정석

 

행동 트리(Behavior Tree)

AI가 해야 할 행동을 분석하고 우선순위가 높은 행동부터 실행하도록 트리 구조로 설계하는 기법이다.

 

블랙보드(Blackboard)

AI가 행동을 결정하는 데 사용할 변수(데이터)를 모아 놓은 집합이다.

 

새로운 행동 트리와 블랙보드를 만든다.

 

 

행동 트리를 열면 루트 노드가 보인다.

 

행동 트리에서 하나의 동작을 나타내는 단위를 (태스크)Task라고 한다.

 

태스크는 독립적으로 실행될 수 없고 반드시 컴포짓(Composite) 노드를 겨쳐 실행되어야 한다.

  • 대표적인 컴포짓으로는 시퀀스(Sequence)와 셀렉터(Selector)가 있다.

 

 

시퀀스를 1개 추가하고 하위에 Wait 태스크를 1개 추가한다.

  • 시퀀스는 하위 태스크들의 결과가 False가 나올 때까지 왼쪽에서 오른쪽으로 계속 실행한다.
  • 가장 오른쪽 노드가 끝나면 왼쪽부터 다시 실행한다.

 

각 노드 우측 상단의 번호는 실행 순서를 의미한다.

 

 

ABAIController 클래스에서 행동 트리를 이용해 AI를 이동시켜보겠다.

 

언리얼 C++에서 행동 트리 관련 기능을 사용하기 위해서는 {프로젝트명}.Build.cs 파일에 AIModule 모듈을 추가해줘야 한다.

 

 

ABAIController 클래스의 기존 내용을 주석 처리하고 다시 작성한다.

 

생성자와 폰을 소유할 때 호출되는 OnPossess() 함수를 오버라이드 하도록 선언한다.

 

행동 트리와 블랙보드를 저장할 변수를 선언한다.

 

 

필요한 헤더를 선언한다.

 

 

다른 에셋을 가져오는 것과 비슷하게 행동 트리와 블랙보드를 가져온다.

  • 둘 다 Class가 아닌 Object임에 주의한다.
  • Object 타입이라고 해서 하나의 AI에만 사용할 수 있는 것은 아니다.

 

 

컨트롤러가 폰을 소유할 때 블랙보드를 설정하고 행동 트리를 실행한다.

  • AAIController::UseBlackboard(UBlackboardData*, Blackbord) 함수를 통해 컨트롤러의 Blackboard를 설정할 수 있다.
    Blackboard는 AAIController의 멤버 변수이다.
  • AAIController::RunBehaviorTree(UBehaviorTree*) 함수를 통해 행동 트리를 실행할 수 있다.

 

두 함수 모두 성공 여부를 반환한다.

 

 

플레이하면 AI 컨트롤러별로 행동 트리가 실행 중인 모습을 디버깅할 수 있다.

 

 

블랙보드를 열어 AI의 초기 위치를 저장하는 Vector3 타입의 HomePos와 새로운 위치를 저장하는 PatrolPos 키를 만든다.

 

 

ABAIController에서 행동 트리를 실행하기 전에 HomePos에 초기 위치를 설정한다.

  • 어디서든 참조하기 쉽게 편의상 static const 키워드로 ABAIController에 블랙보드 키 이름을 선언했다.

 

 

블랙보드 관련 기능을 사용하기 위한 헤더를 포함하고 블랙보드 키 이름을 초기화한다.

  • 선언부에는 static const를 사용했지만 구현부에서는 const 키워드만 사용한다.

 

 

OnPossess() 함수에서 행동 트리를 실행하기 전에 Blackboard->SetValueAsVector() 함수를 이용해 HomePosKey에 초기 위치를 저장한다.

 

 

플레이 중 행동 트리에서 블랙보드 키 값들을 확인할 수 있다.

 

 

다음 위치를 구해 PatrolPos 블랙보드 키에 저장하는 일을 수행할 새로운 태스크를 만든다.

 

 

행동 트리를 사용하기 위해 AIModule을 추가한 것과 별개로 태스크 관련 기능을 사용하기 위해서는 GameplayTasks라는 모듈을 추가해야 한다.

 

 

새로운 클래스를 만들었으므로 CoreMinimal.h를 ArenaBattle.h로 변경해준다.

 

 

행동 트리는 태스크를 실행할 때 태스크 클래스의 ExecuteTask()라는 멤버 함수를 실행한다.

 

ExecuteTask() 함수는 다음 넷 중 하나의 값을 반환해야 한다.

이 값에 따라 컴포짓 내에서 다음 태스크를 수행할지 결정된다.

  • Aborted
    도중에 중단됐고 결과적으로 실패했다.
  • Failed
    실행했지만 실패했다.
  • Succeeded
    성공했다.
  • InProgress
    아직 수행 중이고 나중에 FinishLatentTask() 함수를 통해 종료하게 된다.


NodeMemory는 I객체나 구조체의 포인터 통해 값을 유지하기 위해 사용된다.

 

 

다음 위치를 찾기 위한 내비게이션 시스템과 블랙보드를 다루기 위한 헤더를 추가한다.

 

 

생성자에서 NodeName을 지정해주면 행동 트리에서 이 이름으로 표시할 수 있다.

 

 

UBehaviorTreeComponent.GetAIOwner() 함수를 통해 행동 트리의 AI 컨트롤러를 가져올 수 있다.

 

UBehaviorTreeComponent.GetBlackboardComponent() 함수를 통해 행동 트리의 블랙보드를 가져올 수 있다.

 

AAIController->GetPawn() 함수를 통해 AI 컨트롤러가 빙의한 폰을 가져올 수 있다.

 

APawn->GetWorld() 함수를 통해 폰이 속한 월드를 가져올 수 있다.

 

도중에 오류가 발생했거나 이동 가능한 위치를 찾이 못했으면 실패, 아니면 PatrolPos 블랙보드 키에 새 위치를 저장하고 성공을 반환한다.

 

 

행동 트리에 FindPatrolPos 태스크와 Move To 태스크를 추가한다.

  • Move To 태스크의 블랙보드 키에 PatrolPos를 목적지로 지정해주면 그 위치로 이동하게 된다.

 

 

행동 트리로 AI를 이동시키는 데 성공했다.

 

profile

Make Unreal REAL.

@diesuki4

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그