게임 내 다이얼로그와 환경의 흐름 변화 및 그 제어 방법에 대하여

source: winterdream gamemaker
장르를 불문하고 대부분의 게임에 걸쳐 널리 사용되고 있는 공통적인 양식으로, 플레이어와 NPC 사이에 상호작용하는 데 사진과 같은 다이얼로그(혹은 대화창)가 널리 사용된다.
오늘날의 다이얼로그는 양식과 형태가 어느정도 정립되어, (1) 텍스트를 문자별로 순차적으로 출력, (2) 순차적으로 텍스트 출력 중 다음 다이얼로그로 진행 입력이 발생 시에 순차적인 출력이 중지되고, 출력중인 텍스트 전문이 즉시 출력되며, (3) 인게임의 상황을 파악하는 데 도움이 되도록 인게임의 연출이 다이얼로그와 함께 연동되어 재생되는 정도는 표준적인 모습으로 어렵지 않게 확인할 수 있다.
위에서 말한 다이얼로그 연출의 전형적인 사례 중 하나
주의: 영상은 원신 6.6 버전 메인 스토리(마신임무) 스포일러를 포함함
이 기능의 표준성에도 불구하고, 게임 엔진 단계에서 이 기능을 지원하거나 제공하고 있지는 않다. 게임 엔진의 역할을 생각해보았을 때, 엔진의 입장에서는 다소 지엽적인 기능이므로 제공하지 않는 것이 부자연스럽지 않다.
하지만 엔진 차원에서의 의도사항이 없어서인지, 이 기능의 구현에는 여러가지 시도가 있는 모양이었다. 수행중인 프로젝트에서도 여러 차례 시행착오와 내부 규격의 개정을 거치면서, 이러한 기능을 더욱 체계적으로 구현할 수 있는 방법을 모색하고 있다.
단순 다이얼로그의 데이터 정의와 구현
최초에는 다이얼로그가 소설책과 같이 고정된 시나리오 흐름으로 선형적으로 진행되는 상황으로 가정하였다. 이 단계에서는 줄글 텍스트 데이터를 로드하고, 텍스트 한 문자씩 순차적으로 나타나는 연출을 재생하는 방법에 대해서만 고려하면 되었다.
1
2
3
4
5
[
"근육을 위해 운동하는 사람은 밥은 항상 든든하게 먹어야 해요.",
"하지만 아무것이나 집어먹으라는 뜻은 아닌데요.",
"지금 손에 들고 있는 그 떡볶이 당장 내려놔요."
]
따라서 데이터 구조는 위와 같이 단순 문자열 리스트로만 다루어도 되었다. 사례 연구에서 이와 같이 문자열 리스트 필드를 만들어 다이얼로그를 구현하는 사례를 어렵지 않게 확인할 수 있었다.

수행 중인 프로젝트를 과정 중 고려한 초기 버전 다이얼로그 데이터의 예시
다만 단순 문자열 리스트로는 세 명 이상이 참여하는 대화를 묘사하거나, 재생 속도를 조정하는 등의 연출을 구현할 수 없다. 또한 대화 과정에서 선택지가 등장해야하는 소요가 발생하면서 각각의 다이얼로그 데이터가 더 많은 정보를 담아야 했다.
아직까지는 선형적인 흐름으로 볼 수 있는것이, 게임 《원신》의 사례와 같이1 선택지를 어느것으로 고르더라도 게임 흐름이 분기되지는 않기 때문이다. 선택지에 따라서 잠시 다른 다이얼로그가 재생될 수는 있어도, 금방 원래의 큰 흐름으로 복구되었다.

1 : 심지어 원신에서는 주인공이 한 번에 대답할 법한 문장을 둘로 나누어 선택지로서 제공하여, 선택지로서는 무가치한 선택을 제시하는 것이 일반화되었다.1
복잡한 다이얼로그의 데이터 정의와 인게임 흐름 제어의 구현
하지만 점차 다이얼로그 시스템에 더 많은 것들이 연계되거나, 기능들이 요구되면서, 이것을 더 이상 다이얼로그를 제어하는 시스템만으로는 설명하기 어려워졌다.
우선, 다이얼로그 선택지 시스템은 분기 처리가 가능하다는 점에서, (1) 팀/역할 선택, (2) 문제와 답안 제출, 그 외 다양한 상황에서 플레이어-게임 시스템 인터페이스로서 활용될 수 있었다.

문제-답안 제출 시스템으로서 활용되는 다이얼로그 선택지 시스템
이 자료처럼 문제-답안 제출 시스템으로 활용하는 상황에서는, 오답에 대해서 재시도 다이얼로그로 분기처리하여, 틀린 선택지를 골랐을 때 계속해서 이전 다이얼로그로 복귀하는 흐름을 구현할 수 있었다. 하지만 틀린 선택지를 골랐을 때 게임 시스템이 틀린 선택지를 골랐음을 인식하거나, 앞서 언급한 “(1) 팀/역할 선택”에서는 선택한 선택지에 따라서 게임 시스템과 연계하여야 했다. 이것은 다이얼로그를 재생하는 시스템 바깥의 내용이다.
또한 프로젝트가 진행됨에 따라서, 도입에서 언급했던 다이얼로그의 양식 중 하나, “(3) 인게임의 상황을 파악하는 데 도움이 되도록 인게임의 연출이 다이얼로그와 함께 연동되어 재생”하는 기능이 부분적으로 요구되었다. NPC 캐릭터가 인게임의 특정한 위치로 걸어가거나, 다이얼로그 진행 중 인게임에서 이벤트를 발생시키는 등 상황 설명을 위한 연출 기능이 요구되었다. 이 역시 다이얼로그를 재생하는 시스템 바깥의 내용이다.
사례 연구: Naninovel
이러한 요구 사항을 충족하는 사례로서 Naninovel 엔진이 있었다. Naninovel은 유니티 엔진에 기반하여 비주얼 노벨 게임들의 공통 기능들을 사전에 모두 구현해두고 자체적인 스크립팅 포맷으로 게임을 구현할 수 있도록 하는 비주얼 노벨 엔진이다.
(어떤 비주얼노벨 게임 프로젝트에서, 크리티컬한 버그를 수정하기 위해 발매 날짜 직전에 급하게 투입되면서, 처음 접해보았다. 유니티 프로젝트라고 하여 도우러 갔다가 처음 보는 포맷에 당황했던 기억이 있다.)

source: Naninovel
Naninovel의 스크립트는 명령어 없는 일반 텍스트 한 줄마다 하나의 다이얼로그로 취급된다. 위 사진 속 11번째 줄의 텍스트 하나가 온전히 한 순간의 발화로서 재생되고, 사용자의 다음 입력을 받을 때 까지 정지한다.
@set "randomScore = random(-100, 100)"
@goto #EpicLabel if:"abs(randomScore) >= 50"
또한 @로 시작하는 명령어로 다이얼로그 재생에 요구되는 부가적인 데이터와 인게임 환경을 제어할 수 있다. @set으로 변수를 설정하거나, @choice로 선택지를 제공하고, @goto을 조건문 명령어와 함께 사용하여 다이얼로그를 분기 처리할 수도 있다.
다이얼로그 시스템의 확장
앞서 언급하였듯 기능 요구사항은 더 이상 다이얼로그 시스템으로는 설명하기 어려워졌다. 따라서 사례 연구의 사례와 같이, 다이얼로그 재생에 사용되는 구현을 시나리오 시스템으로 개명하고 다이얼로그 재생을 이 기능의 하위 기능으로 수정하였다.
다만 Naninovel과 같이 자체적인 스크립팅 포맷을 정의하지는 않았다. 스크립팅 포맷을 처리할 컴파일러 분석기를 구현하는 것은 여건 상 어려웠고, 요구사항이 계속해서 변화하였기 때문에, 컴파일러를 따로 두는 것은 유지보수에 부담이었다. 스크립팅 데이터를 컴파일하더라도 AST와 비슷한 구조의 데이터로 변환하여 사용하게 될 것이므로, 원래의 다이얼로그 데이터를 더욱 확장, 구조화하여 사용하는 편이 더 나았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
"D019": {
"identifier": "D019",
"nodeType": "Dialogue",
"nextIdentifier": "N012",
"speakerName": "의사 NPC",
"dialogueContent": "그래도 혈압이 잡히지 않네요. C-line 잡아서 수액을 빠르게 투여하겠습니다. 간호사 C 선생님, C-line set 건네주세요.",
"portraitSpriteIdentifier": null
},
"E034": {
"identifier": "E034",
"nodeType": "InvokeEvent",
"nextIdentifier": "L016",
"eventIdentifier": "start_ambubagging",
"moveNextBehavior": "Immediately"
},
"L016": {
"identifier": "L016",
"nodeType": "Delay",
"nextIdentifier": "C025",
"durationSeconds": 4,
"waitUntil": "WaitUntilDone"
},
...
시나리오 시스템에서의 데이터 구조
이 시스템을 그래프 이론의 그래프에 대입하여, 데이터 파일이 묘사하는 일련의 시나리오 흐름을 ‘시나리오 그래프’로 명명했다. 또 이전 다이얼로그 시스템 시기의 개별 다이얼로그, 그리고 시나리오 시스템에서 시스템이 재생 처리를 수행하는 개별 수행 단위, 즉 위의 JSON 데이터의 D019, E034, L016과 같은 단위를 ‘시나리오 그래프 노드’로 명명했다. (후술할 내용에서 이 시나리오 그래프를 실제로 그래프로 표현하였다.)

작성 중에는 25종 내외의 유형이 있었고, 이후에도 요구사항 변화에 따라 계속해서 늘었다.
또한 인게임에서 대화가 진행되는 동안 함께 제어되어야 하는 것들, 예를 들어 카메라 포커스, NPC 캐릭터 혹은 플레이어 캐릭터의 위치 이동, 인게임 이벤트 발생, 사운드 재생 등이 이 시나리오 시스템의 하위 기능으로 구현하였다.
이렇게 데이터가 확장되니, 하나의 시나리오 데이터가 수천 줄에 이르러 유지보수가 어려워졌다. 팀 내 인원 구성 상황 상, 시리얼라이저와 DTO에 맞게 데이터를 작성하는 역할은 주로 비개발 분야 전공자가 맡아야 하였기 때문에, 시나리오 데이터가 기획에 불충분하게 작성되거나 여러 차례 개발 분야 전공자의 리뷰가 요구되었다. 하지만 팀에는 개발 분야 전공자가 나 혼자였으므로, 시나리오 데이터를 리뷰하는 것은 곧 다른 요구사항의 개발이 지연으로 이어졌다.
사실 수천 줄의 데이터의 맥락을 이해하면서 수정하는 것은 각자의 전공 분야를 생각할 것 없이 누구나 기피하는 어려운 작업이다. 한 번에 많은 데이터를 동시에 고려하여야 함에도, 데이터는 한 눈에 읽히지 않아, 여러 창에 걸쳐 같은 파일을 열어놓고, 스크롤을 위아래로 움직이면서 작업해야 하므로, 매우 오래 걸리거나 실수가 발생하기 쉬웠다.
시각화 에디터 구현
따라서 시나리오 데이터를 수정하는 데 효과적인 방법으로 언리얼 엔진의 블루프린트 에디터, 유니티의 셰이더 에디터, 비주얼 스크립팅 에디터 등에서 볼 수 있는, 그래프 뷰(Graph View) 형태의 시각화 에디터를 구현해 시나리오 작성에 사용하였다.
이미 시나리오 데이터는 노드를 중심으로 구성된 그래프 구조로 구성되었기 때문에, 시각화 에디터를 구현하는 데 있어서 추가적인 데이터 재구조화 작업이 필요하지는 않았다. 구현에는 아직 실험 단계 API인 UnityEditor.Experimental.GraphView를 사용하였다.
초기 원신의 평가에서는 다른 AAA급 콘솔 게임에 비교되어, 이 선형적인 스토리 진행이 부정적인 평가의 주요한 원인이 되었으나, 오늘날 원신라이크라는 문법이 생길 수준으로 게임 모델이 정착, 성숙해지고 나서는 이러한 용례가 일반적인 것으로 받아들여진다. ↩

