mcpField
D
dialogue-manager

dialogue-manager

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

关于 dialogue-manager

Dialogue Manager 是面向 Godot 开发者和编剧的对话插件,使用可读的 .dialogue 文本编写分支对话。它内置条件、状态变更及 CSV 或 PO 翻译流程,并提供编辑器标签页和 C# 支持。

主要功能

  1. 01脚本式 .dialogue 格式
  2. 02分支回复与条件判断
  3. 03状态变更和随机台词
  4. 04CSV 与 PO 翻译流程
  5. 05编辑器对话标签页
  6. 06官方 C# 支持

适用场景

  1. 01以文本编写游戏角色对话
  2. 02为对话选项设置条件分支
  3. 03在对话中更新游戏状态
  4. 04导出对话文本以进行翻译

安装

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

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

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

详细介绍

Dialogue Manager

相关技能: 手动编写对话数据结构可使用 dialogue-system,翻译台词可使用 localization,完整的冒险游戏工作流可使用 popochiu。

插件: Dialogue Manager · 版本 v3.10.4 · Godot 4.6 · MIT · 源码:https://github.com/nathanhoad/godot_dialogue_manager · GDScript,并官方支持 C#。


1. 何时使用 Dialogue Manager

方案最适合
dialogue-system 技能(手动编写 Resource 数据)完全掌控数据结构、不依赖插件、小型对话树
Dialogue Manager类脚本的 .dialogue 文本格式、分支式回复、条件/状态变更、翻译流程、可视化编辑器标签页、官方 C# 封装
完整的冒险游戏框架需要将房间/物品栏/角色与对话打包在一起的点击式游戏——Dialogue Manager 只负责对话层

如果编剧希望以易读的脚本文本(而非通过 Inspector 编辑的 Resource)编写分支对话,并且你希望免费获得内置条件、状态变更、随机台词和翻译工作流(CSV 或 PO),请选择 Dialogue Manager。如果你需要完全掌控的强类型 Resource 对话数据,请改用 dialogue-system。Dialogue Manager 只处理对话,并非完整的冒险/任务框架,因此较大型的点击式游戏框架仍需负责管理房间、物品栏和角色。


2. 安装与设置

  1. Godot AssetLib → 搜索“Dialogue Manager”→ 下载。或者将 GitHub 仓库中的 addons/dialogue_manager/ 复制到 res://addons/dialogue_manager/。
  2. Project → Project Settings → Plugins → 启用“Dialogue Manager”。这会添加一个 Dialogue 主屏幕标签页(位于 2D/3D/Script 旁边),并自动注册 DialogueManager 自动加载项——无需手动设置自动加载。
  3. addons/dialogue_manager/plugin.cfg:
[plugin]
name="Dialogue Manager"
description="A powerful nonlinear dialogue system"
author="Nathan Hoad"
version="3.10.4"
script="plugin.gd"
  1. C# 项目无需额外的 NuGet 包——使用 using DialogueManagerRuntime; 即可(§4)。
  2. 设置位于 Project Settings → General → Dialogue Manager:特别是 State Autoload Shortcuts(可在对话中不加前缀直接使用的自动加载项名称)和 Balloon Path(show_dialogue_balloon() 打开的场景——留空则使用内置示例气泡)。

3. .dialogue 语法

从 Dialogue 编辑器选项卡打开或创建 .dialogue 文件。每行的格式为 Character: text 或纯文本 text(旁白)。支持 Godot 的 RichTextLabel BBCode,以及一些额外语法:[[A|B|C]](随机选择行内内容)、 [wait=N] / [wait="ui_accept"](暂停打字)、[speed=N]、[next=auto](自动推进)。

标题和跳转 — ~ name 标记标题;=> name 跳转到该标题;=> END 结束流程;=> END! 会强制结束流程,即使还有待处理的跳转并返回;=>< name 跳转并在该分支遇到 END 时返回此处一次:

~ start
Nathan: Well?
- First one
- Another one => another_title
- Start again => start
=> END

~ another_title
Nathan: Another one?
=> END

响应选项(- )按缩进嵌套,也可以在 [...] 中添加条件 — 将所有跳转放在最后:

Nathan: How many projects have you started and not finished?
- Just a couple
	Nathan: That's not so bad.
- A lot [if SomeGlobal.some_property == true]
	Nathan: Maybe you should finish one before another.
- Another one [if SomeGlobal.some_method()] => another_title

条件 — if / elif / else、布尔运算符 and/or/(), 以及 match/while 代码块:

if SomeGlobal.some_property >= 10
    Nathan: That property is >= 10.
elif SomeGlobal.some_other_property == "some value"
    Nathan: Or we might be in here.
else
    Nathan: If neither are true, I'll say this.

行内条件:Nathan: I have done this [if already_done]once again[/if],也可以添加可选的 [else]。使用 ?. 进行空值安全的成员访问:if some_node_reference?.name == "SomeNode"。

状态变更 — set 赋值状态,do 调用方法或发出信号;两者都可以行内执行 ([do wave()],使用 [do! wave()] 可禁用隐式 await):

if SomeGlobal.has_met_nathan == false
    do SomeGlobal.animate("Nathan", "Wave")
    Nathan: Hi, I'm Nathan.
    set SomeGlobal.has_met_nathan = true

内置状态变更:do wait(float)、do debug(...)。在对话中发出信号:do SomeGlobal.some_signal.emit("arg")。

局部变量与额外游戏状态 — set locals.asked = true 会创建一个单次对话内的临时变量 (这是 example balloon 实现的惯例,并非 Dialogue Manager 核心功能)。传入 extra_game_states 数组的对象会直接按名称引用,其状态变更会在对话结束后保留 — 传入实例,而不是类 (GameStateClass.new(),不要直接传入类)。

随机行 — 前缀 % 表示权重相同,%N 表示相对权重;空行用于分隔随机组:

Nathan: I will say this.
%3 Nathan: This line has a 60% chance of being picked
%2 Nathan: This line has a 40% chance of being picked

变量和标签 — {{SomeGlobal.some_property}} 会将状态插入文本中(也可用作角色名称)。[#happy, #mood=calm] 用于附加标签,可通过 line.get_tag_value("mood") 读取。


4. 运行时 API 和示例气泡

加载 DialogueResource(.dialogue 文件),然后可以让 DialogueManager 为你打开气泡,或通过 await DialogueManager.get_next_dialogue_line(resource, title) 手动获取行。没有下一行时会返回 null — 在 GDScript 中检查假值(if not line: / while line:),在 C# 中检查 line != null。(该标签的 API.md 文档写着“空字典 {}”,但 v3.10.4 源码在每种对话结束路径上都会返回 null — 检查 line == {} 永远不会触发。)

GDScript
# npc.gd
extends Node2D

@export var dialogue_resource: DialogueResource

func _on_interact() -> void:
    # Opens the configured balloon (Settings → Balloon Path), or the built-in example balloon.
    DialogueManager.show_dialogue_balloon(dialogue_resource, "start")

func _manual_walk() -> void:
    # Manual traversal — build a totally custom balloon around this loop.
    var line: DialogueLine = await DialogueManager.get_next_dialogue_line(dialogue_resource, "start")
    while line:
        print("%s: %s" % [line.character, line.text])
        if line.responses.is_empty():
            line = await DialogueManager.get_next_dialogue_line(dialogue_resource, line.next_id)
        else:
            var chosen: DialogueResponse = line.responses[0]  # replace with real UI selection
            line = await DialogueManager.get_next_dialogue_line(dialogue_resource, chosen.next_id)
C#
// Npc.cs
using Godot;
using DialogueManagerRuntime;

public partial class Npc : Node2D
{
    [Export] public Resource DialogueResource;

    private void OnInteract()
    {
        DialogueManager.ShowDialogueBalloon(DialogueResource, "start");
    }

    private async void ManualWalk()
    {
        var line = await DialogueManager.GetNextDialogueLine(DialogueResource, "start");
        while (line != null)
        {
            GD.Print($"{line.Character}: {line.Text}");
            if (line.Responses.Count == 0)
            {
                line = await DialogueManager.GetNextDialogueLine(DialogueResource, line.NextId);
            }
            else
            {
                var chosen = line.Responses[0]; // replace with real UI selection
                line = await DialogueManager.GetNextDialogueLine(DialogueResource, chosen.NextId);
            }
        }
    }
}

其他 DialogueManager 方法:show_dialogue_balloon_scene(balloon_scene, resource, title)(打开特定气泡场景)、show_example_dialogue_balloon(resource, title)(强制使用内置气泡)、create_resource_from_text(text)(在运行时编译 .dialogue 字符串 — 遇到语法错误时会失败)。C# 方法名为相同的 PascalCase 形式:ShowDialogueBalloonScene、ShowExampleDialogueBalloon、 CreateResourceFromText。

get_next_dialogue_line 接受 mutation_behaviour 参数 — GDScript 使用 DMConstants.MutationBehaviour,C# 使用 MutationBehaviour(两者默认值均为 Wait,还支持 DoNotWait、Skip)。该枚举位于 DMConstants 上,不在 DialogueManager 自动加载项上。除非你清楚其他值的作用,否则请保留 Wait;示例气泡仅支持 Wait。


5. 信号和类型化访问

DialogueLine 字段:id、next_id、character、text、tags(PackedStringArray)、 translation_key、responses(元素类型未指定的 Array,包含 DialogueResponse)、concurrent_lines。DialogueResponse 在相同的 id/next_id/character/text/ tags/translation_key 字段基础上,还添加了 is_allowed: bool 和 condition_as_text: String。

GDScript
func _ready() -> void:
    DialogueManager.dialogue_started.connect(_on_dialogue_started)
    DialogueManager.dialogue_ended.connect(_on_dialogue_ended)
    DialogueManager.got_dialogue.connect(_on_got_dialogue)
    DialogueManager.mutated.connect(_on_mutated)

func _on_dialogue_started(resource: DialogueResource) -> void:
    pass

func _on_dialogue_ended(resource: DialogueResource) -> void:
    pass

func _on_got_dialogue(line: DialogueLine) -> void:
    print(line.character, ": ", line.text, " tags=", line.tags)

func _on_mutated(mutation: Dictionary) -> void:
    pass  # fires before a `do`/inline mutation runs (not `set` lines)
C#
using DialogueManagerRuntime;

public override void _Ready()
{
    DialogueManager.DialogueStarted += (Resource resource) => { };
    DialogueManager.DialogueEnded += (Resource resource) => { };
    DialogueManager.GotDialogue += (DialogueLine line) =>
    {
        GD.Print($"{line.Character}: {line.Text} tags={line.Tags}");
    };
    DialogueManager.Mutated += (Godot.Collections.Dictionary mutation) => { };
}

⚠️ 这些 C# 事件会在首次访问 DialogueManager.Instance 时,延迟连接到底层 Godot 信号。仅在此处订阅、随后通过 GDScript 驱动对话的项目不会访问 Instance,因此处理程序不会触发。至少要通过 C# 访问一次 API (例如 _ = DialogueManager.Instance;,或通过 C# 调用 GetNextDialogueLine/ShowDialogueBalloon 来运行对话),以完成连接。

内置的响应菜单节点只将 response_selected 暴露为 Godot 信号,因此应使用 Connect + Callable 连接,而不要使用 C# 事件处理程序:

responsesMenu.Connect("response_selected", Callable.From((DialogueResponse response) =>
{
    // advance using response.NextId
}));

每次遇到 ~ title 标记时,都会触发 passed_title(title)(GDScript)/ DialogueManager.PassedTitle += (string title) => { }(C#), 可用于分析或保存点书签。


6. 翻译

默认情况下,所有对话/响应文本都会通过 Godot 的 tr() 处理。DialogueManager.translation_source 属性用于选择后端——其枚举类型为 GDScript 的 DMConstants.TranslationSource / C# 的 TranslationSource(None、CSV、PO、默认值 Guess),而不是 DialogueManager.TranslationSource。 Guess 会检查项目设置中的区域设置,查找 PO 文件;如果找不到,则回退到 CSV。

每行的静态 ID(Nathan: Hi! I'm Nathan. [ID:HI_IM_NATHAN])可提供稳定的 translation_key, 用于匹配配音台词以及 CSV/PO 往返处理,而不是以字面文本作为键。.dialogue 文件会自动注册到 POT 生成列表中;对话行前的 ## 注释行会在导出的 PO/POT 中成为 #. TRANSLATORS: 注释。可从编辑器的 Translations 菜单(Dialogue 选项卡)导出/导入 CSV; 重新导入时,只会更新带有静态 ID 的行。

有关 TranslationServer、区域设置切换和 RTL,请参阅 localization 技能。Dialogue Manager 只负责生成翻译键以及导出 CSV/PO;将区域设置变更接入运行中的游戏则由 localization 负责。


实现检查清单

  • 在 Project Settings → Plugins 中启用 Dialogue Manager 插件(会自动注册 DialogueManager 自动加载项)
  • 通过 load()/preload() 加载 .dialogue 资源,不要手动解析
  • 每次调用 get_next_dialogue_line / GetNextDialogueLine 时都使用 await
  • 手动遍历循环通过 null 返回值检测对话是否结束(GDScript 中使用 if not line:,C# 中使用 line != null)——绝不要与 {} 比较
  • 响应行应将 [if condition] 放在任何 => target 跳转之前,绝不要放在之后
  • extra_game_states 条目必须是实例(GameStateClass.new()),不能是裸类
  • 对话读取/写入的 C# 状态属性都应带有 [Export]
  • C# 代码应使用 using DialogueManagerRuntime; 和 PascalCase 成员(ShowDialogueBalloon、GetNextDialogueLine、DialogueStarted、GotDialogue)
  • 自定义气泡应在 Settings 中覆盖 Balloon Path,而不是分叉 show_dialogue_balloon 调用点
  • 为任何需要配音或稳定 CSV/PO 键的行添加静态翻译 ID([ID:KEY])
  • 内联 do/await 修改若不应阻塞调用方,则使用 do!

评论

支持 Markdown