Scriber
기획자가 쓴 대사를 Unity 게임이 코드 수정 없이 그대로 읽게 해 주는 게임 기획 도구 (데스크톱·웹)
개요
핵심 성과
제가 쓰려고 만든 기획 도구를, 글루미 리부트의 대사 시스템과 이어지는 파이프라인으로 넓혔습니다. 도구가 내보내는 파일을 게임이 그대로 읽어야 해서, 화면보다 파일 규격과 데이터가 깨지지 않는 것에 가장 공을 들였습니다.
1. 기획 도구와 게임 엔진 사이의 파일 규격 설계
- 문제:
- 기존 내보내기는 키 이름과 줄 종류 값을 설정표로 바꿀 수 있었습니다. 기획자에게는 편했지만, 여러 게임에서 다시 쓸 대사 엔진은 고정된 이름이 없으면 파일을 읽을 수 없었습니다.
- 배경 전환, 효과음, 애니메이션처럼 게임마다 다른 동작은 기획 문장만으로는 게임이 알아들을 수 없었습니다.
- 새 대사에 어떤 캐릭터, 표정, 연출이 필요한지 개발자에게 따로 전할 방법이 없어서, 빠진 것은 플레이하다가 발견했습니다.
- 해결:
- 내보내기를 엔진용(고정 규격 v1)과 커스텀(기존 설정표) 두 모드로 나눴습니다. 엔진용은 설정표를 아예 적용하지 않습니다.
- 규격은 Unity
JsonUtility에 맞췄습니다. 루트는 객체,null없음, 모든 줄이 종류와 상관없이 같은 키 15개를 가집니다. 그래서 C# 클래스 하나로 모든 줄을 읽습니다. 옛 엔진이 읽지 못하게 되는 변경일 때만version을 올립니다. - 선택지 분기 트리를
index/next번호로 이어진 한 줄 목록으로 펼쳤습니다. 분기의 마지막 줄은 선택지 다음 줄로 합류합니다. - 게임마다 다른 동작은
명령(이름 + 인자)줄로 보냅니다. 연출 문장에도 명령을 붙일 수 있게 해서, 기획자가 문장을 고쳐도 게임 쪽 연결은 끊기지 않습니다. - 파일 안에
requires(게임이 준비할 것)를 자동으로 넣습니다. 내보낸 씬에 실제로 쓰인 인물과 표정, 효과 단위, 명령과 인자 조합,{변수}를 모읍니다. 엔진은 시작할 때 이 목록을 게임이 등록한 것과 비교하고, 기획자는 같은 목록을 텍스트로 복사해 개발자에게 넘깁니다. - 팀 작업을 전제로, Unity 폴더에 바로 저장하는 기능은 일부러 만들지 않았습니다. 기획자는 파일을 넘기고 개발자가 넣습니다.
- 성과:
- 대사, 분기, 화자, 효과 수치, 연출 문구를 고쳐도 게임 코드는 그대로입니다. Scriber에서 내보낸 파일만 바꾸면 됩니다.
- 빠진 연결은 플레이 도중이 아니라 재생 전에 목록으로 드러납니다.
- 분기 연결,
requires정확성(안 쓰인 인물과 명령이 빠지는지), 설정표를 바꿔도 엔진 출력이 그대로인지를 자체 검사 코드로 확인합니다.
파일 예시
"requires": {
"characters": [{ "id": "yeonguwon", "name": "연구원", "isPlayer": false, "expressions": ["수줍음"] }],
"commands": [{ "name": "bg", "examples": [["lab_night"], ["lab_day"]] }],
"variables": ["playerName"]
},
"lines": [
{ "index": 3, "type": "narration", "text": "글루미가 시선을 창밖으로 옮긴다",
"command": "look", "args": ["geullumi", "window"], "next": 4, ... }
]
연출 문장은 화면에 띄울 수도(비주얼노벨식) 숨기고 명령만 실행할 수도(애니메이션식) 있습니다. 어느 쪽으로 할지는 게임이 정하고, Scriber는 둘 다 내보냅니다.
2. 여러 기기에서 편집할 때 수정이 되돌아가는 문제
- 문제:
- 데스크톱과 웹에서 같은 게임을 열어 두면, 한쪽에서 고친 내용이 다른 쪽 저장에 덮여 사라졌습니다.
- 원인은 게임을 열 때 한 번만 불러오고, 저장할 때는 모든 칸을 통째로 덮어쓰는 구조였습니다. 오래된 상태로 열려 있던 창이 저장하면서 다른 기기의 수정을 되돌린 것입니다.
- 해결:
- DB와 마지막으로 맞춘 값을 칸별 JSON으로 기억해 두고, 달라진 칸만
update합니다. - 저장은 Promise 체인으로 묶어 보낸 순서대로 처리합니다.
- 창을 떠날 때(
blur,pagehide, 화면 숨김) 남은 저장을 바로 보내고, 다시 돌아오면 최신 상태를 불러옵니다. 그사이 사용자가 새로 편집했다면 불러온 값은 버립니다. - 권한(RLS) 때문에 수정된 행이 0개인 경우, 에러 없이 조용히 지나가던 것을 실패로 알리게 했습니다.
- DB와 마지막으로 맞춘 값을 칸별 JSON으로 기억해 두고, 달라진 칸만
- 성과 / 한계:
- 서로 다른 칸을 동시에 고치면 둘 다 남습니다.
- 같은 칸을 양쪽에서 동시에 고치면 여전히 나중 저장이 이깁니다. 1인 도구라는 사용 범위를 고려해, 칸 단위 병합까지 하고 멈췄습니다.
3. 데스크톱 앱 로그인이 계속 풀리는 문제
- 문제:
- 데스크톱 앱에서 일정 시간이 지나면 로그인이 풀렸습니다.
- Electron의 메인 프로세스와 렌더러, 두 클라이언트가 같은 refresh token을 각자 갱신하고 있었습니다. refresh token은 한 번만 쓸 수 있어서, 서버가 재사용을 감지하면 세션 전체를 끊었습니다.
- 해결:
- 세션을 관리하는 쪽을 렌더러 하나로 정했습니다. 메인 프로세스는 PKCE 방식 OAuth 로그인 창만 띄우고 받은 토큰을 렌더러에 넘깁니다. 메인은 세션을 저장하거나 갱신하지 않습니다.
- 예전 버전이 디스크에 저장해 둔 토큰은 다시 쓰이지 않게 시작할 때 지웁니다.
- 성과:
- 같은 토큰을 두 곳에서 갱신하는 경로를 없애 세션 끊김의 원인을 제거했습니다.
AI 협업 방식
Scriber는 게임 본체와 달리 개발용 도구라서, 코드를 한 줄씩 직접 짜기보다 무엇을 만들지 정하고 결과를 검증하는 일에 제 시간을 썼습니다.
상세
- 역할 분담
- 요구사항, 데이터 구조와 파일 규격 결정, 화면 시안 선택, 테스트, 배포 판단은 제가 합니다.
- 구현 계획, 디자인 시안, 코드 작성은 Claude Code가 합니다. 계획은 함께 보고, 최종 결정은 제가 합니다.
- 기억 문서
PLAN.md에 확정된 결정을 표로 정리해, 새 대화 세션도 같은 기준에서 이어 가게 했습니다.- 화면 디자인은 AI가 임의로 정하지 않고, 시안 여러 개를 실제 화면으로 띄워 비교한 뒤 제가 고릅니다.
- 검증 방식
- 기능 하나 단위로 진행합니다. 명세를 먼저 쓰고, 목업 데이터 화면에서 확인한 뒤, 제가 실제 앱에서 테스트하고, 확인한 것만 커밋합니다.
- 내보내기처럼 다른 프로그램과 맞물리는 로직은 자체 검사 코드를 함께 남겨, 바꿀 때마다 돌립니다.
- AI가 테스트하다 실제 기획 문서를 덮어쓴 일이 있은 뒤로, 편집 테스트는 목업 데이터에서만 하고 실제 데이터는 읽기만 하도록 규칙을 정했습니다.



