--- id: jame581/GodotPrompter/dialogue-manager version: "9035549f" license: MIT install: manual updated: 2026-07-27 --- # dialogue-manager — Dialogue Manager is a Godot addon that lets you write branching conversations in a readable `.dialogue` text format instead of Inspector-edited data structures. It handles conditions, state mutations, random lines, and built-in translation workflows (CSV/PO), with both GDScript and C# support. Use it when writers need script-like authoring; for full adventure-game frameworks with rooms and inventory, pair it with a larger system. Publisher: jame581 · Stars: 487 · Updated: 2026-07-27 Install (manual): `git clone https://github.com/jame581/GodotPrompter` ## SKILL.md # Dialogue Manager > **Related skills:** **dialogue-system** for hand-rolled dialogue data structures, **localization** for translating lines, **popochiu** for full adventure-game workflows. > **Addon:** Dialogue Manager · version `v3.10.4` · Godot 4.6 · MIT · source: https://github.com/nathanhoad/godot_dialogue_manager · GDScript with official C# support. --- ## 1. When to use Dialogue Manager | Approach | Best for | |---|---| | **dialogue-system** skill (hand-rolled `Resource` data) | Full control over data shape, no addon dependency, small dialogue trees | | **Dialogue Manager** | Script-like `.dialogue` text format, branching responses, conditions/mutations, translation pipeline, visual editor tab, official C# wrapper | | Full adventure-game framework | Point-and-click games needing rooms/inventory/actors bundled with dialogue — Dialogue Manager only owns the dialogue layer | Choose Dialogue Manager when writers want to author branching dialogue as readable script text (not Inspector-edited Resources) and you want built-in conditions, mutations, random lines, and a translation workflow (CSV or PO) for free. If you need dialogue as strongly-typed Resources you fully own, use `dialogue-system` instead. Dialogue Manager only handles dialogue — it is not a full adventure/quest framework, so a larger point-and-click framework would still own rooms, inventory, and actors around it. --- ## 2. Install & setup 1. Godot AssetLib → search "Dialogue Manager" → Download. Or copy `addons/dialogue_manager/` from the [GitHub repo](https://github.com/nathanhoad/godot_dialogue_manager) into `res://addons/dialogue_manager/`. 2. **Project → Project Settings → Plugins** → enable "Dialogue Manager". This adds a **Dialogue** tab to the bottom editor panel and registers the `DialogueManager` autoload automatically — no manual autoload step needed. 3. `addons/dialogue_manager/plugin.cfg`: ```ini [plugin] name="Dialogue Manager" description="A powerful nonlinear dialogue system" author="Nathan Hoad" version="3.10.4" script="plugin.gd" ``` 4. C# projects need no extra NuGet package — `using DialogueManagerRuntime;` is enough (§4). 5. Settings live at **Project Settings → General → Dialogue Manager**: notably **State Autoload Shortcuts** (autoload names usable in dialogue without a prefix) and **Balloon Path** (the scene `show_dialogue_balloon()` opens — leave empty to use the built-in example balloon). --- ## 3. `.dialogue` syntax Open/create a `.dialogue` file from the **Dialogue** editor tab. Lines are `Character: text` or bare `text` (narrator). Godot's RichTextLabel BBCode works, plus extras: `[[A|B|C]]` (random inline pick), `[wait=N]` / `[wait="ui_accept"]` (pause typing), `[speed=N]`, `[next=auto]` (auto-advance). **Titles and jumps** — `~ name` marks a title; `=> name` jumps to it; `=> END` ends the flow; `=> END!` force-ends past any pending jump-and-returns; `=>< name` jumps and returns here once that branch hits `END`: ``` ~ start Nathan: Well? - First one - Another one => another_title - Start again => start => END ~ another_title Nathan: Another one? => END ``` **Responses** (`- `) nest by indentation and can carry a condition in `[...]` — put any jump *last*: ``` 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 ``` **Conditions** — `if` / `elif` / `else`, boolean `and`/`or`/`()`, and `match`/`while` blocks: ``` 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. ``` Inline conditions: `Nathan: I have done this [if already_done]once again[/if]`, with an optional `[else]`. Null-safe member access uses `?.`: `if some_node_reference?.name == "SomeNode"`. **Mutations** — `set` assigns state, `do` calls a method or emits a signal; both can run inline (`[do wave()]`, suppress the implicit `await` with `[do! wave()]`): ``` if SomeGlobal.has_met_nathan == false do SomeGlobal.animate("Nathan", "Wave") Nathan: Hi, I'm Nathan. set SomeGlobal.has_met_nathan = true ``` Built-in mutations: `do wait(float)`, `do debug(...)`. Emit a signal from dialogue with `do SomeGlobal.some_signal.emit("arg")`. **Locals vs extra game states** — `set locals.asked = true` creates a per-conversation temp variable (a convention implemented by the *example balloon*, not a Dialogue Manager core feature). Objects passed in the `extra_game_states` array are referenced directly by name and their mutations persist after the conversation ends — pass **instances**, not classes (`GameStateClass.new()`, never the bare class). **Randomised lines** — prefix with `%` (equal weight) or `%N` (relative weight); a blank line separates random groups: ``` 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 ``` **Variables and tags** — `{{SomeGlobal.some_property}}` interpolates state into text (also usable as a character name). `[#happy, #mood=calm]` attaches tags, readable via `line.get_tag_value("mood")`. --- ## 4. Runtime API & example balloon Load a `DialogueResource` (`.dialogue` file) and either let `DialogueManager` open a balloon for you, or pull lines manually with `await DialogueManager.get_next_dialogue_line(resource, title)`. When there is no next line it returns `null` — check falsy in GDScript (`if not line:` / `while line:`) and `line != null` in C#. (The tag's `API.md` prose says "empty dictionary `{}`", but the v3.10.4 source returns `null` on every end-of-dialogue path — a `line == {}` check would never fire.) ### GDScript ```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# ```csharp // 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); } } } } ``` Other `DialogueManager` methods: `show_dialogue_balloon_scene(balloon_scene, resource, title)` (open a specific balloon scene), `show_example_dialogue_balloon(resource, title)` (force the built-in balloon), `create_resource_from_text(text)` (compile a `.dialogue` string at runtime — fails on syntax errors). C# names are identical PascalCase: `ShowDialogueBalloonScene`, `ShowExampleDialogueBalloon`, `CreateResourceFromText`. `get_next_dialogue_line` takes a `mutation_behaviour` param — GDScript `DMConstants.MutationBehaviour`, C# `MutationBehaviour` (both: `Wait` default, `DoNotWait`, `Skip`). The enum lives on `DMConstants`, **not** on the `DialogueManager` autoload. Leave it `Wait` unless you know otherwise; the example balloon only supports `Wait`. --- ## 5. Signals & typed access `DialogueLine` fields: `id`, `next_id`, `character`, `text`, `tags` (`PackedStringArray`), `translation_key`, `responses` (`Array[DialogueResponse]`), `concurrent_lines`. `DialogueResponse` adds `is_allowed: bool` and `condition_as_text: String` on top of the same `id`/`next_id`/`character`/`text`/ `tags`/`translation_key` fields. ### GDScript ```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# ```csharp 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) => { }; } ``` > ⚠️ These C# events are bridged to the underlying Godot signals lazily, on the **first access to > `DialogueManager.Instance`**. A project that only subscribes here and then drives dialogue from > GDScript never touches `Instance`, so the handlers never fire. Touch the API from C# at least once > (e.g. `_ = DialogueManager.Instance;`, or run the dialogue via `GetNextDialogueLine`/`ShowDialogueBalloon` > from C#) to wire them up. The built-in responses menu node only exposes `response_selected` as a Godot signal, so connect it with `Connect` + `Callable` instead of a C# event handler: ```csharp responsesMenu.Connect("response_selected", Callable.From((DialogueResponse response) => { // advance using response.NextId })); ``` `passed_title(title)` (GDScript) / `DialogueManager.PassedTitle += (string title) => { }` (C#) fires every time a `~ title` marker is crossed — useful for analytics or save-point bookmarking. --- ## 6. Translations By default all dialogue/response text is run through Godot's `tr()`. The `DialogueManager.translation_source` property picks the backend — its enum type is GDScript `DMConstants.TranslationSource` / C# `TranslationSource` (`None`, `CSV`, `PO`, `Guess` default), **not** `DialogueManager.TranslationSource`. `Guess` inspects your locale project settings for a PO file and falls back to CSV. Static per-line IDs (`Nathan: Hi! I'm Nathan. [ID:HI_IM_NATHAN]`) give a stable `translation_key` for matching voiced lines and CSV/PO round-trips, instead of keying off the literal text. `.dialogue` files auto-register in the POT Generation list; a `##` comment line before a dialogue line becomes a `#. TRANSLATORS:` note in the exported PO/POT. Export/import CSV from the editor's **Translations** menu (Dialogue tab); re-import matches by static ID when present, otherwise by literal text. See the **localization** skill for `TranslationServer`, locale switching, and RTL — Dialogue Manager only produces the translation keys and CSV/PO export; wiring locale changes into the running game is `localization`'s job. --- ## Implementation checklist - [ ] Dialogue Manager plugin enabled in **Project Settings → Plugins** (registers the `DialogueManager` autoload automatically) - [ ] `.dialogue` resources loaded via `load()`/`preload()`, not parsed by hand - [ ] Every `get_next_dialogue_line` / `GetNextDialogueLine` call is `await`ed - [ ] Manual traversal loops detect dialogue end via the `null` return (`if not line:` in GDScript, `line != null` in C#) — never compare against `{}` - [ ] Response lines put `[if condition]` before any `=> target` jump, never after - [ ] `extra_game_states` entries are **instances** (`GameStateClass.new()`), not bare classes - [ ] C# state properties that dialogue reads/writes carry `[Export]` - [ ] C# code uses `using DialogueManagerRuntime;` and PascalCase members (`ShowDialogueBalloon`, `GetNextDialogueLine`, `DialogueStarted`, `GotDialogue`) - [ ] Custom balloons override **Balloon Path** in Settings rather than forking `show_dialogue_balloon` call sites - [ ] Static translation IDs (`[ID:KEY]`) added to any line needing voice-over or stable CSV/PO keys - [ ] Inline `do`/`await` mutations use `do!` when the caller shouldn't block on them [View on SkillFed](https://skillfed.io/jame581/GodotPrompter/dialogue-manager) · [View on GitHub](https://github.com/jame581/GodotPrompter)