dialogue-systems

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Dialogue systems

对话系统

Model conversations as a graph: nodes hold lines, choices branch the flow, conditions gate options, and variables remember what the player did. The first real decision is build vs. buy — adopt a proven authoring tool (Ink or Yarn Spinner) or write a small data-driven runner. This skill owns both Ink and Yarn; the
visual-novel
and
rpg
genres consume it.
将对话建模为图结构:节点存储台词,选项分流对话流程,条件控制选项可用性,变量记录玩家的行为。首要决策是自研还是选用现成工具——采用成熟的创作工具(InkYarn Spinner),或是编写一个小型数据驱动运行器。本技能涵盖Ink和Yarn的使用,
visual-novel
(视觉小说)和
rpg
(角色扮演)类游戏会用到它。

When to use

适用场景

  • Use to design branching conversations, choice menus, or narrative state (flags, relationship values) that affect later dialogue.
  • Use to decide between Ink, Yarn Spinner, and a custom JSON/resource format.
  • Use to wire a dialogue script into your game loop (advance line, present choices, run commands, resolve variables).
When not to use: for engine UI (text boxes, portraits, choice buttons), use
godot-ui-control
or the engine's UI skill. For persisting narrative variables across sessions, use
save-systems
. For data-as-resources in Godot/Unity, see
godot-resources
/
unity-scriptableobjects
.
  • 用于设计分支对话、选项菜单,或会影响后续对话的叙事状态(标记、关系值)。
  • 用于在Ink、Yarn Spinner和自定义JSON/资源格式之间做选择。
  • 用于将对话脚本接入游戏循环(推进台词、展示选项、执行命令、解析变量)。
不适用场景:若需实现引擎UI(文本框、角色立绘、选项按钮),请使用
godot-ui-control
或对应引擎的UI技能。若需跨会话持久化叙事变量,请使用
save-systems
。若需在Godot/Unity中以资源形式存储数据,请参考
godot-resources
/
unity-scriptableobjects

Core workflow

核心工作流

  1. Choose the authoring approach.
    • Ink — prose-first, writer-friendly, weave/gather flow; great for dialogue-heavy or CYOA narrative. Integrate via ink runtime / inkle plugins.
    • Yarn Spinner — node-based, explicit
      <<commands>>
      , strong for game-driven dialogue with lots of engine hooks.
    • Custom runner — a JSON/resource graph + a small interpreter when you need full control or minimal dependencies. Don't build a language; build a graph.
  2. Define the node contract. A node yields one of: a line (speaker + text), a set of choices, a command/side-effect, or an end/jump. The runner advances through nodes and hands lines/choices to the UI.
  3. Separate variables from flow. Keep a variable store (booleans, numbers, strings) the dialogue reads/writes; gate choices with conditions over it.
  4. Localize from the start. Author with line IDs, not raw strings, so the displayed text comes from a string table keyed by locale.
  5. Drive it from the game loop. The runner is a state machine:
    current
    node → emit content → wait for input (continue or choice) → advance.
  6. Verify by walking branches. Exercise each choice path; confirm conditions, variable writes, and that every branch reaches an end or a valid jump.
  1. 选择创作方式
    • Ink——以叙事文本为核心,对创作者友好,支持灵活的流程编排;非常适合对话密集型或自主选择式(CYOA)叙事内容。通过ink runtime或inkle插件集成。
    • Yarn Spinner——基于节点结构,支持显式
      <<commands>>
      命令,非常适合带有大量引擎钩子的游戏驱动型对话。
    • 自定义运行器——当你需要完全控制权或最小依赖时,可采用JSON/资源图+小型解释器的方案。不要自研一门编程语言,只需构建图结构即可。
  2. 定义节点协议。一个节点需输出以下内容之一:台词(说话者+文本)、一组选项、命令/副作用、或结束/跳转指令。运行器会遍历节点,并将台词/选项传递给UI展示。
  3. 分离变量与流程。维护一个变量存储(布尔值、数字、字符串)供对话读写;通过变量条件控制选项是否可用。
  4. 从一开始就考虑本地化。使用台词ID而非原始字符串进行创作,这样展示的文本可来自按地区语言键控的字符串表。
  5. 由游戏循环驱动。运行器是一个状态机:
    current
    (当前)节点 → 输出内容 → 等待输入(继续或选择选项) → 推进流程。
  6. 遍历分支进行验证。测试每个选择路径;确认条件、变量写入逻辑,以及每个分支都能到达终点或有效跳转。

Patterns

实现模式

1. Engine-neutral dialogue graph (data, not code)

1. 引擎无关的对话图(数据而非代码)

json
{
  "start": "guard_intro",
  "nodes": {
    "guard_intro": {
      "speaker": "Guard", "line": "DLG_GUARD_001",
      "choices": [
        { "text": "DLG_OPT_BRIBE", "to": "bribe", "if": "gold >= 50" },
        { "text": "DLG_OPT_LEAVE", "to": "end" }
      ]
    },
    "bribe": {
      "speaker": "Guard", "line": "DLG_GUARD_BRIBED",
      "set": { "gate_open": true, "gold": "gold - 50" },
      "next": "end"
    },
    "end": { "end": true }
  }
}
line
/
text
are string-table IDs (localization), not literal text.
if
gates a choice;
set
mutates the variable store. The full interpreter that walks this graph is in
references/runner.md
.
json
{
  "start": "guard_intro",
  "nodes": {
    "guard_intro": {
      "speaker": "Guard", "line": "DLG_GUARD_001",
      "choices": [
        { "text": "DLG_OPT_BRIBE", "to": "bribe", "if": "gold >= 50" },
        { "text": "DLG_OPT_LEAVE", "to": "end" }
      ]
    },
    "bribe": {
      "speaker": "Guard", "line": "DLG_GUARD_BRIBED",
      "set": { "gate_open": true, "gold": "gold - 50" },
      "next": "end"
    },
    "end": { "end": true }
  }
}
line
/
text
字符串表ID(用于本地化),而非字面文本。
if
控制选项是否可用;
set
用于修改变量存储。遍历此图的完整解释器可查看
references/runner.md

2. Runner step (a state machine over the graph)

2. 运行器步骤(基于图结构的状态机)

gdscript
undefined
gdscript
undefined

The runner holds the current node and a variable store; the UI calls advance().

The runner holds the current node and a variable store; the UI calls advance().

func present(node): if node.has("line"): ui.show_line(node.speaker, localize(node.line)) if node.has("choices"): var shown = node.choices.filter(func(c): return eval_cond(c.get("if", ""))) ui.show_choices(shown) # only choices whose condition passes
func choose(choice): # called when the player clicks a choice apply_set(choice.get("set", {})) # write variables goto(choice.to)
func goto(id): current = graph.nodes[id] apply_set(current.get("set", {})) if current.get("end", false): ui.close(); return present(current) if current.has("next") and not current.has("choices"): goto(current.next) # auto-advance linear nodes
undefined
func present(node): if node.has("line"): ui.show_line(node.speaker, localize(node.line)) if node.has("choices"): var shown = node.choices.filter(func(c): return eval_cond(c.get("if", ""))) ui.show_choices(shown) # only choices whose condition passes
func choose(choice): # called when the player clicks a choice apply_set(choice.get("set", {})) # write variables goto(choice.to)
func goto(id): current = graph.nodes[id] apply_set(current.get("set", {})) if current.get("end", false): ui.close(); return present(current) if current.has("next") and not current.has("choices"): goto(current.next) # auto-advance linear nodes
undefined

3. Ink — branching with knots, choices, and variables (inkle)

3. Ink——使用节点、选项和变量实现分支(inkle)

ink
// Ink: '*' = once-only choice, '+' = sticky. [bracketed] text shows only in the
// choice, not the printed result. '->' diverts; '-> END' stops the flow.
VAR gold = 60

=== guard_intro ===
The guard blocks the gate.
* {gold >= 50} [Offer 50 gold]   "Here, take it."
    ~ gold = gold - 50
    The guard pockets it and steps aside. -> END
* [Leave]   You turn back. -> END
Ink tracks how often each knot was seen, so
{visited_knot}
is a built-in condition. Variables are global (
VAR
) or temporary (
~ temp
).
ink
// Ink: '*' = once-only choice, '+' = sticky. [bracketed] text shows only in the
// choice, not the printed result. '->' diverts; '-> END' stops the flow.
VAR gold = 60

=== guard_intro ===
The guard blocks the gate.
* {gold >= 50} [Offer 50 gold]   "Here, take it."
    ~ gold = gold - 50
    The guard pockets it and steps aside. -> END
* [Leave]   You turn back. -> END
Ink会跟踪每个节点的访问次数,因此
{visited_knot}
是内置条件。变量分为全局变量(
VAR
)或临时变量(
~ temp
)。

4. Yarn Spinner — nodes, options, and commands (Yarn 2.x)

4. Yarn Spinner——节点、选项和命令(Yarn 2.x)

yarn
title: GuardIntro
---
<<declare $gold = 60>>
Guard: You can't pass.
-> Offer 50 gold <<if $gold >= 50>>
    <<set $gold = $gold - 50>>
    Guard: ...fine. Go on through.
    <<set $gate_open to true>>
-> Leave
    Guard: Good choice.
===
Yarn lines may start with
Speaker:
; options use
->
;
<<set>>
/
<<declare>>
manage
$variables
;
<<if>>
gates an option;
<<jump NodeName>>
moves between nodes. Interpolate values in text with
{$gold}
.
yarn
title: GuardIntro
---
<<declare $gold = 60>>
Guard: You can't pass.
-> Offer 50 gold <<if $gold >= 50>>
    <<set $gold = $gold - 50>>
    Guard: ...fine. Go on through.
    <<set $gate_open to true>>
-> Leave
    Guard: Good choice.
===
Yarn的台词可以
Speaker:
开头;选项使用
->
<<set>>
/
<<declare>>
用于管理
$variables
<<if>>
控制选项是否可用;
<<jump NodeName>>
用于在节点间跳转。可通过
{$gold}
在文本中插入变量值。

Pitfalls

常见陷阱

  • Hardcoding display strings instead of line IDs makes localization a rewrite. Author against a string table from day one.
  • Inventing a scripting language for a simple branching tree. If you only need lines + choices + flags, a JSON/resource graph plus a 50-line runner beats a parser you must maintain. Use Ink/Yarn when writers need real flow control.
  • Variables coupled to the UI: store narrative state separately so the same dialogue works in cutscenes, menus, and tests. Persist it via
    save-systems
    .
  • Unreachable or dead-end nodes: a node with no
    next
    , choices, or end silently stalls. Validate that every node terminates or branches.
  • Mutating state in a line node the player can revisit double-applies (
    gold
    drained twice). Apply
    set
    on the transition, or guard with a seen-flag.
  • Mixing Ink's
    *
    (once-only) and
    +
    (sticky)
    by accident: looped menus need sticky
    +
    choices or the options vanish after one use.
  • 硬编码显示字符串而非使用台词ID,会导致本地化工作变成重写。从第一天起就基于字符串表进行创作。
  • 为简单分支树自研脚本语言。如果只需要台词+选项+标记,一个JSON/资源图加50行代码的运行器,远比你需要维护的解析器更好。只有当创作者需要真正的流程控制时,才使用Ink/Yarn。
  • 变量与UI耦合:将叙事状态单独存储,这样同一对话可在过场动画、菜单和测试中复用。通过
    save-systems
    实现持久化。
  • 不可达或死胡同节点:没有
    next
    、选项或结束标记的节点会导致流程静默停滞。验证每个节点都能终止或分支。
  • 在玩家可重复访问的台词节点中修改状态会导致重复执行(比如金币被两次扣除)。在跳转时应用
    set
    操作,或通过已访问标记进行控制。
  • 意外混用Ink的
    *
    (一次性选项)和
    +
    (常驻选项)
    :循环菜单需要使用常驻的
    +
    选项,否则选项会在使用一次后消失。

References

参考资料

  • references/ink-and-yarn.md
    — side-by-side syntax cheat sheet (choices, diverts/jumps, variables, conditions, includes) and integration notes.
  • references/runner.md
    — a complete custom dialogue runner: graph schema, condition/expression evaluation, variable store, and localization lookup.
  • references/ink-and-yarn.md
    ——Ink与Yarn的语法对比速查表(选项、跳转/转移、变量、条件、引用)及集成说明。
  • references/runner.md
    ——完整的自定义对话运行器:图结构 schema、条件/表达式解析、变量存储、本地化查找。

Related skills

相关技能

  • save-systems
    — persist narrative variables and seen-flags.
  • godot-resources
    ,
    unity-scriptableobjects
    — store dialogue as engine data.
  • godot-ui-control
    — render text boxes, portraits, and choice buttons.
  • visual-novel
    ,
    rpg
    — genres that compose this skill.
  • save-systems
    ——持久化叙事变量和已访问标记。
  • godot-resources
    ,
    unity-scriptableobjects
    ——以引擎资源形式存储对话。
  • godot-ui-control
    ——渲染文本框、角色立绘和选项按钮。
  • visual-novel
    ,
    rpg
    ——会用到本技能的游戏类型。