들어가며

<발더스 게이트 3(이하 BG3)>는 정말 미친 게임이다. 지난 6월 퇴사를 하고 풀타임 게이머라고 해도 무방할 정도로 게임을 해서 어느덧 100시간이 훌쩍 넘는 플레이 시간을 기록했다. BG3는 플레이어 관점에서도 엄청난 게임이지만, 웹 프론트엔드 개발자의 관점에서 바라봤을 때 특히 감탄을 자아낸다.

그 이유 중 하나는 방대한 맵에서 수많은 인물들과의 관계, 수많은 퀘스트와 헤아릴 수 없는 아이템들이 모두 기억되고, 그러면서도 사용성이 매끄럽다는 점이다. 게다가 퀘스트 실행 순서에 따라 달라지는 내러티브까지… 감히 상상도 못할 개발적 규모를 지녔음에도 지금까지 심각한 버그를 찾아내지 못했다.

비록 나는 웹 프론트엔드라는 한정된 경험을 해왔지만, 이렇게 방대하고도 촘촘한 설계를 이해하면 분명 BG3의 또 다른 축복을 받을 수 있을 거라 생각해 이 글을 쓰게 되었다.

※ 참고로 BG3의 개발 기술 리소스는 제한적이라, 상당 부분 모딩 웹페이지와 문서를 기반으로 추측하여 작성했다. 또한 필자는 위에서 언급했듯 웹 개발자로, 게임 개발에 문외한이다. 따라서 실제 게임이 개발된 방식과는 다를 수 있으며 오류가 있을 수 있다. 틀린 부분이 있다면 메일로 알려주시면 감사하겠다.

와우포인트1: BG3는 어떻게 수많은 객체들의 상태값들을 기억하는가?

1) Osiris

모딩웹에 따르면 BG3는 두가지 스크립트 언어로 구성되어있다. 그 중 Osiris(이하 오시리스)는 이벤트 중심의 선언적 언어로, 게임 내에서 발생하는 다양한 상황을 감지하고 이벤트 발생 시 연동할 이벤트를 생성한다. RPG게임에서는 흔히 “조건이 만족되면 → 어떤 결과 실행” 같은 규칙이 필요한데, 오시리스가 바로 이 규칙을 작성하는데에 특화되어있다고 보면 될 것 같다.

오시리스는 내부적으로 Database라는 개념을 사용하여 상태를 “fact” 형태로 기록한다. 예를 들어 개발자가 아래와 같은 규칙을 작성하면,

IF;
  CharacterJoinedParty(_Char);
THEN;
  DB_InParty(_Char);

엔진은 이를 해석해 “캐릭터가 파티에 합류했다”는 사실을 DB_InParty 테이블에 기록한다. 여기서 Database는 우리가 아는 MySQL 같은 범용 DB가 아니라, 오시리스 스크립트 내부에서만 쓰이는 Prolog fact와 비슷한 구조의 경량화된 fact store이다. 이런 구조 덕분에 “내가 무엇을 했는지”와 같은 사실들이 일관된 형식으로 저장되고, 이후 다른 규칙이 이 DB를 참조해 세계를 변화시킨다. 따라서 오시리스는 세계 상태를 구조화하고 일관되게 기록하는 틀이라고 볼 수 있다. 이 때문에 엔진이 세이브/로드 대상을 파악하기 쉬워지고, 결과적으로 직렬화 과정도 안정적으로 이루어진다고 추정된다.

2) 세계 전체를 스냅샷처럼 저장하는 세이브 구조

많은 RPG에서 세이브 파일은 보통 퀘스트 진행도와 캐릭터 스탯만 저장한다. 그래서 다시 불러오면 NPC나 환경이 초기화되거나, 이벤트가 꼬이는 경우가 있다. 하지만 BG3의 경우에는 아래와 같은 구체적인 상태값들이 그대로 저장되어 유지하는 것을 확인할 수 있다.

  • 전투로 불탄 건물과 전투 위치에 그대로 남아 있는 시체
  • 사과의 위치
  • NPC의 관계, 내 캐릭터에 대한 호감도

즉, BG3의 세이브는 단순한 진행도 저장을 넘어, 세계 전체의 스냅샷을 기록한다고 볼 수 있다.

공식 문서(Osiris: Using the Story Editor)에서도 Reload Level and Story 기능이 언급되는데, 이는 현재 상태를 저장하고 다시 불러오는 구조가 있음을 간접적으로 보여준다.

이처럼 BG3는 세계의 방대한 상태값을 그대로 저장/복원하면서도 여전히 높은 성능을 유지한다는 점에서 놀라운 게임이다. 구체적인 내부 구현 방식은 공개되지 않았지만, 플레이어 경험만 놓고 보더라도 자유도와 몰입도를 중시하는 개발진의 철학을 기술적으로 실현한 사례라 할 수 있다.

3) GUID 기반 상태 관리

위에서 살펴보았듯, 수많은 객체들의 상태값이 저장될 수 있는 이유는 BG3의 객체들이 단순히 “종류”로만 구분되지 않기 때문이다. 객체들은 아래와 같이 각각이 GUID(Globally Unique Identifier, 전역 유일 식별자)를 갖는다.

  • “고블린 졸개 #231” = GUID_abc123
  • “언더다크에서 주운 사과” = GUID_def456

세이브 파일은 이 GUID를 키로 삼아 객체의 상태(위치, 소유자, 생사 여부)를 저장한다. 그래서 내가 언더다크에서 사과를 하나 주웠다면, 그 사과는 그냥 ‘사과’가 아니라 게임 속에서 유일한 ID를 가진 내 사과로 저장된다. 즉 GUID는 일종의 해시 키 같은 역할을 하며, 이에 따라 상태 DB에서 리스트 전체를 순회하지 않고도 빠르게 조회가 가능할 것으로 추정된다.

와우포인트2: BG3는 어떻게 그 방대한 시나리오 속에서 퀘스트 간의 관계 및 실행 타이밍을 관리하는가?

1) Goal 간의 계층 구조

공식 문서를 보면 “Firstly, goals are executed from top to bottom … Secondly, parent goals execute first, and will only activate their children once that parent goal is complete”라는 문구를 찾아볼 수 있다. 이 말은 곧, BG3의 퀘스트가 단순히 체크리스트처럼 나열된 항목이 아니라 계층 구조를 가진다는 걸 보여준다. 상위 Goal이 먼저 실행되고, 그것이 완료되어야 하위 Goal들이 순차적으로 활성화되는 구조다.

이런 설계 덕분에 수많은 퀘스트가 서로 얽혀 있어도 순서와 의존성이 명확히 보장된다. 단순히 “퀘스트가 완료됐다/안 됐다” 수준이 아니라, 상위 목표와 하위 목표가 어떤 관계에 있고, 언제 실행 가능한지가 엔진 차원에서 통제되는 것이다. 추측컨대, 이런 복잡한 상태가 꼬이지 않도록 이벤트를 관리하기 위해서는 Composite 패턴과 State Machine(FSM) 같은 전통적인 소프트웨어 설계가 적용되었을 가능성이 높다. Composite 패턴은 상위/하위 Goal을 트리 구조로 묶어주는 역할을 하고, FSM은 각 Goal이 가질 수 있는 상태 전이(예: 미시작 → 진행 중 → 완료/실패)를 명확히 규정해준다.

놀라운 점은 BG3가 이 패턴을 수백 명의 NPC, 수천 개 아이템, 수많은 퀘스트가 동시에 얽히는 대규모 RPG 환경 전체에 전면적으로, 그리고 일관되게 적용했다는 것이다. 대부분의 게임은 주요 퀘스트만 정교하게 관리하고, 부수적인 건 단순 플래그로 처리하기 때문에 규모가 커지면 꼬임과 버그가 발생하기도 하고 혹은 아예 퀘스트들이 독립적으로만 작동하여 상대적으로 단순하게 느껴지기도 한다. 하지만 BG3는 모든 Goal을 계층화하고, 각 Goal의 상태 전이를 FSM처럼 관리하면서도 플레이어가 어떤 순서로 어떤 선택을 해도 세계가 무너지지 않도록 통제해냈다. 플레이어 입장에서는 어떻게 이런 방대한 내러티브가 꼬이지 않고 굴러갈 수 있는지 놀랍기만 하다.

2) 이벤트 안정성 보장

하지만 계층 구조만으로는 충분하지 않다. 플레이어가 이 계층을 의도적으로 혹은 우연히 망가뜨릴 수 있기 때문이다. 예컨대 어떤 NPC가 특정 위치까지 걸어와야 이벤트가 발동되는데, 길이 전투로 막혀버린다면 어떻게 될까? 이런 경우 이벤트가 꼬여 스토리 진행이 멈춰버릴 가능성이 크다. BG3는 이런 상황을 엔진 차원에서 보정한다. 예를 들어, NPC가 정상적으로 이동할 수 있다면 그대로 이벤트가 실행되지만 이동이 불가능하다면 엔진은 대체 경로를 탐색하고, 최악의 경우에는 해당 NPC를 순간이동시켜서라도 이벤트를 강제로 발생시킨다.

이 원리는 Osiris 공식 문서에 적힌 “Operations done with Osiris are meant to be guaranteed because they must happen”이라는 설명으로 요약된다. 즉, 이벤트는 반드시 실행된다는 보장 모델이다. 이 덕분에 플레이어가 어떤 장난을 치든 퀘스트와 시나리오는 논리적으로 끊기지 않고 이어진다. 이는 실패해도 전체 흐름이 깨지지 않도록 안전장치를 두는 방식인 Saga 패턴과 유사한데, Saga 패턴이 분산 시스템에서 부분 실패가 있어도 전체 트랜잭션이 깨지지 않도록 보상 액션을 정의하듯, BG3 역시 개별 이벤트 실패를 허용하지 않고 끝까지 보정해 흐름을 이어간다.

결론

BG3를 플레이하면서 이걸 웹으로 만든다면 어떻게 했을지를 상상하기도 했는데, 그 방대한 규모와 촘촘한 설계는 상상만으로도 머리가 아파온다. 프론트엔드에서는 이정도로 복잡한 수준의 상태관리와 이벤트 관리가 필요하진 않겠지만, 이 관점에서 설계를 한다면 위에 언급한 패턴들과 설계 방식들을 더 깊이 있게 적용할 수 있을거라 생각한다.

필자는 주로 게임의 상태 및 이벤트 관리의 관점에서 이 게임의 와우포인트를 찾았지만, 게임 개발자에게는 어느정도 당연한 수준(?)인건지 정작 라리안의 게임 컨퍼런스 발표 영상에는 관련해서 하나도 언급하지 않고, 시네마틱이 얼마나 챌린지 했는지만 공유해주고 있다.(Game Developers Conference에서 라리안이 발표한 영상은 여기에서 볼 수 있다.)

아름다운 게임을 만들어준 라리안에게 찬사를 보내며 이 글을 마친다. BG3는 단순한 RPG의 걸작을 넘어, 복잡한 시스템도 올바른 설계 패턴과 철학이 있으면 제어할 수 있다는 사실을 증명해준다.

참고자료