최근 출시되는 AAA급 게임들을 플레이하다 보면, 플레이어의 전술에 맞춰 우회하거나 엄폐하고, 심지어 동료와 협동하여 플레이어를 압박하는 적들의 모습에 감탄하게 됩니다. 과거의 게임들이 단순히 플레이어를 향해 돌진하거나 정해진 구역을 배회하는 단순한 패턴을 보였다면, 현대의 게임 캐릭터들은 마치 살아있는 듯한 '지능'을 보여줍니다. 이러한 고도화된 인공지능을 구현하는 핵심 기술 중 하나가 바로 '행동 트리 알고리즘(Behavior Tree Algorithm)'입니다. 게임 개발자, 특히 AI 프로그래머를 꿈꾸는 분들이라면 반드시 정복해야 할 이 기술은 복잡한 AI 로직을 체계적이고 시각적으로 관리할 수 있게 해주는 강력한 도구입니다. 오늘은 행동 트리 알고리즘을 활용하여 똑똑하고 생동감 넘치는 게임 NPC AI를 구현하는 방법에 대해 심도 있게 다뤄보겠습니다.
1. 게임 NPC AI의 진화: FSM에서 행동 트리로
게임 개발의 역사 초기에는 유한 상태 기계(FSM, Finite State Machine)가 AI 구현의 표준이자 정석처럼 사용되었습니다. FSM은 '대기(Idle)', '추적(Chase)', '공격(Attack)'과 같은 명확한 상태(State)를 정의하고, 특정 조건(이벤트)이 발생하면 상태를 전환(Transition)하는 방식으로 작동합니다. 이 방식은 구조가 직관적이고 구현이 빠르다는 장점이 있어 여전히 간단한 AI에는 널리 사용됩니다.
하지만 게임의 규모가 커지고 NPC에게 요구되는 행동의 가짓수가 늘어나면서 FSM의 한계가 드러나기 시작했습니다. 상태가 늘어날수록 각 상태 간의 연결 고리가 기하급수적으로 복잡해지며, 코드를 유지 보수하기 어려운 이른바 '스파게티 코드' 문제가 발생하기 쉬웠습니다. 또한, 특정 행동 패턴을 다른 NPC에게 재사용하기 어렵다는 단점도 있었습니다.
여기서 등장한 혁신적인 해결책이 바로 행동 트리 알고리즘입니다. 행동 트리는 이름 그대로 NPC의 의사 결정 과정을 나무(Tree) 구조로 표현한 것입니다. 가장 상위의 뿌리(Root)에서 시작하여 가지(Branch)를 타고 내려가며 조건에 맞는 잎(Leaf) 노드인 실제 행동을 실행하는 계층적 구조를 가집니다. 이 방식은 모듈화가 용이하고, 특정 행동 패턴을 서브 트리(Sub-tree)로 만들어 재사용하기 쉬우며, 무엇보다 언리얼 엔진이나 유니티 같은 최신 상용 엔진에서 비주얼 스크립팅 형태의 에디터를 기본적으로 지원할 만큼 업계 표준으로 자리 잡았습니다.
행동 트리의 핵심 철학
행동 트리는 '어떻게(How)' 상태를 전환할 것인가보다는 '무엇을(What)' 할 것인가에 집중합니다. 상태 전이의 복잡한 그물망을 끊어내고, 계층적인 구조를 통해 우선순위를 명확히 함으로써 개발자가 의도한 대로 NPC가 행동하게 만듭니다. 이는 AI의 사고 과정을 사람이 이해하기 쉬운 논리적 흐름으로 시각화해 줍니다.
2. 행동 트리의 주요 구성 요소 완벽 분석
행동 트리 알고리즘을 제대로 구현하기 위해서는 트리를 구성하는 노드(Node)들의 역할과 작동 방식을 정확히 이해해야 합니다. 각 노드는 자식 노드로부터 반환받은 '성공(Success)', '실패(Failure)', '실행 중(Running)'이라는 세 가지 상태 값을 통해 전체 흐름을 제어합니다. 이 세 가지 반환 값의 조합만으로도 놀랍도록 복잡한 지능을 만들어낼 수 있습니다.
컴포짓 노드 (Composite Node): 흐름의 제어자
컴포짓 노드는 자식 노드들을 거느리며 그들의 실행 순서와 결과를 관리하는 분기점 역할을 합니다. 가장 대표적인 두 가지 유형은 다음과 같습니다.
- 셀렉터(Selector) 노드: 자식 노드 중 하나라도 성공하면 즉시 성공을 반환하고 실행을 멈춥니다. 주로 '우선순위가 있는 선택'을 할 때 사용됩니다. 예를 들어, 생존 게임의 NPC라면 '체력이 낮은가? -> 회복', '적이 보이는가? -> 공격', '할 일이 없는가? -> 순찰' 순서로 자식을 배치합니다. 앞선 조건이 충족되어 행동이 실행(성공)되면 뒤의 행동은 무시됩니다. 이는 프로그래밍의 OR 연산 혹은
if-else if구문과 유사합니다. - 시퀀스(Sequence) 노드: 자식 노드들이 모두 성공해야만 성공을 반환합니다. 도중에 하나라도 실패하면 즉시 실패를 반환하고 실행을 중단합니다. 이는 일련의 절차를 수행할 때 사용됩니다. 예를 들어, '문을 열고 들어가는 행동'을 구현한다면 '문 위치 확인 -> 문으로 이동 -> 문 열기 -> 진입'의 순서가 될 것입니다. 만약 '문으로 이동'에 실패했다면 '문 열기'를 실행해서는 안 되므로 시퀀스 노드가 적합합니다. 이는 AND 연산과 유사합니다.
데코레이터 노드 (Decorator Node): 조건과 변형
데코레이터 노드는 자식 노드를 오직 하나만 가질 수 있으며, 자식 노드의 실행 여부를 결정하거나 결과를 조작합니다. 디자인 패턴의 데코레이터 패턴과 유사하며, 프로그래밍의 조건문(If, While)이나 부정 연산자(Not)와 같은 역할을 합니다.
- 인버터(Inverter): 자식 노드의 결과를 반대로 뒤집습니다 (성공 -> 실패, 실패 -> 성공). '적이 보이지 않음'과 같은 조건을 만들 때 유용합니다.
- 리피터(Repeater): 자식 노드를 지정된 횟수만큼 또는 무한히 반복 실행합니다.
- 조건(Condition) 체크: 특정 변수(예: 플레이어와의 거리, 남은 총알 수, 체력 수치)를 확인하여 자식 노드의 실행을 허가하거나 차단합니다. 이를 통해 트리의 가지치기가 이루어집니다.
리프 노드 (Leaf Node): 실제 행동의 주체
트리의 가장 끝단에 위치하는 노드로, 더 이상 자식을 가질 수 없습니다. 실제로 게임 월드 내에서 동작을 수행하거나 구체적인 조건을 검사하는 역할을 합니다.
- 액션(Action): '이동하기', '공격 애니메이션 재생', '사운드 출력', '대기하기' 등 구체적인 행위를 담당합니다. 액션 노드는 행동이 완료될 때까지 '실행 중(Running)'을 반환하여 트리가 다른 행동으로 넘어가지 않도록 붙잡아둘 수 있습니다.
- 컨디션(Condition): '적이 시야에 있는가?', '총알이 남았는가?'와 같은 단순한 사실 여부를 판단하여 즉시 성공 또는 실패를 반환합니다.
3. 실전 구현: 경비병 NPC AI 설계하기
이론을 바탕으로 실제 잠입 액션 게임에 등장할법한 경비병 NPC의 행동 트리 알고리즘을 설계해 보겠습니다. 이 경비병은 평소에는 지정된 경로를 순찰하다가, 플레이어를 발견하면 추격하고, 공격 사거리 내에 들어오면 발포해야 합니다. 또한, 플레이어를 놓치면 다시 순찰로 복귀해야 합니다.
- 루트(Root) 설정: 트리의 시작점입니다. 매 프레임(혹은 지정된 틱)마다 여기서부터 신호가 내려갑니다.
- 최상위 셀렉터(Selector): 경비병의 최우선 행동을 결정합니다. [전투 모드] 혹은 [평시 모드] 중 하나를 선택해야 합니다. 전투가 더 높은 우선순위를 가집니다.
- 전투 시퀀스(Sequence) 설계:
- 조건(Decorator): '플레이어가 감지되었는가?' (이 조건이 실패하면 즉시 평시 모드로 넘어갑니다.)
- 전투 행동 셀렉터: 상황에 따른 전투 행동을 선택합니다.
- 사격 시퀀스: [공격 가능 거리인가?] -> [조준] -> [발포]
- 추격 액션: 사격이 불가능하다면 [플레이어 위치로 이동] (추격)을 수행합니다.
- 순찰 시퀀스(Sequence) 설계:
- 전투 조건이 실패했을 때(플레이어가 없을 때) 실행됩니다.
- [다음 웨이포인트 선정] -> [해당 지점으로 이동] -> [주변 두리번거리기(대기)]를 반복합니다.
이렇게 설계하면, 프레임마다 트리를 순회하면서 경비병은 상황에 맞는 최적의 행동을 지능적으로 선택하게 됩니다. 만약 추격 중에 플레이어가 연막탄을 써서 시야에서 사라지면, '플레이어 감지' 조건이 실패(Failure)를 반환하므로, 자연스럽게 상위 셀렉터를 타고 넘어가 순찰 모드로 복귀하게 됩니다. 이 모든 과정이 복잡한 if-else 중첩 없이 깔끔한 트리 구조로 해결됩니다.
4. 블랙보드(Blackboard) 시스템의 활용
행동 트리 알고리즘만으로는 복잡한 데이터를 효율적으로 관리하기 어렵습니다. 트리는 로직의 흐름을 담당할 뿐, 데이터를 저장하는 공간은 아니기 때문입니다. 이때 함께 사용되는 것이 바로 블랙보드(Blackboard) 패턴입니다. 블랙보드는 AI가 의사 결정을 내리는 데 필요한 데이터(변수)들을 저장하는 공용 메모리 저장소입니다.
- 데이터 공유의 중심: 예를 들어, 시야 센서 컴포넌트가 플레이어를 발견하면 블랙보드의
TargetObject라는 변수에 플레이어 객체를 할당합니다. 그러면 행동 트리의 각 노드들은 이TargetObject가 비어있는지(null) 아닌지를 확인하여 전투 모드 진입 여부를 결정합니다. - 의사소통의 매개체: 여러 개의 행동 트리가 하나의 블랙보드를 공유하여 분대 단위의 전술을 짤 수도 있고, 외부 이벤트 시스템(예: 폭발 소리 감지)이 블랙보드 값을 변경하여 AI에게 즉각적인 정보를 전달할 수도 있습니다.
따라서 효과적인 NPC AI를 구현하려면 행동 트리는 로직(Logic)을, 블랙보드는 데이터(Data)를 담당하도록 철저히 분리하여 설계해야 유지 보수성과 확장성을 모두 잡을 수 있습니다.
5. 행동 트리 구현 시 주의사항 및 최적화 팁
행동 트리 알고리즘은 강력하지만, 잘못 구현하면 게임 성능 저하의 주범이 될 수 있습니다. 다음은 현업에서 자주 사용되는 최적화 팁입니다.
- 틱(Tick) 빈도 조절: 모든 NPC가 매 프레임(60fps 기준 초당 60회)마다 전체 트리를 검사할 필요는 없습니다. 플레이어와 거리가 멀거나 중요도가 낮은 NPC는 0.1초나 0.5초에 한 번씩만 트리를 업데이트하도록 설정하여 연산 부하를 줄여야 합니다.
- 이벤트 기반 업데이트 (Observer Aborts): 데이터가 변경되었을 때만 트리를 재평가하도록 설정하면 불필요한 연산을 획기적으로 줄일 수 있습니다. 언리얼 엔진에서는 이를 'Observer Aborts'라고 부르는데, 예를 들어 '공격' 행동을 수행 중이라도 '체력' 값이 급격히 떨어지면 즉시 행동을 중단하고 '도망' 노드로 흐름을 강제 전환시키는 기능입니다.
- 순환 참조 및 무한 루프 방지: 설계 시 논리적 오류로 인해 특정 노드들 사이를 무한히 오가는 상황이 발생하지 않도록 주의해야 합니다. 항상 명확한 '종료 조건'이나 '실패 조건'을 마련해야 합니다.
- 복잡도 관리와 서브 트리: 트리가 너무 깊어지거나 비대해지면 가독성이 떨어져 디버깅이 힘들어집니다. 이럴 때는 트리의 일부(예: '엄폐물 찾기' 로직)를 떼어내어 별도의 파일인 '서브 트리(Sub-tree)'로 만들고, 이를 메인 트리에서 호출하여 사용하는 것이 좋습니다.
6. 결론: 살아있는 게임 세상을 위한 필수 기술
지금까지 행동 트리 알고리즘을 활용하여 지능형 게임 NPC를 구현하는 방법과 그 원리에 대해 알아보았습니다. 행동 트리는 단순히 NPC를 움직이게 하는 기능을 넘어, 캐릭터에 '지능'과 '성격'을 부여하는 창조적인 과정입니다. FSM의 경직된 구조를 넘어 더욱 유연하고 확장 가능한 AI 시스템을 구축하고 싶다면, 행동 트리는 선택이 아닌 필수입니다.
앞으로의 게임 개발 트렌드는 더욱 정교하고 인간적인 AI를 요구할 것입니다. 최근에는 머신러닝(Machine Learning)이나 GOAP(Goal Oriented Action Planning) 같은 기술과 행동 트리를 결합하여 더욱 진화된 형태의 AI가 연구되고 있습니다. 오늘 다룬 내용을 바탕으로 여러분만의 독창적이고 생동감 넘치는 NPC를 만들어, 플레이어들에게 잊지 못할 몰입감과 경험을 선사해 보시길 바랍니다. 끊임없는 테스트와 디버깅, 그리고 창의적인 로직 설계만이 플레이어의 마음을 움직이는 최고의 AI를 탄생시킬 것입니다.