
유닛 AI를 어떻게 구현할지 고민을 꽤 했다. 상태 머신(State Machine)이 첫 번째 선택지였는데, 상태가 늘어날수록 전환 조건이 복잡해지는 게 걱정됐다. 찾아보다가 LimboAI라는 Godot 플러그인을 알게 됐고, 비헤이비어 트리(Behavior Tree)를 써보기로 했다.
비헤이비어 트리가 뭔가
비헤이비어 트리는 AI 행동을 트리 구조로 표현한다. 루트부터 시작해서 조건을 검사하고, 성공/실패에 따라 다음 노드로 넘어간다. Selector는 자식 중 하나가 성공하면 멈추고, Sequence는 모든 자식이 성공해야 다음으로 넘어간다.
Tiny Reign 유닛의 기본 흐름은 이렇다. 적이 감지 범위 안에 있으면 추격하고, 공격 범위 안에 들어오면 공격한다. 감지된 적이 없으면 깃발 위치로 이동하거나 대기한다. 이 흐름을 비헤이비어 트리로 표현하면 코드보다 훨씬 읽기 쉽다.
커스텀 태스크 작성
LimboAI에서 AI 행동 하나하나는 BTTask를 상속받아 구현한다. BTDetectEnemy, BTChaseEnemy, BTAttackEnemy 같은 식으로 각각 파일을 만들었다.
태스크는 _tick() 함수에서 매 프레임 실행되고 SUCCESS, FAILURE, RUNNING 중 하나를 반환한다. RUNNING을 반환하면 다음 프레임에도 계속 실행된다. BTChaseEnemy는 목표까지 이동하는 동안 RUNNING을 반환하고, 목표에 도달하면 SUCCESS를 반환하는 식이다.
유닛마다 트리를 바꿀 수 있다
몽크는 공격 대신 힐을 써야 해서 일반 유닛과 행동이 다르다. LimboAI에서는 .tres 파일로 비헤이비어 트리를 저장하고 유닛마다 다른 트리를 붙일 수 있다. archer_bt.tres, monk_bt.tres 이런 식으로 분리했다.
새 유닛을 추가할 때 기존 태스크를 재사용하면서 트리 구조만 바꾸면 된다. 코드를 복사할 필요 없이 트리 에디터에서 노드를 조합하면 됐다.
정리하자면
비헤이비어 트리는 처음 배우는 데 시간이 좀 걸린다. 그런데 일단 구조를 이해하고 나면 AI 행동을 추가하거나 수정하기가 상태 머신보다 훨씬 편하다. LimboAI는 Godot 4 에디터 안에서 트리를 시각적으로 편집할 수 있어서 더 좋았다. 여러 유닛 AI를 관리해야 한다면 추천한다.