visual-novel

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Visual Novel

Visual Novel

A playbook for visual novels — the branching script, the presentation (text box, characters, backgrounds), choices, and the quality-of-life systems players expect (save anywhere, backlog, skip, auto). This is a compositional skill: it drives a dialogue engine and a UI layer. It does not re-teach the dialogue engine or UI nodes; it defines the script model and the player conveniences that make a VN pleasant to read.
这是一份视觉小说开发指南——涵盖分支脚本、呈现方式(文本框、角色、背景)、选项,以及玩家期望的便捷功能(任意位置存档、历史记录、跳过、自动播放)。这是一项组合式技能:它驱动对话引擎和UI层,但不会重新讲解对话引擎或UI节点;它定义了脚本模型以及让视觉小说阅读体验更愉悦的玩家便捷功能。

When to use

适用场景

  • Use when the game is mostly reading branching text with character art and backgrounds: visual novel, dating sim, branching interactive fiction, story-choice game.
  • Use when designing a choice/route structure, story flags, or VN conveniences (backlog, skip, auto-advance, save-anywhere).
When not to use: dialogue as one feature inside a larger game →
rpg
consuming
dialogue-systems
. Card/board play → other genres. For the branching-script engine itself, use
dialogue-systems
(Ink / Yarn Spinner).
  • 当游戏以分支文本阅读为主,搭配角色立绘和背景时适用:视觉小说、恋爱模拟游戏、分支交互式小说、剧情选择类游戏。
  • 当需要设计选择/路线结构、剧情标记或视觉小说便捷功能(历史记录、跳过、自动推进、任意位置存档)时适用。
不适用场景:对话只是大型游戏中的一个功能 → 使用
rpg
结合
dialogue-systems
。卡牌/桌游玩法 → 其他类型。若仅需分支脚本引擎本身,请使用
dialogue-systems
(Ink / Yarn Spinner)。

Core loop

核心循环

Read a line → advance → (at a branch) make a choice → the story branches on flags/choices → read on → reach an ending. The "game" is the shape of the branching and whether choices feel consequential; everything else is presentation and convenience.
阅读一行文本 → 推进剧情 → (遇到分支时)做出选择 → 剧情根据标记/选择分支 → 继续阅读 → 达成结局。 所谓“游戏性”在于分支的结构以及选择是否带来切实影响;其余部分均为呈现方式与便捷功能。

Must-have systems

必备系统

  1. Branching script — ordered lines + choices + jumps, with conditions and variables (Ink/Yarn).
  2. Text box — speaker name, body text, typewriter reveal, advance on click/key.
  3. Characters — sprites with expressions/poses, positions, show/hide transitions.
  4. Backgrounds + transitions — scene images, fades/dissolves.
  5. Choices — present options, gate some on flags, record the pick.
  6. Story state — flags/variables that branch the script and unlock content.
  7. Save/load (save-anywhere) — full script position + state; multiple slots; quick save.
  8. VN conveniences — backlog/history, skip (read text), auto-advance, text-speed setting.
  9. Audio — music per scene, SFX, optional voice clips.
  1. 分支脚本 —— 有序文本行 + 选项 + 跳转功能,包含条件判断与变量(基于Ink/Yarn)。
  2. 文本框 —— 包含说话人名称、正文文本、打字机式逐字显示、点击/按键推进。
  3. 角色系统 —— 带有表情/姿态的角色 sprite,支持位置调整、显示/隐藏过渡效果。
  4. 背景与过渡效果 —— 场景图片、淡入/溶解效果。
  5. 选项系统 —— 展示可选内容,部分选项受剧情标记限制,记录玩家选择。
  6. 剧情状态 —— 用于分支剧情和解锁内容的标记/变量。
  7. 存档/读档(任意位置存档) —— 保存完整脚本位置 + 状态;支持多个存档位;快速存档。
  8. 视觉小说便捷功能 —— 历史记录/回溯、跳过(已读文本)、自动推进、文本速度设置。
  9. 音频系统 —— 场景专属音乐、音效、可选语音片段。

Design knobs

设计调节项

KnobEffectNotes
Text speed / instantreading comfortAlways allow instant + a skip.
Auto-advance delayhands-free readingTunable; pause on choices.
Skip scopere-readingSkip read text only by default.
Branch breadth/depthreplay value vs. costBranches multiply writing/art work.
Flag-gated contentreactivityLines/choices that check past decisions.
Route structurestory shapeBranch-and-merge vs. distinct routes (refs).
Choice visibilityfairnessShow locked choices vs. hide them.
Backlog lengthconvenienceKeep enough to re-read recent context.
调节项效果说明
文本速度 / 即时显示阅读舒适度始终允许即时显示 + 跳过功能。
自动推进延迟免手持阅读体验可调节;遇到选项时暂停。
跳过范围重玩体验默认仅跳过已读文本。
分支广度/深度重玩价值 vs 开发成本分支会成倍增加写作/美术工作量。
标记解锁内容剧情响应性依据过往决策显示的文本/选项。
路线结构剧情形态分支合并式 vs 独立路线式(参考资料)。
选项可见性公平性显示锁定选项 vs 隐藏锁定选项。
历史记录长度便捷性保留足够内容以重阅近期上下文。

Patterns

设计模式

1. Script as data the engine walks

1. 脚本作为引擎遍历的数据

python
undefined
python
undefined

Pseudocode. Lines, choices, and jumps as data — usually authored in Ink/Yarn and stepped

Pseudocode. Lines, choices, and jumps as data — usually authored in Ink/Yarn and stepped

through by that runtime. The engine asks the script for "the next thing to show".

through by that runtime. The engine asks the script for "the next thing to show".

node = script.current() if node.kind == "line": show_text(node.speaker, node.text) # wait for advance input elif node.kind == "choice": options = [o for o in node.options if condition_met(o.condition, flags)] # gate by flags show_choices(options) # wait for selection elif node.kind == "set": flags[node.var] = eval_expr(node.expr, flags) script.advance(selected_option_or_none)
undefined
node = script.current() if node.kind == "line": show_text(node.speaker, node.text) # wait for advance input elif node.kind == "choice": options = [o for o in node.options if condition_met(o.condition, flags)] # gate by flags show_choices(options) # wait for selection elif node.kind == "set": flags[node.var] = eval_expr(node.expr, flags) script.advance(selected_option_or_none)
undefined

2. Typewriter reveal + advance (skippable)

2. 打字机式逐字显示 + 可跳过的推进功能

python
undefined
python
undefined

Pseudocode. Reveal characters over time; a click first completes the line, then advances.

Pseudocode. Reveal characters over time; a click first completes the line, then advances.

def show_text(speaker, text): name_label.text = speaker revealed = 0 while revealed < len(text): if advance_pressed(): # first press: reveal the whole line instantly revealed = len(text); break revealed += chars_per_second * dt body_label.text = text[:int(revealed)] push_to_backlog_when_complete(speaker, text) wait_for_advance() # second press: go to the next line
undefined
def show_text(speaker, text): name_label.text = speaker revealed = 0 while revealed < len(text): if advance_pressed(): # first press: reveal the whole line instantly revealed = len(text); break revealed += chars_per_second * dt body_label.text = text[:int(revealed)] push_to_backlog_when_complete(speaker, text) wait_for_advance() # second press: go to the next line
undefined

3. Choice sets a flag that branches later content

3. 选择设置标记以影响后续剧情分支

python
undefined
python
undefined

Pseudocode. Choices write flags; later conditions read them — that is "reactivity".

Pseudocode. Choices write flags; later conditions read them — that is "reactivity".

def on_choice(option): if option.set: flags[option.set] = True # e.g. flags["helped_npc"] = True script.jump(option.target) # follow the branch
def on_choice(option): if option.set: flags[option.set] = True # e.g. flags["helped_npc"] = True script.jump(option.target) # follow the branch

Elsewhere, a line/choice/ending checks the flag:

Elsewhere, a line/choice/ending checks the flag:

if flags.get("helped_npc"): play_route("good_ending") else: play_route("neutral_ending")
undefined
if flags.get("helped_npc"): play_route("good_ending") else: play_route("neutral_ending")
undefined

Pitfalls / failure modes

常见陷阱/失败模式

  • Save that only stores a checkpoint → VNs need save-anywhere. Persist the exact script position and all flags/variables (and seen-text data) so a load resumes the same line.
  • Presentation logic baked into the script → unmaintainable. Keep content (text, choices) in the script and how it looks (sprites, transitions) in the engine layer.
  • No skip/auto/backlog → readers feel trapped, especially on replays. These are expected baseline features, not extras.
  • Skipping unread text → players miss content. Skip should fast-forward read text only.
  • Choices with no consequence → branches that reconverge instantly feel fake. Set flags that visibly change later lines, choices, or endings.
  • Combinatorial branch explosion → unshippable. Prefer branch-and-merge with a few flagged variations over fully distinct trees (refs).
  • Lost reading context → no backlog to re-read the last lines. Keep a history buffer.
  • Hardcoded language → no localization path. Keep text in data keyed for translation.
  • 仅存储检查点的存档系统 → 视觉小说需要任意位置存档。保存精确的脚本位置以及所有标记/变量(和已读文本数据),确保读档后从同一行继续。
  • 呈现逻辑嵌入脚本 → 难以维护。将内容(文本、选项)放在脚本中,将呈现方式(sprite、过渡效果)放在引擎层。
  • 无跳过/自动播放/历史记录 → 玩家会感到受困,尤其是重玩时。这些是必备基础功能,而非额外选项。
  • 跳过未读文本 → 玩家会错过内容。跳过功能应仅快进已读文本。
  • 无实际影响的选择 → 立即合并的分支会显得虚假。设置能明显改变后续文本、选项或结局的标记。
  • 组合式分支爆炸 → 无法交付。优先选择带有少量标记变体的分支合并结构,而非完全独立的分支树(参考资料)。
  • 丢失阅读上下文 → 无历史记录可重阅最近内容。保留历史缓冲。
  • 硬编码语言 → 无法本地化。将文本存储为带翻译键的数据源。

Composition (build it from these skills)

组合方式(基于以下技能构建)

  • Script engine:
    dialogue-systems
    (Ink / Yarn Spinner) — branching, conditions, variables, localization hooks.
  • Presentation:
    game-ui-ux
    for text-box/choice-menu layout, scaling, and safe areas;
    godot-ui-control
    for the concrete text box, choice menu, name plate, and backlog UI.
  • Persistence:
    save-systems
    for save-anywhere slots, seen-text/skip data, and settings.
  • Audio:
    audio-design
    for per-scene music, SFX, and voice playback.
  • Visuals: the engine animation/
    Tween
    skill for sprite/background transitions;
    shader-programming
    for dissolves.
  • Process:
    prototype-fast
    to test the branch structure in plain text before adding art.
  • 脚本引擎
    dialogue-systems
    (Ink / Yarn Spinner)—— 分支、条件、变量、本地化钩子。
  • 呈现层
    game-ui-ux
    用于文本框/选项菜单的布局、缩放和安全区域;
    godot-ui-control
    用于具体的文本框、选项菜单、姓名板和历史记录UI。
  • 持久化
    save-systems
    用于任意位置存档位、已读文本/跳过数据和设置。
  • 音频
    audio-design
    用于场景专属音乐、音效和语音播放。
  • 视觉效果:引擎动画/
    Tween
    技能用于sprite/背景过渡;
    shader-programming
    用于溶解效果。
  • 开发流程
    prototype-fast
    在添加美术资源前,先用纯文本测试分支结构。

References

参考资料

  • For the branching data model, route structures (branch-and-merge vs. routes), flags/variables, save-anywhere + backlog/skip data, and the content/presentation split, read
    references/script-and-flow.md
    .
  • 关于分支数据模型、路线结构(分支合并式 vs 独立路线式)、标记/变量、任意位置存档 + 历史记录/跳过数据,以及内容与呈现层分离,请阅读
    references/script-and-flow.md