Retry
기억이 행동이 되는 세계

- →
목차
RETRY
플레이어의 행동과 세계의 사건을 기억하고, 그 맥락에 따라 스스로 다음 행동을 선택하는 Live World NPC 시스템
개발 진행 중 · Unreal Engine 5.8 · C++ · Behavior Tree · Navigation System · HTTP/JSON · UMG · Local LLM
개요
오픈월드 게임을 플레이하면서 가장 인상 깊었던 순간은 내가 한 행동이 이후의 세계에 영향을 주고, NPC가 그 결과를 기억하는 것처럼 느껴질 때였습니다. 기존 게임에서도 범죄, 평판, 관계 같은 값을 누적하고 그 수치에 따라 NPC의 반응을 변화시킬 수 있습니다. 하지만 세계가 기억해야 할 사건과 관계가 많아질수록, 각각의 조합에 대응하는 대사와 행동 조건 역시 개발자가 직접 만들어야 합니다. 예를 들어 플레이어를 적대하는 경비병이 단순히 낮은 평판 수치 때문에 공격하는 것이 아니라, 과거 플레이어가 죽인 NPC의 부모였기 때문에 그 사건을 기억하고 있다면 어떨까 생각했습니다. 경비병은 이전 사건을 떠올리며 플레이어에게 분노를 표현하고 무기를 꺼낼 수 있습니다. 실제 게임에서 실행되는 것은 대사 출력, 무기 장착, 적대 상태 전환처럼 개발자가 미리 만들어 둔 행동이지만, 어떤 행동을 선택할지 NPC의 기억과 현재 상황을 바탕으로 판단하게 한다면 플레이어에게는 자신의 행동이 세계에 계속 남아 있는 것처럼 보일 수 있습니다. RETRY는 이러한 살아 있는 세계, Live World를 만드는 것을 목표로 시작한 프로젝트입니다. 생성형 AI가 이동이나 전투 자체를 만들어내도록 하지 않습니다. NPC와 조직은 자신이 직접 경험하거나 전달받은 사건을 기억하고, AI는 현재 상황과 관련된 기억을 바탕으로 개발자가 구현한 행동과 명령의 조합 중 적절한 대응을 선택합니다.
AI는 무엇을 할지 판단하고, C++은 실제로 가능한지 검증하며, Behavior Tree는 어떻게 행동할지를 실행합니다.
왜 전장 시나리오인가
Live World 구조를 검증하기 위한 첫 환경으로 현대 전장을 선택했습니다. 밀리터리 분야에 대한 개인적인 관심과 이해도가 높았고, 군 조직은 관측 → 보고 → 정보 공유 → 상급자 판단 → 명령 → 수행 결과 보고라는 정보와 행동의 흐름이 명확하기 때문에 계층형 기억과 판단 구조를 검증하기 적합하다고 판단했습니다. 현재 시나리오는 미군과 소련군이 하나의 전략적 요충지를 두고 경쟁하는 상황을 가정합니다. 정찰 그룹이 직접 관측한 도로와 적의 상태는 즉시 모든 아군에게 알려지지 않습니다. 정찰 결과가 보고되어야 상급 조직의 작전 정보가 갱신되고, 지휘부는 자신이 알고 있는 정보만을 근거로 다음 임무를 결정합니다.
정찰 명령
→ 현장 관측
→ 보고
→ 상급 조직의 정보 갱신
→ 새로운 작전 판단
→ 후속 명령
→ NPC 행동
→ 결과 보고
현재는 군사 시나리오를 첫 검증 환경으로 사용하지만, 프로젝트의 목표는 군사 지휘 시스템 자체에 한정되지 않습니다.
World Event
→ 누가 경험했는가
→ 무엇을 기억했는가
→ 누구에게 전달됐는가
→ 현재 기억과 관계를 바탕으로 판단
→ 게임에 존재하는 행동을 선택
→ 새로운 World Event
이 순환 구조가 만들어진다면 시대적 배경이나 장르와 관계없이 적용할 수 있으며, 특히 플레이어가 세계에 미치는 영향이 큰 RPG와 오픈월드 게임에서 활용 가능성이 높다고 보고 있습니다.
주요 기술 구현
개인의 기억과 조직의 정보를 분리한 계층형 기억 구조
NPC가 게임 월드의 모든 정보를 알고 있는 전지적 AI처럼 행동하지 않도록 정보의 소유 범위를 분리했습니다.
개인 기억
Personal Memory
↓ 관측 / 전달
그룹 기억
Group Memory
↓ 구조화된 보고
팀 작전 정보
Team Operational Memory
Personal Memory에는 NPC가 직접 경험한 사건과 감정, 관계 정보를 저장합니다.
Group Memory는 그룹 구성원이 공유한 지역 상황과 현재 임무를 관리합니다.
상위 조직이 사용하는 Team Operational Memory에는 개인 기억 전체를 복사하지 않고, 실제 보고 과정을 통과한 작전 정보만 저장합니다.
따라서 정찰 NPC가 새로운 사실을 발견하더라도 상급 조직은 이를 즉시 알지 못합니다.
Route A 실제 상태: 차단됨
정찰 NPC:
Route A 차단 관측
HQ:
아직 알 수 없음
↓ Report 수신
HQ:
Route A = 차단됨
이를 통해 같은 월드에서도 조직마다 서로 다른 정보를 가지고 판단할 수 있도록 구성했습니다.
기억과 상황을 행동으로 연결하는 판단 구조
생성형 AI에게 NPC 전체를 직접 제어하도록 하면 결과를 예측하거나 검증하기 어렵습니다. 따라서 판단과 실행의 책임을 분리했습니다.
Memory / World Context
↓
상위 판단
↓
Structured Command
↓
C++ 검증
↓
Mission 변환
↓
NPC Decision
↓
Behavior Tree
상위 판단 계층은 무엇을 달성할지 결정합니다. C++ 실행 계층은 해당 명령이 현재 월드에서 실제로 수행 가능한지 확인합니다. Behavior Tree는 이동, 탐색, 엄폐, 전투처럼 어떻게 수행할지를 담당합니다. LLM이 NavMesh 좌표나 Behavior Tree Task를 직접 생성하지 않도록 경계를 두어, 생성형 판단을 사용하면서도 게임 실행의 안정성과 재현성을 유지하는 것을 목표로 했습니다.
조합 가능한 구조화 명령
AI가 자유로운 자연어로 행동을 만들어내는 대신, 개발자가 정의한 명령 공간 안에서 행동을 선택하도록 했습니다. 명령은 동사와 대상을 분리해서 표현합니다.
Recon + Area
Recon + Route
Secure + Area
Defend + Position
Block + Route
이를 통해 새로운 행동을 각각 완성된 명령으로 계속 추가하는 대신, 동사와 대상의 조합으로 명령 공간을 확장할 수 있도록 설계했습니다.
모든 조합을 허용하지는 않습니다.
FCommandIntent를 통해 명령을 구조화하고, 허용된 조합인지와 발행 권한, 대상 존재 여부, 현재 상태 등을 C++에서 검증합니다.
AI는 이 제한된 행동 공간 안에서 현재 기억과 상황에 맞는 선택을 담당합니다.
의미적 명령을 실제 월드 행동으로 변환
상위 계층에서 생성되는 명령은 구체적인 좌표가 아닌 의미적 목표를 사용합니다. 예를 들어:
Recon + Area_01
이라는 명령이 생성되면 바로 특정 좌표를 NPC에게 전달하지 않습니다. 실행 계층이 해당 지역의 후보 지점을 확인하고,
- 이동 가능한 위치인지
- NavMesh에 투영 가능한지
- 완전한 경로가 존재하는지
- 현재 임무 조건에 적합한지
를 검증한 뒤 실제 Mission Context로 변환합니다.
이를 통해 상위 판단 시스템과 Unreal의 실제 월드 실행 로직을 분리했습니다.
그룹 단위 임무와 조직 수명 관리
초기에는 그룹 데이터를 리더 NPC의 Component가 소유했습니다.
하지만 이 구조에서는 리더가 사망할 경우 그룹의 기억과 현재 임무까지 함께 사라질 수 있었습니다.
이를 해결하기 위해 렌더링되지 않는 독립 AGroupManagerActor가 다음 상태를 관리하도록 변경했습니다.
- 그룹 구성원
- 현재 리더
- 그룹 기억
- 현재 명령
- Mission
- 임무 수행 상태와 보고
특정 Pawn과 조직 자체의 수명을 분리하면서 리더 교체와 명령 지속이 가능한 구조를 만들었습니다. 또한 그룹 임무를 배포할 때 일부 NPC에게만 새로운 Mission이 적용되는 상태를 방지하기 위해 구성원 전체를 먼저 검사한 뒤 일괄 적용합니다. 적용 과정에서 실패가 발생하면 기존 상태로 복구하도록 구성했습니다.
C++ 점수 기반 NPC 판단과 Behavior Tree 역할 분리
초기 구조에서는 Behavior Tree의 여러 조건 노드가 성격 수치와 현재 상태를 직접 비교했습니다.
조건이 여러 위치에 분산되면서 비슷한 판단이 경쟁하고, Combat과 Alert 상태가 짧은 시간 동안 반복적으로 변경되는 문제가 발생했습니다.
이를 NPCDecisionComponent의 C++ 판단 계층으로 이동했습니다.
Perception
Health
Ammo
Personality
Current Command
↓
점수 계산
Hard Gate
상태 유지 시간
강제 이탈 조건
↓
CombatState
↓
Behavior Tree
Behavior Tree가 판단과 실행을 동시에 담당하지 않고, C++에서 결정된 상태를 받아 이동·사격·엄폐 같은 실제 행동에 집중하도록 역할을 분리했습니다.
비동기 LLM 요청과 Unreal World 수명 관리
Local LLM은 비동기 HTTP 요청으로 연결되어 있기 때문에 응답이 도착할 때 이미 Scenario가 종료되었거나 Actor가 제거되어 있을 수 있습니다. 이를 위해:
- 대상 Actor를 weak pointer로 관리
- Scenario 변경 시 대기 중인 요청 폐기
- 실행 중인 요청 취소
- 요청 생성 세대(generation) 비교
- 이전 Scenario에서 돌아온 callback 무시
를 적용했습니다. LLM 서버가 동작하지 않거나 응답이 지연되는 상황에서도 기존 NPC 판단과 Mission 실행은 계속 동작하도록 구성해, 생성형 AI가 게임 진행의 필수 의존성이 되지 않도록 했습니다.
첫 Vertical Slice — Recon → Report → Secure
현재 Live World 구조를 실제 실행 흐름으로 검증하기 위한 첫 Vertical Slice를 구현했습니다.
정찰 그룹에 Recon + Area 명령
↓
관측 후보 위치 평가
NavMesh 및 경로 검증
↓
정찰 NPC 이동 및 관측
↓
필요한 정보 확보
↓
AreaObserved Fact 생성
↓
Report 전달
↓
Team Operational Memory 갱신
↓
Secure + Area 후속 명령
↓
전투 그룹 진입
↓
적 점유 여부 / 아군 생존 / 점유 시간 확인
↓
AreaSecured Report
Recon은 목적지에 도착했다고 끝나지 않습니다. 실제 관측 조건을 만족하고 필요한 정보를 확보한 뒤 보고까지 전달되어야 완료됩니다. Secure 역시 NPC가 지역에 들어온 것만으로 성공하지 않고, 적의 점유 상태와 아군 상태를 확인하고 일정 시간 점유를 유지한 뒤 보고가 생성됩니다. 즉 행동의 결과가 다시 조직의 정보가 되고, 그 정보가 다음 판단의 입력이 되는 순환 구조를 첫 시나리오에서 검증했습니다.
문제 해결 경험
생성형 AI보다 결정론적 기준선을 먼저 구현
초기 목표에서는 LLM Commander를 빠르게 연결하는 것을 고려했습니다. 하지만 명령이 실패했을 때 원인이
LLM의 잘못된 판단인지
Command 변환 문제인지
NavMesh 문제인지
NPC Decision 문제인지
Behavior Tree 문제인지
완료 조건 문제인지
구분하기 어려웠습니다. 따라서 LLM보다 동일한 입력에서 항상 같은 결과를 내는 결정론적 Planner를 먼저 구현했습니다.
Operational Objective
↓
Deterministic Planner
↓
Structured Command
↓
동일한 Mission / NPC 실행 경로
향후 LLM Commander 역시 동일한 입력·출력 계약을 사용하게 함으로써 결정론적 기준선과 생성형 판단을 직접 비교할 수 있도록 했습니다.
개인의 관측과 조직의 지식을 분리
초기 구조에서는 NPC가 경험한 사건을 어떻게 상위 조직에 전달할지에 대한 경계가 명확하지 않았습니다. 이를 그대로 공유하면 NPC 하나가 관측한 순간 모든 아군이 해당 정보를 알게 되는 문제가 발생합니다. 그래서:
Observation
→ Personal / Group Knowledge
→ Report
→ Team Operational Memory
라는 명확한 정보 전달 경계를 추가했습니다. 이 변화는 단순한 Memory 저장 구조를 넘어, 각 조직이 제한된 정보를 가지고 서로 다른 판단을 내릴 수 있는 기반이 됐습니다.
리더 사망과 그룹 상태의 수명 분리
그룹 정보가 리더 Component에 종속되어 있을 때 리더 사망이 그룹 자체의 소멸과 연결되는 문제가 있었습니다. 그룹을 독립 Actor로 분리하면서 조직의 수명을 NPC 개체의 수명과 분리했습니다. 이후 리더 교체, 명령 유지, 그룹 단위 기억과 보고를 한곳에서 관리할 수 있게 됐습니다.
이전 World의 LLM 응답으로 인한 비동기 오류 방지
PIE를 종료하거나 Scenario를 변경한 뒤에도 이전 HTTP 요청이 늦게 완료되면서 이미 파괴된 World나 Actor를 참조하는 문제가 있었습니다. 단순히 요청을 취소하는 것만으로는 이미 callback 단계까지 도달한 응답을 완전히 차단할 수 없었습니다. 따라서 weak pointer와 요청 취소뿐 아니라 Scenario generation을 함께 기록했습니다.
Request Generation = 12
Scenario Restart
Current Generation = 13
↓
Generation 12 Response 도착
↓
결과 폐기
이를 통해 이전 실행의 비동기 결과가 새로운 Scenario 상태에 영향을 주지 않도록 했습니다.
프로젝트를 통해 배운 점
이 프로젝트를 진행하면서 생성형 AI를 게임에 적용하는 데 가장 중요한 것은 모델 자체보다 게임 시스템과 AI 사이의 책임 경계를 어디에 둘 것인가라는 점을 배웠습니다. 처음에는 LLM이 더 많은 것을 결정할수록 NPC가 자연스러워질 것이라고 생각했습니다. 하지만 실제 게임에서는 이동 가능한 위치, 공격 가능 여부, 조직의 권한, 성공 조건처럼 반드시 게임 엔진이 보장해야 하는 영역이 존재했습니다. 그래서 현재는 다음 원칙을 유지하고 있습니다.
AI
→ 의미와 의도 선택
C++
→ 유효성 및 실행 가능성 검증
Behavior Tree
→ 실제 행동 실행
또한 살아 있는 세계를 만들기 위해서는 NPC에게 많은 행동을 제공하는 것보다 누가 무엇을 알고 있고, 왜 그 행동을 선택했는가가 이어지는 것이 더 중요하다고 느꼈습니다.
같은 Attack 행동이라도 단순한 상태 수치 때문에 공격하는 것과, 이전 사건에 대한 기억과 관계 때문에 공격하는 것은 플레이어가 받아들이는 의미가 크게 달라질 수 있습니다.
결국 Live World에서 중요한 것은 행동의 종류만 늘리는 것이 아니라:
사건
→ 기억
→ 관계와 정보
→ 판단
→ 행동
→ 새로운 사건
이라는 인과관계를 지속시키는 것이라고 보고 있습니다.
아쉬웠던 점과 현재 개발 방향
현재 프로젝트는 개발 진행 중이며, 전체 Live World 구조 중 첫 Vertical Slice를 검증한 단계입니다. 현재까지는:
- 계층형 Memory와 Report
- Structured Command
- Recon / Secure Mission
- C++ 기반 NPC 판단
- Behavior Tree 실행
- Team Operational Memory
- Local LLM 비동기 요청
- Scenario 기반 반복 검증
을 연결했습니다.
현재 Recon → Report → Team Operational Memory → Secure 흐름은 Automation Test와 PIE를 통해 통합 검증했습니다.
다음 단계에서는 단순히 기능의 수를 늘리기보다, 현재 구조가 실제로 NPC의 기억과 성격 차이를 행동의 차이로 만들어내는지 보여주는 것을 우선하고 있습니다.
재현 가능한 LLM 비교 환경
생성형 AI는 동일한 Scenario와 Seed를 사용하더라도 매 실행마다 다른 결과를 생성할 수 있습니다.
이 상태에서는 두 NPC의 행동 차이가 Memory 때문인지, Personality 때문인지, 단순한 LLM 출력의 차이 때문인지 구분하기 어렵습니다.
이를 해결하기 위해 현재 Cached LLM Replay를 구현하고 있습니다.
Record
Memory Snapshot
→ Local LLM
→ 결과 검증
→ 응답 저장
→ 기존 판단 경로
Replay
Memory Snapshot
→ 저장된 응답 조회
→ 버전 / 입력 검증
→ 기존 판단 경로
동일 Scenario와 Seed, 동일한 LLM 출력을 유지한 상태에서 Memory나 Personality 하나만 변경해 실제 Decision과 NPC 행동이 어떻게 달라지는지 비교하는 것이 목적입니다. 첫 비교에서는:
Run A
동일 Scenario / Seed
Memory 없음
Run B
동일 Scenario / Seed
AllyDeath Memory 존재
처럼 하나의 입력만 변경해 결과를 비교할 예정입니다.
Structured LLM Commander
현재 상위 Command 생성은 결정론적 Planner를 기준선으로 사용하고 있습니다.
다음 단계에서는 같은 FCommandIntent 계약을 사용하는 LLM Commander를 연결하여:
Team Operational Memory
+ Current Objective
+ Commander Personality
↓
LLM Commander
↓
Structured Command
↓
기존 C++ 검증 / Mission / Behavior Tree
구조로 확장할 예정입니다.
LLM이 새로운 행동 코드를 생성하는 것이 아니라 Recon, Secure, Defend, Block 등의 동사와 Area, Route, Position 등의 대상을 조합하여 현재 상황에 맞는 명령을 선택하게 하는 방식입니다.
Live World로의 확장
현재 군사 시나리오는 전체 시스템을 검증하기 위한 첫 적용 사례입니다. 장기적으로는 같은 구조를 오픈월드 RPG의 개인 관계와 사건에도 적용할 수 있는지를 검증하고 싶습니다. 예를 들어:
플레이어가 NPC를 살해
↓
목격 / 보고 / 관계를 통해 사건 전달
↓
관련 NPC가 사건을 Memory로 보유
↓
시간이 지난 뒤 플레이어와 다시 조우
↓
현재 관계 + 과거 사건 + 성격을 기반으로 판단
↓
대사 / 경고 / 체포 / 공격 등
기존 게임 행동 중 하나를 선택
처럼 개발자가 모든 사건 조합에 대한 분기를 작성하지 않아도, 게임에 존재하는 행동들을 축적된 맥락에 맞게 선택하는 방향입니다. 또한 장기적으로는 멀티플레이 환경에서 서버가 게임 상태의 최종 권한을 유지하고, 플레이어 클라이언트의 Local LLM을 비신뢰 추론 Worker로 활용하는 분산 구조도 계획하고 있습니다. RETRY의 최종 목표는 NPC에게 무한한 자유를 주는 것이 아닙니다. 개발자가 만든 게임 규칙과 행동의 범위 안에서, NPC가 자신이 살아온 세계의 맥락을 기억하고 그에 맞는 선택을 할 수 있도록 만드는 것이 목표입니다.