퀘스트 아키텍처

스크립트는 없습니다. 논리는 있습니다: Romergo에서 노드와 변수들이 어떻게 구성되어 있는지

게임 에디터에서 논리에 대해 이야기할 때 보통은 빠르게 스크립트로 연결됩니다. 어떤 곳에서는 저자가 JavaScript를 작성하고, 어떤 곳에서는 Python를 작성하며, 또 어떤 곳에서는 자체 엔진 언어를 학습합니다. 이것은 거의 무한한 자유를 제공하지만, 동시에 문법 오류, 멈춘 루프, 호환되지 않는 플러그인, 심지어 저자 자신도 6개월 후에는 열기 두려운 코드를 초래합니다.

Romergo에서 저는 다른 길을 선택했습니다. 퀘스트 안에 임의의 스크립트는 없습니다. 함수를 삽입하거나, 네트워크 요청을 하거나, 플레이어에 직접 접근할 수 없습니다. 대신 이야기는 제한적이지만 충분히 표현력이 있는 집합에서 구성됩니다: 논리 노드, 변수, 조건 및 효과.

이것은 Romergo에서 단순한 분기만 할 수 있다는 뜻은 아닙니다. 플레이어의 결정을 저장하고, 점수를 계산하고, 옵션을 열고 숨기고, 다음 장에서 다른 경로를 선택하고, 무작위 이벤트를 만들고, 인터랙티브한 인터페이스를 만들 수도 있습니다. 단지 '이 코드를 실행해'라는 명령 대신 저자는 '이 상태일 때 이것이 일어나야 한다'라고 설명합니다.

그래프는 이미 프로그램입니다

가장 기본적인 논리 Romergo는 챕터 맵에 직접 있습니다. 장면은 전환으로 연결되고, 특별한 노드는 진행이 어디로 가야 할지 결정합니다.

아주 단순화하면, 작은 퀘스트는 이렇게 생겼습니다:

시작
  ↓
경비원과 대화
  ↓
출입증이 있나요?
  ├─ 예  → 폐쇄된 아카이브
  └─ 아니요 → 우회 경로

이것은 이미 실행 가능한 로직입니다. Player는 시작 노드에 들어가 장면을 표시하고, 히스토리 상태를 읽으며, 적절한 전환을 선택합니다. 작성자는 동일한 경로를 이해하기 쉬운 지도처럼 보고, if, goto 및 서비스 식별자의 집합으로 보지 않습니다.

![Flow Romergo: Final Accusation는 Switch와 세 개의 If / Else를 거쳐 다양한 엔딩으로 이어지며, 아래쪽에 Logic 패널이 열려 있습니다](../assets/logic-without-scripts-flow-nodes.webp)

*실제 Flow의 Builder에서의 조각: Final Accusation 장면은 Switch로 결정을 전달하고, 그 후 여러 If / Else가 상태를 점검하며 이야기를 다양한 결말로 나눕니다. 하단의 열린 Logic 패널은 모든 사용 가능한 세트를 보여줍니다: Dead End, Checkpoint, If / Else, Switch, Random 및 Teleport.*

현재 논리 패널에는 여섯 개의 주요 노드가 있습니다:

시작과 끝은 장을 장식하고, 일반 장면은 내용을 보여주며, 논리 노드는 이를 경로로 바꿉니다. 이 단계에서 이미 스크립트 한 줄 없이 선형 이야기, 분기, 여러 엔딩, 랜덤 이벤트, 안전한 복귀 지점을 만들 수 있습니다.

변수는 역사에 대한 기억입니다

역사가 이전에 무엇이 일어났는지를 기억해야 한다면, 하나의 그래프만으로는 충분하지 않습니다. 이를 위해 퀘스트에는 세 가지 간단한 유형의 변수가 있습니다:

각 변수에는 키, 작성자가 이해할 수 있는 설명 및 초기 값이 있습니다. 숫자형 변수에는 옵션으로 최소값과 최대값을 추가로 설명할 수 있고, 서비스 변수는 플레이어 상태 패널에서 숨기거나 조건이 충족될 때만 표시할 수 있습니다.

여기서 중요한 점은 변수는 단 한 장면이 아니라 전체 퀘스트 진행에 속한다는 것입니다. 첫 번째 장에서 플레이어가 패스를 얻었다면, 네 번째 장에서 동일한 플래그를 확인할 수 있습니다. 피니시를 통해 다음 장으로 넘어갈 때 상태가 초기화되지 않습니다. 선택한 옵션, 적용된 효과, 체크포인트 및 현재 랜덤 단계와 함께 상태가 유지됩니다.

간단한 작성자 사이클이 생깁니다:

플레이어의 선택이 값을 기록합니다
              ↓
변수가 상태를 저장합니다
              ↓
조건이 나중에 그것을 읽습니다
              ↓
그래프, 장면 또는 인터페이스가 반응합니다

예를 들어, 첫 번째 장면에서 '찾은 사진 보여주기' 선택은 portrait_revealed = true를 설정할 수 있습니다. 나중에 노드 **만약 / 그렇지 않으면**이 이 플래그를 확인하고 새로운 대화 장면을 엽니다. 더 나중에 단말기 인터페이스는 사진을 발견한 플레이어에게만 추가 기록을 보여줍니다.

하나의 변수가 퀘스트의 세 가지 다른 부분을 연결합니다. 이를 위해 장면이나 장을 수동으로 값을 전달할 필요가 없습니다. 이들은 하나의 공통 진행 상태를 읽습니다.

![변수 route 페이지는 상태의 키, 이름, 유형 및 가시성을 보여줍니다.](../assets/logic-without-scripts-variables.webp)

*변수 route는 하나의 카드에서 안정적인 키, 이해하기 쉬운 이름, 값 유형 및 상태 가시성이 설정되어 있습니다. 선택지 및 분기와의 연결은 같은 페이지 아래쪽에 있습니다.*

선택지는 쓰고, 분기는 읽습니다.

일반적인 장면에서 상태를 변경하는 가장 이해하기 쉬운 방법은 선택 항목에 효과를 추가하는 것입니다. 비주얼 에디터에서는 이것이 할당으로 나타납니다:

«마리에게 신뢰를 주기» → ally = "mary"
«혼자 가기»     → ally = "none"

그 후 Switch는 플레이어를 ally 값에 따라 원하는 장면으로 안내할 수 있습니다. 변수 페이지는 어떤 선택이 변수를 기록하고 어떤 분기가 그것을 읽는지 별도로 보여줍니다. 작은 세부사항이지만 큰 퀘스트에서는 수십 개 장면을 일일이 찾는 고통을 대신합니다.

![선택한 경로 변수를 비교하는 Laboratory와 Habitat 분기가 있는 Switch 설정](../assets/logic-without-scripts-switch-rules.webp)

*Switch 분기는 위에서 아래로 확인됩니다. 이 예에서 Laboratory 경로는 route = lab에서 작동하고, Habitat 경로는 route = habitat에서 작동합니다.*

조건은 일반 비교를 지원합니다:

간단한 경우에는 작성자가 If / Else 또는 Switch 설정에서 바로 변수, 연산자 및 값을 선택합니다. 보다 복잡한 논리의 경우, 일반적인 runtime는 **모든 조건**, **어떤 조건** 및 부정을 그룹으로 이해합니다.

이 규칙은 구조적으로 다음과 같이 설명할 수 있습니다:

{
  "op": "all",
  "conditions": [
    { "op": "gte", "key": "evidence", "value": 3 },
    { "op": "eq", "key": "alarm_active", "value": false }
  ]
}

작은 스크립트와 비슷하지만 본질적으로 스크립트는 아닙니다. 여기서는 함수를 호출하거나 무언가를 무작위로 변경할 수 없습니다. Romergo는 모든 허용된 연산을 미리 알고, 구조를 검증하며, 이를 Builder 미리보기와 게시된 Player에서 동일하게 실행합니다.

효과: 프로그래밍 언어 없이 실행되는 작은 명령

조건은 상태를 읽고, 이펙트는 상태를 변경합니다. Runtime Romergo는 여러 원자적 연산을 지원합니다:

일반 선택 인스펙터에서 기본 시나리오는 의도적으로 단순합니다: 변수를 선택하고 set를 통해 새 값을 기록합니다. 고급 인터페이스 논리에서는 조건과 효과를 검증 가능한 구조로 설정할 수 있습니다. 예를 들어, 단말기 상태 간 전환은 동시에 결정 사항을 기록하고, 증거를 추가하며, 경보를 활성화할 수 있습니다:

![Romergo 씬 편집기: 세 가지 선택 옵션이 변수 route에 lab, habitat, server를 기록합니다](../assets/logic-without-scripts-choice-effects.webp)

*효과가 직접 답변 옵션에 연결됩니다: 세 가지 선택지는 routelab, habitatserver 값을 기록합니다. 변수 필드와 새로운 값은 좁은 검사기에서도 분리된 상태로 유지됩니다.*

[
  { "op": "set", "key": "terminal_decision", "value": "cut-power" },
  { "op": "inc", "key": "evidence", "amount": 1 },
  { "op": "toggle", "key": "alarm_active" }
]

이 접근 방식에는 중요한 제한이 있습니다: 이것은 임의 수식 계산기가 아닙니다. evidence * trust / 2를 작성하고, 루프를 실행하거나, 자신의 함수를 만들 수 없습니다. 복잡한 동작은 여러 명시적 단계로 구성되며, 논리 확장은 미지의 코드를 실행하는 것이 아니라 새로운 검증 가능한 runtime 작업을 통해 이루어집니다.

작가에게는 이것이 조금 덜 무한하지만, 그 대신 이야기는 예측 가능하게 남습니다. 같은 효과를 출판 전에 확인하고, 저장하고, 플레이어 간에 동기화하며, 진행 상황을 불러온 후에도 재현할 수 있습니다.

왜 논리 노드 안에 아직 Flow가 없는 걸까요

지금의 접근 방식에는 예측 가능성에도 불구하고 솔직한 단점이 있습니다. 개발 경험이 없는 사람에게는 "변수 — 연산자 — 값"의 형태만으로도 이미 프로그래밍처럼 보일 수 있습니다. 그리고 조건 그룹과 여러 효과가 함께 나타나면, 선언적 구조는 Player에게는 안전하게 남지만, 작가에게 반드시 단순해지는 것은 아닙니다.

언젠가 시각적 노드와 논리 자체 안으로 들어가는 것이 자연스러워 보인다. 조건 목록 대신 블록 **AND**, **OR**, **NOT**, 변수 비교 및 값 변경을 연결할 수 있을 것이다. 종이 위에서는 이것이 텍스트나 JSON보다 친근하게 보인다.

하지만 그 다음 나는 실제 시나리오를 상상한다: 작성자가 챕터 맵에서 논리 노드를 열면, 그 안에서 또 다른 Flow가 자신의 노드와 연결을 가지고 있는 것을 발견한다. 결과는 그래프 안의 그래프가 된다. 지금 자신이 어디에 있는지, 이야기 수준으로 돌아가는 방법, 그리고 두 Flow 중 어디서 오류를 찾아야 하는지 이해해야 한다. 초보자에게 이런 중첩은 현재 형태보다 더 무서울 수 있다.

그래서 현재는 타협을 사용하고 있습니다. 이야기 경로는 시각적으로 남아 있으며, 노드 내의 조건과 효과는 컴팩트한 명시적 필드로 표시됩니다. 이는 작동하지만, 저는 인터페이스가 완전히 해결되었다고 생각하지 않습니다. 나중에는 아마도 준비된 템플릿, 단계별 설정, 시각적 블록의 더 이해하기 쉬운 조합이 나타날 수 있습니다 — 실제로 복잡성을 줄이는 것이지 단순히 또 다른 편집기로 옮기는 것이 아닙니다.

논리는 지도 위뿐만 아니라 살아 있습니다

인터페이스 장면은 동일한 상태를 사용합니다. 위젯, 메시지, 답변 선택지, 과제, 알림 또는 미디어 스트림은 showWhen와 일치할 때만 나타날 수 있습니다. 메트릭 값은 변수에 연결할 수 있으며, 위젯 상태 간의 전환은 지연 후, 조건에 따라 또는 플레이어의 행동 후에 실행할 수 있습니다.

예를 들어, 온보드 컴퓨터 화면은 다음과 같이 동작할 수 있습니다:

1. 처음에는 잠긴 터미널만 보입니다.

2. 플레이어가 선택을 하면 효과가 terminal_unlocked = true를 설정합니다.

3. 조건이 선박 도식과 새로운 버튼을 열어줍니다.

4. 버튼을 누르면 위젯의 상태가 변경되고 evidence이 증가합니다.

5. evidence >= 3가 되면 숨겨진 알림이 나타나고 새로운 퀘스트 가지가 활성화됩니다.

챕터 지도, 일반 선택 옵션, 그리고 인터랙티브 인터페이스는 세 개의 별도 시스템을 생성하지 않습니다. 이들은 하나의 상태 객체를 읽고 변경합니다. 그래서 대화에서 발견된 단서는 인터페이스 요소를 열 수 있고, 인터페이스 내에서의 행동은 이후 장면을 변경할 수 있습니다.

바로 여기서 선언적 논리가 진정한 프로그래밍처럼 느껴지기 시작합니다. 파일, 함수 및 이벤트 버스 대신, 저자는 이야기의 이해할 수 있는 개체들, 즉 사실, 선택, 조건 및 전환과 작업합니다.

왜 제가 일부러 임의의 JavaScript를 추가하지 않는지

스크립트를 삽입할 수 있는 가능성은 새로운 메커니즘으로 가는 가장 짧은 길처럼 보입니다. 그러나 발행된 퀘스트가 임의 코드를 실행할 권한을 획득하면 제품 전체 모델이 바뀝니다.

이 코드에서 어떤 브라우저 API가 사용 가능한지, 네트워크와 저장소를 어떻게 제한할지, 무한 루프를 어떻게 처리할지, 퀘스트를 오프라인으로 어떻게 옮길지, 협동 게임에서 어떻게 동기화할지, 그리고 프로젝트가 Player 업데이트 후에도 계속 작동하도록 어떻게 보장할지 결정해야 합니다.

선언적 규칙은 임의의 함수보다 훨씬 지루하지만, 그렇기 때문에 더 신뢰할 수 있습니다. 실행 전에 검증할 수 있습니다. 규칙이 어떤 변수를 읽고 쓰는지 이해할 수 있습니다. 진행 중인 상태를 체인 중간에도 안전하게 저장할 수 있습니다. 브라우저, 오프라인 빌드, 그리고 내장 Player에서 동일하게 퀘스트를 수행할 수 있습니다.

제한된 사전은 여기서 논리에 방해가 되는 것이 아니라, 오히려 그 경계를 설정합니다. 정말 유용한 연산이 나타나면, 각 프로젝트가 자체 엔진을 발명하도록 강요하기보다는, 이를 공용 runtime에 추가하고 모든 저자에게 동일하게 검증된 동작을 제공하는 것이 더 낫습니다.

디저트로 — 자체 HTML와 CSS

그럼에도 Romergo는 가장 안전한 곳, 즉 일반 장면 구성에서 실제 코드를 넣을 공간을 남겨둡니다.

프로젝트에서는 HTML와 CSS에서 자체 외관 테마를 생성하고 다양한 챕터에서 사용할 수 있습니다. HTML는 준비된 영역의 배치—대사 창, 초상화, 캐릭터 이름, 텍스트, 질문 및 선택 버튼을 지정합니다. CSS는 이러한 영역을 눈에 띄게 변경할 수 있게 합니다: 테두리, 배경, 카드 형태, 여백, 타이포그래피 및 화면 크기에 대한 반응.

완성된 테마 **Standard**, **Minimalism**, **Brutalism**, **Romance** 및 **Neon**는 수동 레이아웃 없이 확인된 범위를 보여줍니다. 맞춤 테마를 만들 때 Romergo는 선택한 템플릿을 복사합니다: 그 후 HTML과 CSS을 편집하고 즉시 실제 Player 미리보기에서 확인할 수 있습니다.

![실제 Romergo 편집기: Brutalism 테마의 CSS 및 모바일 대화 미리보기](../assets/logic-without-scripts-custom-html-css.webp)

*여기에는 콘셉트 아트가 없습니다: 왼쪽에는 실제 CSS 내장 테마 Brutalism이 표시되어 있고, 오른쪽에는 동일한 코드가 전화기 미리보기에서 나온 결과입니다. Desktop / Mobile 전환기를 사용하면 반응형 규칙을 바로 확인할 수 있으며, 사용자 테마는 선택한 템플릿의 사본에서 시작합니다.*

하지만 경계는 그대로입니다. 사용자 HTML에는 <script>, 이벤트 처리기, 폼, iframe 및 외부 리소스가 없습니다. CSS는 외부 url() 또는 @import를 불러올 수 없습니다. 필수 장면 영역은 제자리에 있어야 합니다; 만약 템플릿이 이를 잃어버렸다면, Player는 안전한 기본 디자인으로 돌아갑니다.

즉 HTML와 CSS는 **스토리가 어떻게 보이는지**를 담당하고, 노드, 변수, 조건 및 효과는 **스토리가 어떻게 작동하는지**를 담당합니다.

저는 이러한 구분이 마음에 듭니다. 작가는 퀘스트를 시각적으로 다른 것과 다르게 만들 수 있지만, 그 진행은 여전히 전체 검증 가능한 runtime의 일부로 남아 있습니다. 외부 레이어는 급진적으로 색을 바꾸고 재구성할 수 있으며, 각 주제를 별도의 프로그램으로 만들 필요가 없습니다.

코드 없는 코드는 논리 부재가 아닙니다

Romergo는 프로그래밍 언어를 그림으로 대체하는 것을 목표로 하지 않습니다. 그래프는 복잡한 수학에는 적합하지 않고, 원자 효과의 집합으로는 범용 자동화 엔진이 되지 않습니다.

그러나 인터랙티브 스토리에는 보통 다른 것들이 필요합니다: 사실을 기억하고, 카운터를 변경하고, 선택지를 열고, 경로를 선택하고, 인터페이스 상태를 보여주고, 되돌아갈 지점을 저장하며, 이 모든 결정을 다음 장에 전달하는 것.

이를 위해 노드와 변수는 스크립트의 단순화된 버전이 아니라, 이야기 자체의 보다 직관적인 언어가 됩니다. 저자는 컴퓨터에게 명령을 내리는 것이 아니라 세계의 인과 관계를 설명합니다: 플레이어가 선택을 하고, 상태가 바뀌고, 이야기가 반응하는 것입니다.

그리고 만약 약간의 진짜 코드를 원한다면, HTML와 CSS는 여전히 디저트를 기다리고 있습니다.

개발 블로그로 돌아가기