mcpField
D
dialogue-system

dialogue-system

github.com/jame581开发889165 次安装4 天前更新
代码仓库

关于 dialogue-system

面向 Godot 4.3+ 开发者的对话系统实现指南,涵盖对话数据、分支逻辑与界面呈现,并提供 GDScript 和 C# 示例。

主要功能

  1. 01DialogueLine 与 DialogueData 资源
  2. 02单例管理对话进度与信号
  3. 03支持选项分支和条件判断
  4. 04对话界面与打字机效果
  5. 05JSON 加载与变量插值

适用场景

  1. 01为 NPC 编写可分支对话
  2. 02在游戏界面展示角色台词和选项
  3. 03从 JSON 加载对话文本

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jame581/godotprompter --skill dialogue-system

安装后在 Claude Code 中输入/dialogue-system确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
branching-and-conditions.md
dialogue-manager.md
external-formats.md
ui-presentation.md
variable-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() 消息都包含类名和方法名,便于追踪日志

评论

支持 Markdown