dialogue-systems
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDialogue 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 and genres consume it.
visual-novelrpg将对话建模为图结构:节点存储台词,选项分流对话流程,条件控制选项可用性,变量记录玩家的行为。首要决策是自研还是选用现成工具——采用成熟的创作工具(Ink或Yarn Spinner),或是编写一个小型数据驱动运行器。本技能涵盖Ink和Yarn的使用,(视觉小说)和(角色扮演)类游戏会用到它。
visual-novelrpgWhen 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
or the engine's UI skill. For persisting narrative variables
across sessions, use . For data-as-resources in Godot/Unity, see
/ .
godot-ui-controlsave-systemsgodot-resourcesunity-scriptableobjects- 用于设计分支对话、选项菜单,或会影响后续对话的叙事状态(标记、关系值)。
- 用于在Ink、Yarn Spinner和自定义JSON/资源格式之间做选择。
- 用于将对话脚本接入游戏循环(推进台词、展示选项、执行命令、解析变量)。
不适用场景:若需实现引擎UI(文本框、角色立绘、选项按钮),请使用或对应引擎的UI技能。若需跨会话持久化叙事变量,请使用。若需在Godot/Unity中以资源形式存储数据,请参考 / 。
godot-ui-controlsave-systemsgodot-resourcesunity-scriptableobjectsCore workflow
核心工作流
- 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 , strong for game-driven dialogue with lots of engine hooks.
<<commands>> - 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.
- 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.
- Separate variables from flow. Keep a variable store (booleans, numbers, strings) the dialogue reads/writes; gate choices with conditions over it.
- Localize from the start. Author with line IDs, not raw strings, so the displayed text comes from a string table keyed by locale.
- Drive it from the game loop. The runner is a state machine: node → emit content → wait for input (continue or choice) → advance.
current - Verify by walking branches. Exercise each choice path; confirm conditions, variable writes, and that every branch reaches an end or a valid jump.
- 选择创作方式
- Ink——以叙事文本为核心,对创作者友好,支持灵活的流程编排;非常适合对话密集型或自主选择式(CYOA)叙事内容。通过ink runtime或inkle插件集成。
- Yarn Spinner——基于节点结构,支持显式命令,非常适合带有大量引擎钩子的游戏驱动型对话。
<<commands>> - 自定义运行器——当你需要完全控制权或最小依赖时,可采用JSON/资源图+小型解释器的方案。不要自研一门编程语言,只需构建图结构即可。
- 定义节点协议。一个节点需输出以下内容之一:台词(说话者+文本)、一组选项、命令/副作用、或结束/跳转指令。运行器会遍历节点,并将台词/选项传递给UI展示。
- 分离变量与流程。维护一个变量存储(布尔值、数字、字符串)供对话读写;通过变量条件控制选项是否可用。
- 从一开始就考虑本地化。使用台词ID而非原始字符串进行创作,这样展示的文本可来自按地区语言键控的字符串表。
- 由游戏循环驱动。运行器是一个状态机:(当前)节点 → 输出内容 → 等待输入(继续或选择选项) → 推进流程。
current - 遍历分支进行验证。测试每个选择路径;确认条件、变量写入逻辑,以及每个分支都能到达终点或有效跳转。
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 }
}
}linetextifsetreferences/runner.mdjson
{
"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 }
}
}linetextifsetreferences/runner.md2. Runner step (a state machine over the graph)
2. 运行器步骤(基于图结构的状态机)
gdscript
undefinedgdscript
undefinedThe 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
undefinedfunc 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
undefined3. 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. -> ENDInk tracks how often each knot was seen, so is a built-in
condition. Variables are global () or temporary ().
{visited_knot}VAR~ tempink
// 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. -> ENDInk会跟踪每个节点的访问次数,因此是内置条件。变量分为全局变量()或临时变量()。
{visited_knot}VAR~ temp4. 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 ; options use ; /
manage ; gates an option; moves between
nodes. Interpolate values in text with .
Speaker:-><<set>><<declare>>$variables<<if>><<jump NodeName>>{$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 , choices, or end silently stalls. Validate that every node terminates or branches.
next - Mutating state in a line node the player can revisit double-applies (drained twice). Apply
goldon the transition, or guard with a seen-flag.set - 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
参考资料
- — side-by-side syntax cheat sheet (choices, diverts/jumps, variables, conditions, includes) and integration notes.
references/ink-and-yarn.md - — a complete custom dialogue runner: graph schema, condition/expression evaluation, variable store, and localization lookup.
references/runner.md
- ——Ink与Yarn的语法对比速查表(选项、跳转/转移、变量、条件、引用)及集成说明。
references/ink-and-yarn.md - ——完整的自定义对话运行器:图结构 schema、条件/表达式解析、变量存储、本地化查找。
references/runner.md
Related skills
相关技能
- — persist narrative variables and seen-flags.
save-systems - ,
godot-resources— store dialogue as engine data.unity-scriptableobjects - — render text boxes, portraits, and choice buttons.
godot-ui-control - ,
visual-novel— genres that compose this skill.rpg
- ——持久化叙事变量和已访问标记。
save-systems - ,
godot-resources——以引擎资源形式存储对话。unity-scriptableobjects - ——渲染文本框、角色立绘和选项按钮。
godot-ui-control - ,
visual-novel——会用到本技能的游戏类型。rpg