关于 dialogue-system
面向 Godot 4.3+ 开发者的对话系统实现指南,涵盖对话数据、分支逻辑与界面呈现,并提供 GDScript 和 C# 示例。
主要功能
- 01DialogueLine 与 DialogueData 资源
- 02单例管理对话进度与信号
- 03支持选项分支和条件判断
- 04对话界面与打字机效果
- 05JSON 加载与变量插值
适用场景
- 01为 NPC 编写可分支对话
- 02在游戏界面展示角色台词和选项
- 03从 JSON 加载对话文本
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jame581/ godotprompter --skill dialogue-system安装后在 Claude Code 中输入/dialogue-system确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/branching-and-conditions.mddialogue-manager.mdexternal-formats.mdui-presentation.mdvariable-interpolation.md详细介绍
Godot 4.3+ 中的对话系统
所有示例均面向 Godot 4.3+,不使用已弃用的 API。先展示 GDScript,再展示 C#。
相关技能: resource-pattern 用于将对话数据作为资源,godot-ui 用于 Control 节点布局,state-machine 用于管理对话流程,save-load 用于持久化对话状态,dialogue-manager 用于功能完整的对话插件,popochiu 用于冒险游戏。
1. 架构概览
┌─────────────────────────────────────────────────────────┐
│ UI Layer │
│ DialogueUI (Control) │
│ ├─ Label (speaker_name) │
│ ├─ TextureRect (portrait) │
│ ├─ RichTextLabel (dialogue_text, typewriter effect) │
│ └─ VBoxContainer (choice_container) │
│ └─ Button × N (choice buttons) │
│ │
│ Connects to: line_displayed, choice_presented signals │
└───────────────────────┬─────────────────────────────────┘
│ drives UI via signals
┌───────────────────────▼─────────────────────────────────┐
│ DialogueManager (Autoload / Node) │
│ start_dialogue(dialogue_data) │
│ advance() → next line or end │
│ choose(choice_index) │
│ current_line: DialogueLine (read-only) │
│ │
│ signals: dialogue_started │
│ line_displayed(line) │
│ choice_presented(choices) │
│ dialogue_ended │
└───────────────────────┬─────────────────────────────────┘
│ reads
┌───────────────────────▼─────────────────────────────────┐
│ Data Layer (Resources) │
│ DialogueData (Resource) │
│ lines: Dictionary ← id → DialogueLine │
│ start_line_id: String │
│ │
│ DialogueLine (Resource) │
│ speaker, text, choices, next_line_id, condition │
└─────────────────────────────────────────────────────────┘
2. DialogueLine 资源
DialogueLine 保存单个对话片段的所有数据。Choices 是一个未指定类型的 Array,其中包含 Dictionary,因此每个条目都可以包含 text、next_line_id 和可选的 condition,无需单独创建类。
GDScript
# dialogue_line.gd
class_name DialogueLine
extends Resource
## Display name shown in the UI speaker box.
@export var speaker: String = ""
## The body text. Supports BBCode and variable placeholders: {player_name}.
@export_multiline var text: String = ""
## When non-empty, overrides next_line_id. Each Dictionary must have:
## "text" : String — label on the choice button
## "next_line_id": String — line to jump to when chosen
## "condition" : String — (optional) expression; omit or "" to always show
@export var choices: Array = []
## ID of the next DialogueLine. Ignored when choices is non-empty.
@export var next_line_id: String = ""
## Optional condition expression evaluated before displaying this line.
## If the expression returns false the manager skips to next_line_id.
## Example: "GameState.has_item('key')"
@export var condition: String = ""
C#
// DialogueLine.cs
using Godot;
using Godot.Collections;
[GlobalClass]
public partial class DialogueLine : Resource
{
/// <summary>Display name shown in the speaker box.</summary>
[Export] public string Speaker { get; set; } = "";
/// <summary>Body text. Supports BBCode and {variable} placeholders.</summary>
[Export(PropertyHint.MultilineText)]
public string Text { get; set; } = "";
/// <summary>
/// When non-empty, overrides NextLineId. Each Dictionary entry must contain:
/// "text" : string — choice button label
/// "next_line_id" : string — line to jump to
/// "condition" : string — (optional) expression; omit or "" to always show
/// </summary>
[Export] public Array Choices { get; set; } = new();
/// <summary>ID of the next DialogueLine. Ignored when Choices is non-empty.</summary>
[Export] public string NextLineId { get; set; } = "";
/// <summary>
/// Optional condition expression. Evaluated before displaying this line.
/// Example: "GameState.HasItem(\"key\")"
/// </summary>
[Export] public string Condition { get; set; } = "";
}
3. DialogueData 资源
DialogueData 是一个容器资源,保存以字符串 ID 为键的所有对话行字典。将其创建为 .tres 文件后,就可以在检查器中将其分配给 NPC。
GDScript
# dialogue_data.gd
class_name DialogueData
extends Resource
## Dictionary mapping line ID strings to DialogueLine resources.
## Example: { "intro": <DialogueLine>, "ask_quest": <DialogueLine> }
@export var lines: Dictionary = {}
## ID of the first line to display when dialogue starts.
@export var start_line_id: String = ""
## Convenience accessor — returns null for unknown IDs.
func get_line(id: String) -> DialogueLine:
return lines.get(id, null)
C#
// DialogueData.cs
using Godot;
using Godot.Collections;
[GlobalClass]
public partial class DialogueData : Resource
{
/// <summary>Maps line ID strings to DialogueLine resources.</summary>
[Export] public Dictionary Lines { get; set; } = new();
/// <summary>ID of the first line to display when dialogue starts.</summary>
[Export] public string StartLineId { get; set; } = "";
/// <summary>Returns the DialogueLine for id, or null if not found.</summary>
public DialogueLine GetLine(string id)
{
if (Lines.ContainsKey(id))
return Lines[id].As<DialogueLine>();
return null;
}
}
在检查器中为
lines添加 Dictionary 条目,并使用字符串作为键、DialogueLine资源作为值;也可以通过代码从 JSON 加载(参见第 7 节)。
4. DialogueManager
单例自动加载项负责管理当前的 DialogueData 并跟踪当前对话行。start_dialogue(data) 设置数据并发出第一行;advance() 前进到下一行,choose(choice_index) 则选择一个选项。信号:dialogue_started、line_displayed(line)、choice_presented(choices)、dialogue_ended。
完整的 GDScript 和 C# 管理器实现(包括对话行遍历、选项处理、条件评估钩子和信号),请参见 references/dialogue-manager.md。
5. 分支和条件
选项保存在 DialogueLine.choices(一个 Array of Dictionary)中。每个选项都包含 text、next_line_id 和可选的 condition。条件是通过 Expression 类求值的表达式字符串,并将项目状态(例如 GameState 自动加载项)作为具名输入传入。
完整的选项处理和条件评估器实现(GDScript + C#),以及有关 Expression 输入的安全说明,请参见 references/branching-and-conditions.md。
6. 对话 UI
使用一个 CanvasLayer,其中包含用于对话正文的 RichTextLabel(启用 BBCode)、用于说话者姓名的 Label,以及用于选项按钮的 VBoxContainer。通过 Timer 驱动 RichTextLabel.visible_characters,实现打字机效果。UI 订阅 DialogueManager 的信号。
场景树片段、打字机效果实现、选项按钮生成方式,以及完整的 GDScript + C# 接线实现,请参见 references/ui-presentation.md。
7. 外部格式
从 JSON 加载对话,方便设计师编辑;加载时将 JSON 键映射到 DialogueLine 属性。也可以集成 Dialogic 插件,使用节点图编辑器(社区标准)。
JSON 加载器实现和 Dialogic 集成说明,请参见 references/external-formats.md。
8. 变量插值
对话文本支持 {player_name} 形式的占位符。可以通过简单的模板器解析:text.format(vars)(GDScript),或在 C# 中针对每个占位符调用 string.Replace。
GDScript 和 C# 插值辅助函数,请参见 references/variable-interpolation.md。
9. 实现检查清单
-
DialogueLine和DialogueData继承Resource,并添加[GlobalClass](C#),以便与 Inspector 集成 - 将
DialogueManager注册为 Autoload,以便所有场景共用同一个实例 -
start_dialogue()在访问dialogue_data前断言其不为 null -
advance()会阻止在仍有待处理选项时被调用 -
choose()操作经过筛选的可见选项列表,而不是原始的choices数组 - 条件字符串仅引用稳定的 Autoload 方法名——避免引用场景局部节点
-
_evaluate_condition()将GameState注册为Expression输入名称(parse(cond, ["GameState"])、execute([GameState]))——Expression无法解析 Autoload 名称 - 打字机计时器使用
visible_characters,而不是逐帧切分字符串,以兼容 BBCode - 在打字机效果播放期间按下
ui_accept会显示完整文本;再次按下则推进到下一行 - 创建新选项按钮前,先释放(
queue_free)已有按钮——绝不累积过时的子节点 - JSON 加载器分别验证文件打开和解析步骤,并针对每种失败情况发出清晰的错误消息
- 变量插值集中在一个
_interpolate()辅助函数中,而不是散布在各个信号处理器中 -
next_line_id = ""表示对话结束——除了空字符串,不使用任何特殊哨兵字符串 - 所有
push_error()消息都包含类名和方法名,便于追踪日志