BehaviorTree 와 BlackBoard - UnrealEngine
최근 NPC 를 만들면서 이 두 종목에 대해 많이 다루었다. 그러나 아직 이게 무엇인지는 정확하게 알지 못하기에 이번 시간엔 이 둘의 역할과 사용법에 대해 알아보려고 한다.
BehaviorTree
Tree 라는 이름이 붙은 것에서 알 수 있듯이 기본적으로 트리형 구조를 띈다. 즉, 부모 노드부터 자식 노드를 순회하며 왼쪽에서 오른쪽 순서대로 노드를 순회한다는 의미이다. Behavior Tree 는 게임 캐릭터나 인공지능의 행동 로직을 시각적으로 설계하고 관리할 수 있도록 도와주는 도구이다. 복잡한 AI 행동을 트리 구조로 표현하여, 각 노드를 통해 조건, 액션, 흐름 제어 등을 정의하고 연결함으로써 AI의 의사 결정 과정을 직관적으로 파악하고 수정할 수 있게 만들어준다.
트리 구조를 띄는 만큼 여러 노드들이 존재하는데 처음 시작인 Root Node 부터 시작해 Selector Node, Sequnce Node, Parallel Node, Task Node 등이 존재한다. 간단하게 설명해 보겠다.
컴포지트 노드 (Composite Node) : 자식 노드의 실행 순서를 결정하는 노드로, Selector, Sequence, Parallel 등이 있다.
|
|-Selector : 자식 노드를 왼쪽에서 오른쪽 순서로 실행하며, 자식 노드 중 하나가 실행되면 처리를 멈춘다.
|
|-Sequence : 자식 노드를 왼쪽에서 오른쪽 순서로 실행하며, 모든 노드가 성공해야 전체가 성공으로 처리된다.
|
|-Parallel : 자식 노드를 동시에 실행한다.
Task Node : 실제로 AI가 수행하는 액션을 정의하는 노드로, 움직임, 공격, 대화 등이 있다.
즉,. 컴포지트 노드는 자식 노드들의 실행 순서를 결정해 어떤 Task 를 처리할지 결정하는 역할이며 Task Node 는 해당 NPC 가 수행해야할 동작을 의미한다. 다음은 BlackBoard 에 대해 알아보자.
BlackBoard
Behavior Tree 가 AI 의 행동 로직을 시각적으로 설계하고 관리하는 도구라면 블랙보드는 이의 핵심적인 구성 요소이다. 블랙보드는 Behavior Tree 에서 AI 가 의사 결정을 내리는데 필요한 다양한 정보를 저장하고 공유하는 중앙 저장소 역할을 한다. AI 의 상태, 목표, 주변 환경 정보 등 다양한 데이터를 블랙보드에 저장하고, 각 노드에서 이 데이터를 읽어오거나 수정하며 AI 의 행동을 결정하게 한다. 즉 Behavior Tree 에서 필요한 데이터들을 BlackBoard 에서 관리, 변경 하며 노드들이 블랙보드의 데이터에 따라 활성화 되거나 비활성화 된다.
BlackBoard 의 구성요소는 두 가지가 있다.
Key (키) : 데이터를 식별하는 이름표 역할을 한다. 각 키는 특정 데이터 유형을 가진다. 여기서 데이터 유형이란 정수, 부울, 벡터 등 여러 유형을 말하는 것이다.
Value (값) : 키에 해당하는 실제 데이터이다. AI 의 상태, 목표, 주변 환경 정보 등을 값으로 저장 할 수 있게 된다.
이 둘에 대해 간단히 알아보았다. 그러면 어떻게 이 둘을 사용하는가? 이는 노드를 생성하며 알아보자.
일단 비헤이비어 트리는 콘텐츠 드로우에서 다음과 같이 생성할 수 있다.
이렇게 생성된 비헤이비어 트리는 초기에 다음과 같은 모습을 보인다.
초기에는 루트 하나만 보일텐데 아래의 검은 네모칸을 드래그해 노드를 생성할 수 있다.
우리는 블랙보드 또한 사용할 것이기에 이것도 생성해보자. 이는 콘텐츠 드로우에서 비헤이비어 트리와 비슷하게 생성이 가능하다.
생성된 블랙보드를 비헤이비어 트리에 적용시켜 보겠다. 비헤이비어 트리의 디테일 패널에 가 블랙보드 에셋을 방금 만들어준 블랙보드로 선택해주자.
만약 블랙보드를 만든 뒤 비헤이비어 트리를 만든다면 자동으로 적용되어 있을 것이다. 이제 간단한 로직을 구성해 보겠다. 나는 전에 만들어 두었던 Task 들을 이용해 예시를 들어보겠다. 만약 플레이어가 보이지 않는다면 랜덤한 위치를 돌아다니고 플레이어를 보았다면 플레이어를 쫓아가는 로직이다.
보면 Root 아래에 Selector 를 연결하였다. 이는 앞서 말했듯이 자식 노드들 중 하나만 성공하면 실행을 멈추는 노드이다. 나는 플레이어가 보이거나 보이지 않거나 둘 중 한개의 액션을 NPC 가 취하게 만들어야 했기에 Selector 로 분기를 만들어 주었다. 보면 Patrolling 이라는 시퀀스에 파란색 Can't See Player 와 초록색 Change Speed 가 붙어있는 것을 볼 수 있다. 먼저 파란색 노드를 살펴보겠다.
파란색 노드를 만드는 방법은 다음과 같다. 노드에 우클릭을 하면 데코레이터 추가를 볼 수 있다.
그 전에 데코레이터에 대해 간단하게 설명해 주겠다. 데코레이터는 노드에 붙어 실행 흐름을 제어해주는 역할이다. 데코레이터를 통하여 자식 노드의 실행 조건을 설정할 수 있다. 즉, 특정 조건이 충족되었을 때만 자식 노드를 실행하거나, 실행 중인 자식 노드를 중단시키는 역할을 한다. 설명만 들어보면 Selector 가 있는데 이걸 왜 사용하는가? 싶은데 이는 둘의 기능이 비슷하면서도 결정적인 부분에서 차이가 나기 때문이다. 데코레이터 노드는 자식 노드의 실행 조건을 설정하는 노드이기 때문에 특정 조건이 충족되었을 때만 자식 노드를 실행하거나, 실행 중인 자식 노드를 중단시키는 역할을 한다. 그러나 Selector 는 자식 노드를 왼쪽에서 오른쪽 순서로만 실행하며, 성공한 노드를 찾으면 실행을 종료하게 된다.
결국은 데코레이터 노드에 의해 Selector 에 성공을 반환할지 말 것인지를 정할 수 있다는 것이다.
위의 로직에서는 Can't See Player 와 Can See Player 로 나누어 캐릭터를 볼 수 있는지 아닌지의 데코레이터를 설정해 이를 토대로 Selector 는 어떤 노드가 성공할 수 있는지를 결정한다. 데코레이터의 디테일을 살펴보자.
위는 Can't See Player 데코레이터 이다. CantSeePlayer 라는 블랙보드 키를 가져와 키 쿼리 (Value 값) 이 Is Not Set(false) 라면 이 노드가 성공 가능한 상태라는 의미이다. 이렇게 데코레이터는 블랙보드의 키를 이용하여 흐름을 제어할 수 있게 만들어 준다.
다음은 Service 이다. Patrolling 노드를 보면 파란 노드 말고도 아래에 초록색으로 생긴 노드가 존재한다. 이게 Service 이다. 서비스의 생성 방법은 아래와 같다.
데코레이터가 실행 조건을 설정하는 흐름 제어 노드라면 서비스는 특정 주기로 AI의 상태를 업데이트 하는 기능을 제공한다. 즉, 블랙보드에 저장된 데이터를 업데이트 하는데 사용한다는 의미이다. 아래의 예시를 통해 알아보겠다.
위의 예시를 보면 Change Speed 와 Is Player In Melee Range 가 존재한다. Change Speed 의 디테일을 살펴보겠다.
즉, Selector 에 의해 Can See Player 노드가 실행되면 SelfActor (자기 자신) 의 스피드를 600 으로 고정시키겠다는 의미이다. BTService 는 C++ 코드 또는 블루프린트로 작성이 가능하다.
마지막으로 Task Node 에 대해 알아보겠다. Task Node 는 작성해놓은 Task C++ 파일이나 Task 블루프린트에 작성된 내용을 실행시켜주는 노드이다. 아래 이미지에서 보이는 보라색 노드들이 Task Node 이다.
다음 Task Node 들은 Sequence 에 의해 11 -> 12 -> 13 순서대로 진행된다. Melee Attack 은 Is Player In Melee Range 가 true 일 경우에 공격을 시행하나 이가 false 일 때에도 무조건 Succeeded 를 반환시켜 주어야 한다. 그렇지 않으면 Sequence 노드는 Selector 에 의한 선택을 받을 수 없기 때문이다. 헷갈리면 위의 Behavior Tree 속성을 다시한번 보고오자.
오늘은 간단하게 BehaviorTree 와 BlackBoard 에 대해 알아보았다. 아직 이 부분이 미숙하기에 저장하기 위함과 배웠던 내용을 다시 한번 복습하기 위해 올린다. 다음엔 좀 더 정리된 내용으로 작성하겠다.