바이브코딩으로 게임 만들기 #12 — 비헤이비어 트리 삽질기: BTSelector가 새 깃발을 무시한 이유

깃발 시스템을 구현하고 나서 이상한 버그가 생겼다. 깃발을 새로 배치해도 유닛들이 반응을 안 했다. 처음 배치된 깃발로만 계속 이동하고, 키를 눌러서 다른 깃발로 바꿔도 꿈쩍도 하지 않았다.
증상
집결 깃발(1번)을 배치하면 유닛들이 거기로 움직였다. 그런데 돌격 깃발(2번)로 바꿔도 유닛들은 이전 위치 근처에서 계속 맴돌았다. FlagManager에서 활성 깃발이 바뀐 건 확인했다. 그런데 BT 태스크는 새 목표를 인식하지 못하는 것 같았다.
원인: BTSelector의 캐싱
한참 디버깅하다가 LimboAI 문서에서 단서를 찾았다. BTSelector는 자식 태스크 중 하나가 RUNNING 상태면 다음 틱에도 그 태스크를 계속 실행한다. 다른 자식을 다시 검사하지 않는다는 거다.
BTMoveToFlag가 깃발을 향해 이동하는 동안 RUNNING을 반환하고 있었다. BTSelector 입장에서는 실행 중인 태스크가 있으니 조건을 다시 확인할 이유가 없었던 것이다. 깃발이 바뀌어도 태스크는 이전 목표를 향해 계속 달리고 있었다.
해결: BTDynamicSelector
LimboAI에는 BTDynamicSelector라는 노드가 있다. 일반 BTSelector와 다르게, RUNNING 중인 태스크가 있어도 매 틱마다 자식 태스크를 처음부터 다시 평가한다. 우선순위가 높은 조건이 충족되면 실행 중인 태스크를 중단하고 새 태스크로 전환한다.
BTSelector를 BTDynamicSelector로 바꾸고 나서 깃발을 바꾸면 유닛들이 즉시 새 깃발 방향으로 전환됐다. 코드 한 줄 바꾼 게 아니라 노드 타입 하나 바꾼 거였다.
언제 어느 걸 써야 하나
BTSelector는 실행 중인 행동을 끝까지 수행해야 할 때 쓴다. 공격 모션처럼 중간에 끊기면 안 되는 경우. BTDynamicSelector는 외부 상황이 바뀌면 즉시 반응해야 할 때 쓴다. 깃발 명령이나 위험 감지처럼 우선순위가 바뀌는 경우.
정리하자면
비헤이비어 트리를 쓰다 보면 이런 식의 미묘한 차이에서 버그가 생긴다. 증상은 “명령이 안 먹힌다”인데 원인은 노드 타입 하나 차이였다. LimboAI 쓴다면 BTSelector와 BTDynamicSelector의 차이를 미리 알아두는 게 삽질을 줄이는 지름길이다.


