| Engine | Godot 4.2 or newer |
|---|---|
| Language | GDScript |
| Drives | A CanvasLayer/Control dialogue box |
| Time to run | ~4 minutes |
| Level | Intermediate |
| Output | One script + a short scene setup note |
| Dependencies | None. No plugins. |
The prompt
Open a fresh chat at claude.ai/new, paste, and send.
$ Build me a Godot 4.2+ dialogue system as a single GDScript that reads conversations from JSON. # data format - Dialogue is a JSON array of nodes. Each node: { "id": string, "speaker": string, "text": string, "next": string_or_null, "choices": optional array of { "text": string, "next": string } }. - Loaded from a user-supplied path or a passed-in parsed Dictionary/Array. # scene it drives (describe, do not build the .tscn) - Assume a CanvasLayer with: a Label/RichTextLabel for text, a Label for speaker, and a VBoxContainer for choice Buttons. Export NodePaths to them. # behaviour - start(dialogue, start_id) begins the conversation. - Typewriter reveal: show the current line one character at a time on a @export chars_per_second (default 40). - On advance input (ui_accept): if still typing, instantly finish the line; if finished, go to next. - If a node has choices, show a Button per choice after the text finishes; picking one jumps to that choice's next. - When next is null and there are no choices, emit a signal dialogue_finished and hide the box. - Emit a signal line_shown(id) each line (handy for triggers). # constraints - Godot 4 API (JSON.parse_string, signals via signal keyword, await/timers for the typewriter). - Guard against a missing id (log and finish, do not crash). - No magic numbers. # return format - The GDScript, plus a short comment block listing the scene nodes and export paths needed. - No prose before or after.
What this gets you
A dialogue system that scales past the first cutscene. Because the conversation is data, you can write a whole branching quest in a text file and never touch code again. The typewriter and choice buttons are handled, and signals let the rest of your game react to specific lines.
It drives a plain Control scene you own, so styling the box to match your game is just editing nodes, not fighting a plugin's built-in look.
Why it matters
Data-driven, not code-driven
Hard-coding dialogue as print statements or match blocks falls apart around line thirty. Keeping it in JSON means writers, translators and you can change words without risking logic, and the same engine plays every conversation in the game.
The typewriter needs an interrupt
Players hammer the advance button. A typewriter that ignores input until it finishes feels sluggish. The right behaviour is: first press completes the line instantly, second press advances. This prompt asks for exactly that.
Branching by id, not by nesting
Flat nodes referenced by id (a graph) handle loops, merges and skips that nested if/else dialogue cannot. It is the structure every serious dialogue tool uses under the hood.
What good output looks like
- Advance input has two stages. A press while typing should finish the line; a press after should advance. If one press does both or the line cannot be skipped, say so and regenerate.
- Choices jump by next id. Each choice button should set the current node to its
nextid, not to an index. Ids survive reordering the file; indices do not. - Finish is signalled. There should be a
dialogue_finishedsignal (and ideallyline_shown), so the rest of the game can react without polling. - Missing id fails safe. Jumping to an id that does not exist should log and end the conversation, not throw.
How to wire it up
- Save the output as
dialogue.gd. - Build the scene from the comment block: a
CanvasLayerwith a text label, a speaker label, and aVBoxContainerfor choices. Attach the script and wire the export NodePaths. - Write a conversation in a
.jsonfile matching the node format. - Load it with
JSON.parse_string(FileAccess.get_file_as_string(path))and callstart(data, "intro"). - Connect the
dialogue_finishedsignal to whatever should happen after the conversation.
Common gotchas
Text appears all at once
The typewriter loop is not awaiting between characters. It should reveal characters over time (a timer or await), not set the full string in one frame.
Choices do nothing
The buttons are created but their pressed signal is not connected to a handler that sets the next id. Bind each button to its choice when you create it.
It crashes at the end of a branch
A node's next points to an id that does not exist, or is null without being handled. Treat null-and-no-choices as finished, and a missing id as a safe end.
Where to go next
- Variables. Support simple placeholders like {player_name} replaced at display time.
- Conditions. Let choices carry a condition (a flag) so options appear only when unlocked.
- Save integration. Store seen lines and flags via the save system.
Use it however you want
The prompt is CC0, paste it, modify it, ship a game with it. The output Claude gives you is yours. A link back to pixeldex.dev in your project's README helps the next solo dev find this stuff.