NPC 행동: 상태 기계, 행동 트리, GOAP, AI 기반 NPC
나만의 NPC 행동 만들기
NPC는 무엇을 할지 어떻게 결정할까요? 상태 기계, 행동 트리, GOAP, 효용 AI, LLM 기반 NPC를 브라우저에서 바로 실행되는 3D 예시와 함께 설명합니다.
NPC 행동은 모델, 체력 바, 충돌 캡슐을 무언가를 원하는 것처럼 보이게 만드는 논리입니다. 이는 보통 매 프레임 또는 고정 틱마다 한 가지 질문에 반복해서 답합니다. 세상에 대해 내가 아는 것을 바탕으로, 다음에 무엇을 해야 하는가? 이 페이지의 나머지 모든 것 — 유한 상태 기계, 행동 트리, GOAP, 효용 AI, LLM 기반 의도 — 는 그 질문에 대한 서로 다른 답이며, 비용과 실패 방식도 각기 다릅니다.
우리는 Neta Studio의 3D 게임 빌더 안에서 브라우저 탭으로 실행되는 NPC를 만듭니다. 이 제약이 아래 조언의 방향을 결정합니다. 전용 서버도, 별도의 물리 스레드도, 틱마다 40밀리초 동안 탐색하는 계획기에 쓸 예산도 없습니다. 중급 노트북에서 렌더링 옆에서 돌릴 만큼 저렴하게 만들 수 없는 기법은 등급을 낮추거나 핵심 경로에서 빼냅니다. 이어지는 절에서는 우리가 실제로 출시하는 것과, 각 접근 방식이 어디서 무너지는지를 설명합니다.
간단한 답: NPC는 어떻게 결정하는가
NPC는 일정한 주기로 결정 함수를 실행하고, 세계 상태의 작은 일부(대상까지의 거리, 시야, 현재 체력, 마지막으로 알려진 위치)를 읽고, 이동 및 애니메이션 시스템이 실행할 수 있는 행동을 반환함으로써 결정합니다. 주기는 논리만큼이나 중요합니다. 100밀리초에서 250밀리초 틱으로 결정하고 이동과 충돌은 매 프레임 처리하면, 매 프레임 결정 논리를 돌리는 비용의 일부만 들고 대부분의 경우 플레이어가 구분할 수 없습니다.
네 가지 고전 모델은 복잡성이 어디에 있는지가 다릅니다. 상태 기계는 결정을 이름 붙은 상태 사이의 전이로 부호화합니다. 행동 트리는 선택자와 시퀀스 노드의 계층으로 부호화합니다. GOAP는 목표에 더해 사전 조건과 효과를 지닌 행동 집합으로 부호화하고 계획을 탐색합니다. 효용 AI는 모든 후보 행동에 대한 점수 함수로 부호화합니다. LLM 기반 NPC는 상위 수준 의도를 자연어로 부호화하고 그 결과를 더 저렴한 모델 중 하나에 넘깁니다.
실제로는 이들을 조합합니다. 효용 점수기가 목표를 고르고, 행동 트리나 상태 기계가 그것을 실행하며, 내비메시가 몸을 움직이고, 언어 모델은 대사와 의도에만 관여합니다. 이 페이지의 나머지 부분은 각 계층을 차례로 다루며 여러분이 마주칠 구체적인 실패 방식을 짚어 줍니다.
상태 기계 (FSM)
유한 상태 기계는 가장 저렴하게 쓸 수 있는 모델이며 대부분의 NPC에 맞는 첫 선택입니다. 대기, 순찰, 추격, 공격, 도주, 기절 같은 상태 집합과 그들 사이의 전이 조건 집합을 정의합니다. 매 틱마다 현재 상태에서 나가는 전이를 평가하고, 하나가 발동하면 전환합니다. 비용은 확인하는 전이 수에 비례하며, 그 수는 적습니다. 상태는 단일 변수라서 직렬화하고, 디버그하고, 편집기 오버레이에 표시하기가 매우 쉽습니다.
실패 방식은 진동입니다. 플레이어로부터 5.1미터에 있는 NPC가 순찰에서 추격으로 전이하고, 4.9미터로 이동한 뒤, 대칭적인 전이가 다시 순찰로 되돌립니다. 그러면 NPC는 경계에서 영원히 떨립니다. 해결책은 히스테리시스입니다. 추격은 5.0미터에서 시작하고 6.5미터에서 벗어나도록 하는 것입니다. 같은 문제는 플레이어가 기둥 뒤로 몸을 돌릴 때 깜빡이는 시야 확인에서도 나타나며, 해결책도 같습니다. 조건이 연속 N틱 동안 유지되도록 요구하거나, 마지막 목격 이후 2초 동안 NPC를 추격 상태로 붙잡아 두는 기억 타이머를 추가하는 것입니다.
두 번째 실패 방식은 전이 얽힘입니다. 상태가 다섯 개면 전이가 열 개 필요하고, 열 개면 마흔다섯 개에 가까워집니다. 새 상태는 그것에 도달할 수 있는 모든 상태와 연결해야 하며, 조건들은 서로 겹쳐서 이해하기 어려워집니다. 전이 표가 한 화면에 들어가지 않게 되는 순간이 행동 트리로 옮겨 갈 신호입니다.
순찰 경비병을 위한 구체적인 유한 상태 기계: 대기(2초 기다림) → 순찰(다음 웨이포인트로 이동) → 경계(소리 쪽으로 회전, 1.5초) → 추격(달리기 속도로 마지막으로 알려진 위치로 이동) → 공격(2미터 이내이고 재사용 대기시간이 끝났을 때) → 수색(마지막으로 알려진 위치 근처를 8초 동안 배회) → 순찰. 상태가 일곱 개, 전이가 대략 열두 개이며, 수색 상태의 기억 타이머가 NPC를 덜컥거리지 않고 신중하게 행동하는 것처럼 보이게 만듭니다.
- 단일 변수 상태현재 상태는 하나의 열거형입니다. 디버깅은 스택에서 의도를 재구성하는 것이 아니라 값을 하나 읽는 일입니다.
- 모든 임계값에 히스테리시스양방향으로 전이할 수 있는 거리나 타이머는 들어오는 값과 나가는 값을 다르게 두어야 합니다. 그렇지 않으면 NPC가 경계에서 진동합니다.
- 전이 예산표를 유지할 수 없게 되기 전에 상태를 대략 열 개 이하로 유지하세요. 그 이상이면 행동 트리로 전환하거나 하위 기계로 나누세요.
행동 트리
행동 트리는 전이 표를 계층 구조로 대체합니다. 리프 노드는 행동(대상으로 이동, 애니메이션 재생, 대기)과 조건(플레이어가 보임, 체력이 기준 이하)입니다. 두 가지 복합 노드 유형이 대부분의 일을 합니다. 선택자는 자식들을 순서대로 시도하고 하나가 성공하는 즉시 성공을 반환합니다. 이것이 우선순위 목록입니다. 시퀀스는 자식들을 순서대로 실행하고 하나가 실패하는 즉시 실패합니다. 이것이 단계별 절차입니다.
확장성의 장점은 조합 가능성입니다. 새 행동을 추가한다는 것은 선택자 아래에 하위 트리를 추가하는 것이지, 새 상태를 그것에 도달할 수 있는 모든 상태와 연결하는 것이 아닙니다. 우선순위는 자식 순서에 명시적으로 드러나며, 이는 서로 겹치는 불리언 조건 집합보다 훨씬 읽기 쉽습니다. 하위 트리는 재사용할 수 있습니다. 같은 '소리 조사' 분기가 경비병, 개, 민간인에게 모두 통합니다.
트리가 자동으로 더 빠른 것은 아닙니다. 순진한 구현은 틱마다 트리 전체를 다시 평가하는데, 그래서 결정은 100~250밀리초 주기로 계속 틱하고, 현재 실행 중인 행동이 매 프레임 이동을 이끌도록 합니다. 오래 걸리는 행동은 트리가 기억하는 실행 중 상태가 필요합니다. 그렇지 않으면 행동이 틱마다 처음부터 다시 시작되고 NPC가 경로 시작점으로 미끄러져 돌아갑니다. 행동 트리를 제공하는 대부분의 엔진 — Unity의 Behavior 패키지, Unreal의 내장 트리, 또는 TypeScript로 직접 만든 작은 구현 — 은 실행 중 반환값으로 이를 처리하지만, NPC가 끊길 때 가장 먼저 확인해야 할 것이 바로 이것입니다.
전투 NPC를 위한 선택자 구조를 우선순위 순서로 나타내면 다음과 같습니다. (1) 체력이 25퍼센트 미만이면 시퀀스 [엄폐물로 도주, 회복]; (2) 플레이어가 보이고 2미터 이내이면 시퀀스 [플레이어 쪽으로 회전, 공격]; (3) 플레이어가 보이면 시퀀스 [측면 이동 위치로 이동, 공격]; (4) 마지막으로 알려진 위치가 설정되어 있으면 수색; (5) 순찰. 선택자는 조건이 통과하는 첫 번째 분기를 고릅니다. 그 자식들의 순서를 바꾸는 것이 코드를 건드리지 않고 공격성을 조율하는 방법입니다.
- 선택자 노드성공하는 첫 자식에서 성공을 반환합니다. 대체 우선순위에 사용하세요. 가장 구체적인 행동을 시도하고, 그다음, 마지막으로 기본값을 시도합니다.
- 시퀀스 노드실패하는 첫 자식에서 실패합니다. 첫 단계가 일어나지 않으면 두 번째 단계가 무의미한 순차 절차에 사용하세요.
- 실행 중 상태오래 걸리는 행동은 실행 중을 반환하고 멈춘 지점에서 재개해야 합니다. 즉시 성공을 반환하면 트리가 틱마다 행동을 다시 시작하고 이동이 깨집니다.
- 블랙보드트리가 읽고 쓰는 사실을 위한 공유 키-값 저장소입니다. 대상 위치, 경계 수준, 재사용 대기시간 만료 같은 것입니다. 작고 형식이 지정된 상태로 유지하세요.
GOAP(목표 지향 행동 계획)
GOAP는 모델을 뒤집습니다. 언제 전이할지를 설명하는 대신, 행동이 무엇을 하고 무엇을 요구하는지를 설명하며, 계획기가 목표에 도달하는 순서를 탐색합니다. 각 행동에는 사전 조건, 효과, 비용이 있습니다. '공격'은 무기 소지와 사거리 내를 요구하고 대상 사망을 설정합니다. '무기 획득'은 무기 사용 가능을 요구하고 무기 소지를 설정합니다. '대상으로 이동'은 대상 인지를 요구하고 사거리 내를 설정합니다. 계획기는 목표에서 시작해 거꾸로 작업하며, 효과가 목표를 충족하는 가장 저렴한 행동 사슬을 찾습니다.
이는 세계 상태가 풍부하고 올바른 순서가 명백하지 않을 때 이득이 됩니다. 시야를 끊고, 재장전하고, 위치를 재조정한 뒤 교전해야 하는 NPC는 설계자가 명시적으로 작성한 적 없는 계획을 만들어 냅니다. 계획기가 사전 조건 그래프에서 순서를 발견했기 때문입니다. 또한 제작 과정에서도 이득이 됩니다. 새 아이템이나 능력을 추가한다는 것은 행동 정의 하나를 추가하는 것이지, 그것을 알아야 하는 모든 상태 기계를 편집하는 것이 아닙니다.
대가는 탐색입니다. 순진한 계획기는 계획을 찾을 때까지 행동 조합을 탐색하며, 행동과 상태 변수를 추가할수록 폭발적으로 커질 수 있습니다. 표준적인 완화책은 휴리스틱 — 목표까지의 거리 추정을 사용하는 상태 그래프 위의 A* — 과 엄격한 확장 한도입니다. 우리 브라우저 환경에서는 계획을 결정 틱에 계산하고 노드 확장을 몇백 개로 제한하며, 이를 초과하는 것은 캐시된 계획이나 단순한 행동 트리 분기로 대체합니다. 솔직한 한계는 이렇습니다. GOAP는 의미 있는 행동 순서의 수가 많을 때 탐색 비용을 감수할 가치가 있고, NPC가 늘 세 가지 일만 할 때는 낭비입니다.
- 사전 조건과 효과각 행동은 실행되려면 무엇이 참이어야 하고 무엇을 바꾸는지 선언합니다. 계획기는 코드가 아니라 그 선언을 바탕으로 추론합니다.
- 목표에서 거꾸로 탐색원하는 상태에서 시작해 그것을 만들어 내는 효과를 지닌 행동을 찾고, 그다음 그 사전 조건에 대해 재귀합니다. 보통 순방향 탐색보다 저렴합니다.
- 확장 한도계획 호출당 노드 확장 수를 제한하세요. 한도를 넘으면 대체하고, 계획기가 프레임을 지연시키지 못하게 하세요.
- 계획 캐싱세계 상태가 의미 있게 바뀔 때만 다시 계획하세요. 정적인 상황에서는 이전 계획을 비용 없이 재사용합니다.
효용 AI
효용 AI는 모든 선택지에 점수를 매기고 가장 높은 것을 고릅니다. 각 후보 행동에는 세계 상태를 숫자로 대응시키는 함수가 있고, 보통 0에서 1 사이로 정규화됩니다. '도주'는 체력이 낮고 적이 가까울 때 높은 점수를 받습니다. '공격'은 대상이 사거리 내이고 탄약이 0보다 많을 때 높은 점수를 받습니다. '배회'는 다른 긴급한 일이 없을 때 중간 정도의 점수를 받습니다. NPC는 최댓값을 취하며, 선택적으로 작은 무작위 흔들림을 더해 동점이 예측 불가능하게 해소되도록 합니다.
이것은 가장 예상 밖의 캐릭터를 만들어 내는 모델입니다. 행동이 명시적 규칙이 아니라 곡선들의 상호작용에서 창발하기 때문입니다. 점수 가중치가 조금 다른 두 NPC는 분기 코드 없이도 눈에 띄게 다르게 행동하며, 이 때문에 성격 변화에 자연스럽게 어울립니다. 또한 실행 중에 조율하기 가장 쉬운 모델입니다. 디버그 패널에 가중치를 노출하고 게임이 돌아가는 동안 끌어 조정하면 됩니다.
실패 방식은 평평하거나 잡음이 많은 효용입니다. 모든 행동이 0.4점이면 NPC는 우유부단해 보이고 끊임없이 전환합니다. 최고 점수가 매 틱마다 두 근접한 값 사이에서 뒤집히면, 잘못된 상태 기계와 같은 진동이 생깁니다. 표준 해결책은 최소 유지 기간입니다. 행동이 한 번 선택되면 정해진 기간 동안 최소한 유지하는 것이며, 500밀리초가 합리적인 기본값이고, 최고 점수가 확약된 행동의 점수를 의미 있는 폭으로 넘어설 때는 예외입니다. 곡선도 중요합니다. 체력에 대한 선형 반응 곡선은 매 전투에서 정확히 같은 순간에 도주하는 NPC를 만들어 냅니다. 지수나 로지스틱 곡선은 상황에 따라 움직이는 임계값을 만들어 냅니다.
- 행동별 점수 함수각 행동은 세계 상태를 0에서 1 사이의 효용으로 대응시킵니다. 서로 독립적으로 유지해 하나를 조정할 때 다른 것들을 건드리지 않아도 되게 하세요.
- 최소 유지 기간한 번 선택되면 최소 기간 동안 행동을 유지합니다. 거의 같은 점수 사이에서 틱마다 뒤집히는 것을 막습니다.
- 조율 가능한 가중치가중치를 실시간으로 노출하세요. 가중치만 다른 두 NPC가 서로 다른 캐릭터로 읽히며, 이는 사용할 수 있는 가장 저렴한 성격 시스템입니다.
- 반응 곡선선형 곡선은 고정된 임계값을 만듭니다. 로지스틱 곡선은 마법의 숫자가 아니라 상황에 반응하도록 NPC를 만듭니다.
내비게이션과 충돌
결정 논리는 목적지를 고릅니다. 내비게이션은 몸을 그곳으로 데려갑니다. 내비메시는 삼각분할된 보행 가능 표면입니다. 길 찾기는 그 다각형 그래프 위에서 A*를 실행하고, 결과는 NPC가 따라가는 다각형 모서리 목록입니다. 우리는 로드 시점에 레벨 형상으로부터 내비메시를 만들어 캐시합니다. 매 프레임 다시 만드는 것이 브라우저 게임에서 프레임률을 떨어뜨리는 가장 손쉬운 방법이기 때문입니다.
스티어링은 마지막 몇 미터와 순간순간의 이동을 처리합니다. 추적과 도착은 전방 벡터를 만들어 내고, 분리는 NPC를 주변 에이전트로부터 밀어내며, 장애물 회피는 문이나 다른 캐릭터 같은 동적 방해물을 피해 경로를 휘게 합니다. 스티어링은 매 프레임 실행하고, 길 찾기는 목적지가 바뀌거나 경로가 무효화될 때만 실행하세요. 모든 NPC에 대해 매 프레임 경로를 다시 계산하는 것은 흔하고 피할 수 있는 비용입니다.
충돌은 NPC가 벽을 통과해 걷는 것을 막는 단계입니다. 내비메시는 경로를 보행 가능한 지면 위에 유지하지만, 스티어링과 물리는 여전히 캐릭터를 형상 속으로 밀어 넣을 수 있으므로, 캐릭터 컨트롤러가 이동 후에 충돌체에 대한 침투를 해소합니다. 우리의 등각 세계 — Mini World와 The Cyclops' Island — 에서 눈에 보이는 NPC 버그 대부분이 여기서 나옵니다. 캐릭터 반지름보다 좁은 틈을 통과하는 경로, 또는 모퉁이에서 에이전트를 벽으로 밀어 넣는 스티어링 벡터입니다. 내비메시에서 에이전트 반지름을 올바르게 설정하고, 분리 힘을 컨트롤러의 밀어내기 강도 아래로 유지하면 대부분이 해결됩니다.
01
로드 시점에 정적 형상으로부터 내비메시를 굽고 캐시하세요. 매 프레임 다시 만들지 마세요.
02
에이전트 반지름과 높이를 모델의 경계 상자가 아니라 캐릭터의 실제 충돌 캡슐에 맞게 설정하세요.
03
목적지가 바뀌거나, 경로가 막히거나, NPC가 임계값 이상으로 경로를 벗어났을 때만 길 찾기를 실행하세요.
04
스티어링, 분리, 장애물 회피를 매 프레임 실행하되 분리 힘은 상한을 두세요.
05
이동 후 충돌체에 대한 충돌을 해소한 다음, 캐릭터를 내비메시 위로 다시 스냅하세요.
06
경로와 내비메시를 디버그 오버레이에 기록하고 시각화하세요. 버그는 보통 논리적이지 않고 공간적입니다.
LLM 기반 NPC
언어 모델은 내비게이션 계층이 아니라 의도와 대사 계층에서 그림을 바꿉니다. 길 찾기, 스티어링, 충돌은 그대로입니다. 응답하는 데 수백 밀리초가 걸리는 모델은 매 프레임 움직여야 하는 몸을 이끌 수 없기 때문입니다. 모델이 할 수 있는 일은 플레이어의 말을 고려해 어떤 목표가 말이 되는지 결정하고, 캐릭터에 맞는 응답 텍스트를 생성하고, 상황이 모호하고 직접 작성한 규칙이 평평한 결과를 낼 때 상위 수준 선택지 중에서 고르는 것입니다.
실용적인 구조는 두 가지 속도를 분리합니다. 즉각적인 반응 — 공격, 도주, 엄폐 — 은 한두 프레임 안에 일어나야 하므로 상태 기계나 효용 점수기에서 실행합니다. 심사숙고한 의도는 훨씬 느린 주기로, 아마 상호작용마다 한 번 또는 몇 초마다 모델을 통과하며, 그 출력은 빠른 계층이 실행하는 구조화된 명령입니다. 목표를 추종으로 설정, 플레이어에 대한 태도를 적대적으로 설정, 대사 한 줄 설정 같은 것입니다. 모델은 위치를 직접 건드리지 않습니다.
솔직한 한계는 지연, 비용, 결정성입니다. 응답 시간이 길어서 NPC는 입력을 즉시 제스처나 짧은 준비된 대사로 인지해야 하며, 그렇지 않으면 고장 난 것처럼 읽힙니다. 비용은 호출 빈도에 비례하므로 묶어서 호출하고 캐시합니다. 그리고 출력이 비결정적이므로 게임에 도달하기 전에 스키마에 대해 검증해야 합니다. 구문 분석에 실패한 자유 텍스트 응답은 대화 시스템을 중단시키지 말고 기본 대사로 대체해야 합니다. 모델을 형식이 지정된 인터페이스 뒤의 불안정한 서비스로 취급하면 통합이 견고하게 유지됩니다.
- 형식이 지정된 명령 출력모델은 게임 코드가 구문 분석하는 산문이 아니라 구조화된 의도를 반환합니다. 스키마에 대해 검증하고 잘못된 형식은 거부하세요.
- 두 속도 루프반응에는 빠른 로컬 논리를, 심사숙고에는 느린 모델 호출을 사용하세요. 이동 경로에 네트워크 왕복을 절대 넣지 마세요.
- 즉각적 인지입력이 도착하는 즉시 제스처나 짧은 준비된 대사를 재생하고, 모델이 응답하면 교체하세요. 지연을 가립니다.
- 우아한 대체호출이 실패하거나 시간 초과되면 상태 기계의 기본 응답으로 대체하세요. NPC는 모델을 기다리며 멈춰서는 안 됩니다.
그것이 속한 세계로 걸어 들어가기
스프라이트는 설 자리가 없는 캐릭터입니다. 마지막 단계는 다른 누구도 제공하지 않는 것입니다. 방금 만든 시트를 누군가 걸어 다닐 수 있는 세계에 넣는 것입니다. 작은 섬, 행성, 비 내리는 거리 모퉁이 같은 곳입니다. 이 사이트의 모든 세계는 하나의 글로 된 묘사에서 만들어져 플레이할 수 있는 페이지로 공개되었습니다.
3D 게임 빌더로 돌아가기
완성된 3D 세계, 게임, 장면 31개가 모두 한 페이지에 있습니다 — 하나같이 플레이할 수 있습니다.