Install
$ agentstack add skill-jame581-godotprompter-dialogue-system ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Dialogue Systems in Godot 4.3+
All examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.
> Related skills: resource-pattern for dialogue data as Resources, godot-ui for Control node layout, state-machine for dialogue flow management, save-load for dialogue state persistence.
1. Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ 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 Resource
DialogueLine holds all data for a single beat of dialogue. Choices is an Array[Dictionary] so each entry can carry a text, next_line_id, and optional condition without a separate class.
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
{
/// Display name shown in the speaker box.
[Export] public string Speaker { get; set; } = "";
/// Body text. Supports BBCode and {variable} placeholders.
[Export(PropertyHint.MultilineText)]
public string Text { get; set; } = "";
///
/// 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
///
[Export] public Array Choices { get; set; } = new();
/// ID of the next DialogueLine. Ignored when Choices is non-empty.
[Export] public string NextLineId { get; set; } = "";
///
/// Optional condition expression. Evaluated before displaying this line.
/// Example: "GameState.HasItem(\"key\")"
///
[Export] public string Condition { get; set; } = "";
}
3. DialogueData Resource
DialogueData is a container Resource that holds a dictionary of all lines, keyed by their string ID. Creating it as a .tres file lets you assign it to NPCs in the Inspector.
GDScript
# dialogue_data.gd
class_name DialogueData
extends Resource
## Dictionary mapping line ID strings to DialogueLine resources.
## Example: { "intro": , "ask_quest": }
@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
{
/// Maps line ID strings to DialogueLine resources.
[Export] public Dictionary Lines { get; set; } = new();
/// ID of the first line to display when dialogue starts.
[Export] public string StartLineId { get; set; } = "";
/// Returns the DialogueLine for id, or null if not found.
public DialogueLine GetLine(string id)
{
if (Lines.ContainsKey(id))
return Lines[id].As();
return null;
}
}
> Populate lines in the Inspector by adding Dictionary entries with string keys and DialogueLine resource values, or load them programmatically from JSON (see section 7).
4. DialogueManager
A singleton autoload owns the active DialogueData and tracks current line ID. start(data) sets the data and emits the first line; advance(choice_index) moves forward. Wires three signals: line_changed(line), choices_presented(choices), dialogue_ended().
> See [references/dialogue-manager.md](references/dialogue-manager.md) for the full GDScript and C# manager (line traversal, choice handling, condition evaluation hooks, signals).
5. Branching and Conditions
Choices live on DialogueLine.choices (an Array of Dictionary). Each choice has text, next_line_id, optional condition. Conditions are GDScript expressions evaluated via the Expression class — passed a context object with project state (e.g. GameState).
> See [references/branching-and-conditions.md](references/branching-and-conditions.md) for the full choice-handling and condition-evaluator implementations (GDScript + C#), plus security notes on Expression input.
6. Dialogue UI
A CanvasLayer with a RichTextLabel for the line body (BBCode-enabled), a Label for speaker name, and a VBoxContainer for choice buttons. Typewriter effect via RichTextLabel.visible_characters driven by a Tween. UI subscribes to DialogueManager signals.
> See [references/ui-presentation.md](references/ui-presentation.md) for the scene-tree fragment, typewriter recipe, choice-button spawning, and full GDScript + C# wiring.
7. External Formats
Load dialogue from JSON for designer-friendly editing — map JSON keys to DialogueLine properties at load time. Or integrate the Dialogic addon for a node-graph editor (community standard).
> See [references/external-formats.md](references/external-formats.md) for the JSON loader recipe and Dialogic integration notes.
8. Variable Interpolation
Dialogue text supports {player_name}-style placeholders. Resolve via a small templater: text.format(vars) (GDScript) or string.Format with named-tag preprocessing (C#).
> See [references/variable-interpolation.md](references/variable-interpolation.md) for the GDScript and C# interpolation helpers.
9. Implementation Checklist
- [ ]
DialogueLineandDialogueDataextendResourceand carry[GlobalClass](C#) for Inspector integration - [ ]
DialogueManageris registered as an Autoload so all scenes share a single instance - [ ]
start_dialogue()asserts thatdialogue_datais non-null before accessing it - [ ]
advance()guards against being called when choices are pending - [ ]
choose()operates on the filtered visible-choices list, not the rawchoicesarray - [ ] Condition strings reference only stable autoload method names — avoid referencing scene-local nodes
- [ ]
_evaluate_condition()passes a known base instance (GameState) toExpression.execute()to resolve method calls - [ ] Typewriter timer uses
visible_characters, not frame-by-frame string slicing, for BBCode compatibility - [ ] Pressing
ui_acceptmid-typewriter reveals full text; a second press advances the line - [ ] Choice buttons are freed (
queue_free) before creating new ones — never accumulate stale children - [ ] JSON loader validates file open and parse steps separately, emitting clear error messages for each failure
- [ ] Variable interpolation is centralised in one
_interpolate()helper, not scattered across signal handlers - [ ]
next_line_id = ""signals end-of-dialogue — no magic sentinel strings beyond the empty string - [ ] All
push_error()messages include class name and method for easy log tracing
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: jame581
- Source: jame581/GodotPrompter
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.