Doki World
AI와 대화하며 캐릭터와 관계를 쌓아가는 인터랙티브 비주얼 노벨
개발 기간: 2026-02-01 ~ 진행중
왜 만들게 되었나?
Thought Lab을 만들다가 멈춘 가장 큰 이유는, AI-native로 일하다 보니 투두리스트를 별도 도구에 적을 필요가 없어졌기 때문입니다. 에이전트 코딩으로 개발하면 플랜이나 고민을 프로젝트 안에서 바로 처리하게 되고, 정작 시간 추적이나 가설 관리 도구는 쓰지 않게 되었습니다.
다만 Thought Lab에서 실험했던 멀티 에이전트 대화 컨셉 자체는 재밌다고 느꼈습니다. 여러 AI 페르소나가 한 주제를 놓고 토론하는 구조 — 이걸 미연시(비주얼 노벨)로 가져오면 어떨까? 페르소나의 “의견”이 캐릭터의 “대사”가 되고, “토론 주제”가 “스토리”가 되는 구조가 자연스럽게 그려졌습니다.
주요 기능
듀얼 게임 모드
채팅 모드와 비주얼 노벨 모드를 실시간으로 전환할 수 있습니다.
- VN 모드: 스프라이트 기반 클릭 진행, 배경 + 캐릭터 + 대사창
- Chat 모드: 메시지 버블 UI + 스티커 + 선택지



AI 에이전트 시스템
Master-Agent 아키텍처로 스토리를 구동합니다.
- Master Agent: 플레이어 메시지를 라우팅하고, 스토리 진행을 판단하며, 선택지를 생성
- Character Agent: 캐릭터별 고유한 말투와 감정으로 대화 생성 (감정 표현, 스티커, 속마음 포함)
- Creative + Judge 파이프라인: OpenAI 2단계 호출 — 첫 번째가 생성하고, 두 번째가 검증/보정
SSE(Server-Sent Events) 스트리밍으로 character_start → character_end → narration → story_update → done 순서로 실시간 전달됩니다.
Beat 기반 스토리 시스템
스토리 계층 구조: Novel > Story(에피소드) > Scene > Beat
5종류의 Beat가 있습니다:
narration— AI가 서술하는 장면 묘사dialogue— 캐릭터 간 정해진 대사freeChat— 플레이어가 캐릭터와 자유 대화trigger— 특정 조건 충족 시 발동choice— 플레이어의 선택이 스토리를 분기
호감도 시스템
0~100 범위의 6단계 관계 시스템:
| 단계 | 범위 | 의미 |
|---|---|---|
| rejection | 0 | 이별 엔딩 |
| guarded | 1-19 | 경계 |
| neutral | 20-39 | 중립 |
| friendly | 40-59 | 우호 |
| trust | 60-79 | 신뢰 |
| bond | 80-100 | 유대 |
호감도가 목표에 미달하면 자동으로 스토리가 연장되고, 0에 도달하면 이별 엔딩이 발동됩니다.
10명의 캐릭터 × 3개 소설

3개의 소설 세계관에서 10명의 캐릭터를 만날 수 있습니다:
- 이세계의 별 — 아리아, 네루, 메이, 진숙, 루나 (판타지)
- 월요일의 온도 — 소라, 지호 (현대)
- 달빛 궁정 — 하루, 유리, 소하 (동양 판타지)
Ring 과금 시스템
- 1 Ring = 10 채팅, 웰컴 보너스 3 Ring (30 채팅)
- 듀얼 결제: 한국(토스페이먼츠) + 해외(Creem.io)
- 첫 구매 2배 보너스, 리워드 광고 (1회 = 1채팅, 일 10회 한도)
- 1년 유효기간 + 7일 환불
다국어 지원
- 정적 텍스트: next-intl 기반 ko/en/ja 라우팅 (
/ko/play/...,/en/play/...) - AI 응답: User.locale DB 필드로 캐릭터가 해당 언어로 대화
- 시드 데이터도 3개 언어별 분리 관리 (
prisma/data/{ko,en,ja}/)
에셋 생성 파이프라인
게임에 필요한 캐릭터 스프라이트, 배경, 이벤트 CG를 별도 프로젝트(asset_generator)로 자동화했습니다. Animagine XL 4.0 Opt + ComfyUI 기반이고, RTX 5070 12GB에서 구동합니다.
두 프로젝트 간 워크플로우
스토리를 쓸 때마다 에셋이 필요해지는데, 이 과정을 커맨드 기반으로 자동화했습니다:
doki-world asset_generator
────────── ───────────────
1. 스토리 작성 (prisma/data/)
↓
2. npm run check-assets
→ 에피소드 데이터에서 필요한
배경/CG/캐릭터를 자동 추출
→ log/asset-report.md 생성
↓
3. npm run asset-request
→ 미완성 에셋만 패키징
→ .asset-request/ 생성
(requirements.md + 페르소나 + 레퍼런스)
↓
4. npm run asset-push
→ asset_generator로 전달 ──────→ asset-requirement/ 수신
↓
5. python generate.py from-requirements
→ requirement 파싱
→ gen:X 항목만 필터
→ 배치 생성 (캐릭터/배경/CG)
→ gen:X → gen:O 자동 갱신
↓
6. 수동: output/ → public/assets/ ←── 생성된 에셋핵심은 스토리 데이터가 에셋 요구사항의 원천이라는 점입니다. 에피소드의 scene.background, showSceneImage 트리거, NOVELS 배열에서 필요한 에셋을 자동으로 추출합니다. 수작업으로 “이 씬에 배경이 필요하네” 같은 추적을 할 필요가 없습니다.
asset-requirement.md에는 각 에셋의 상태가 (gen:X|pub:X) 마커로 추적됩니다. gen:O는 생성 완료, pub:O는 게임에 배포 완료를 의미합니다.
생성 결과물
22번의 커밋, 총 934건의 이미지 변경(548 생성, 39 수정, 347 삭제)을 거쳤습니다.
캐릭터 스프라이트 — 같은 seed로 base를 생성하고, 표정별로 얼굴만 face compositing하는 방식으로 일관성을 확보합니다.
| base | happy | sad | angry |
|---|---|---|---|
![]() | ![]() | ![]() | ![]() |
이벤트 CG — 스토리 핵심 장면 일러스트. solo 태그 필수(없으면 캐릭터 복제), upper body/cowboy shot 프레이밍만 사용(복잡한 앵글은 그림체 파괴).


배경 — 소설 세계관별 화풍 분리(이세계=anime, 월온=realistic, 달빛=동아시아풍). 총 28+ 배경 관리.

3차례 대전환
프로젝트 방향이 바뀔 때마다 캐릭터를 통째로 폐기하고 재생성했습니다.
- 커밋 #11, #14, #19에서 대규모 삭제 (총 삭제 347건 중 98%)
- 대부분 캐릭터가 1회 이상 삭제→재생성 사이클
- 에셋 구조도 진화:
profile.png→profile/face+full,assets/→output/분리
이런 대전환을 겪으면서 커맨드 기반 자동화의 가치를 절감했습니다. 처음부터 다시 만들어야 할 때, from-requirements 한 번이면 되니까요.
Behind the Scenes
가장 어려웠던 것: AI 대화 통제
이 프로젝트에서 가장 많은 개발 세션을 투자한 부분은 스토리 안에서 AI 대화를 통제하는 것이었습니다.
자유 채팅이라면 간단합니다. 캐릭터 페르소나를 주고 “알아서 대화해”라고 하면 됩니다. 하지만 비주얼 노벨은 다릅니다. 특정 장면에서는 특정 이야기를 해야 하고, 호감도에 따라 반응이 달라져야 하며, 적절한 타이밍에 다음 장면으로 넘어가야 합니다. 자유도와 통제 사이의 줄다리기입니다.
구체적으로 어려웠던 포인트들:
-
누가 다음 장면으로 넘기는가? — Master Agent의
beatCompleted신호, 캐릭터의action_suggestion평가, freeChat의maxTurns도달, 3+ 메시지 fallback. 이 4가지 메커니즘이 동시에 존재하고, 우선순위가 충돌하는 엣지 케이스가 끊임없이 나타났습니다. -
freeChat 구간의 딜레마 — 플레이어가 캐릭터와 자유롭게 대화하는 구간에서, AI가 스토리와 무관한 방향으로 대화를 끌고 갈 수 있습니다.
scene.hints에goal,keyInfo,forbidden,talkingPoints를 넣어서 가이드하지만, 너무 강하게 제한하면 자유 채팅의 의미가 없어집니다. -
호감도 판정의 모호함 — Judge 파이프라인이 대화 내용을 보고 호감도를 평가하는데,
affinityTopics가 빠져있으면 Judge가 뭘 기준으로 평가할지 모릅니다. 반대로 기준을 너무 상세하게 주면 기계적인 대화가 됩니다. -
스토리 데이터 동기화 — 스토리 내용을 한 줄 바꾸려면 7단계를 거쳐야 합니다: 에피소드 데이터 수정 → 캐릭터 페르소나 업데이트 → seed.ts 등록 → DB seed 실행 → 스키마 변경 → 타입 검증 → 문서 업데이트. 한 단계라도 빠지면 AI가 옛날 페르소나로 대화합니다.
솔직히 말하면 아직 완성도가 높지는 않습니다. 스토리 흐름이 자연스럽게 이어지다가도, 갑자기 맥락을 잃거나 장면 전환이 어색한 순간이 있습니다. “AI가 이야기를 만든다”는 것과 “AI가 이야기를 따라간다”는 것 사이의 균형을 잡는 건 여전히 진행 중인 과제입니다.
결제 심사 대기중
토스페이먼츠와 Creem.io 이중 결제 시스템을 구축하고, 법적 요건도 갖추었습니다:
- 사업자 등록 및 통신판매업 신고 완료
- 이용약관, 개인정보처리방침 작성 (ko/en/ja)
- 성인인증 게이트 (첫 로그인 시 연령 확인 + 약관 동의)
코드는 준비되어 있고, 심사가 완료되면 바로 서비스할 수 있는 상태입니다.
Claude Code와의 협업
.claude/commands/ 폴더에 에셋 체크, 스토리 업데이트, git push 등 반복 작업을 명세로 정의해두고, Claude가 그대로 실행하는 방식으로 개발했습니다.
PRD Agent 때와 마찬가지로, 명세서를 잘 쓰는 것이 곧 개발이라는 감각이 더 강해졌습니다. 특히 에셋 파이프라인처럼 반복적이고 규칙 기반인 작업에서 이 방식이 빛을 발했습니다.
이 글은 계속 업데이트됩니다.



