Genre: Visual Novel
Branching narratives, meaningful choices, and quality-of-life features define visual novels.
Core Loop
- Read → dialogue / narration
- Decide → choice moment
- Branch → flag or path change
- Consequence → immediate line variation and/or lasting flag
- Conclude → one of multiple endings
NEVER Do (Expert Anti-Patterns)
Narrative & Flow
- NEVER create the "Illusion of Choice" exclusively; strictly provide Immediate Dialogue Variations or Flag Changes even if the plot converges later.
- NEVER skip mandatory QoL features; strictly implement Auto-Play, Fast-Forward, and Backlog/History for replayability.
- NEVER display "Walls of Text"; strictly limit dialogue boxes to 3-4 Lines max to avoid intimidating the reader.
- NEVER hardcode dialogue text inside GDScripts; strictly store narrative scripts in External Files (JSON, CSV, or custom Resources) for iteration.
- NEVER ignore the Rollback mechanic; strictly maintain a history stack so players can undo miss-clicks or reread missed lines.
Technical & UI
- NEVER use plain text for emotional beats; strictly use RichTextLabel BBCode (e.g.,
[shake],[wave]) to add visual weight. - NEVER parse massive narrative files on the main thread; strictly use
ResourceLoader.load_threaded_request()to prevent transition stutters. - NEVER use standard Strings for frequently accessed game flags; strictly use
StringName(&"met_alice") for faster dictionary lookups. - NEVER use
_processfor letter-by-letter animation; strictly use a Tween onvisible_ratiofor smooth, frame-independent reveals. - NEVER neglect character Z-ordering; strictly ensure the active speaker is brought to the front for visual clarity.
- NEVER use
z_indexforControlnode priority if input handling is required; strictly usemove_to_front()to ensure draw order and input propagation match. - NEVER use absolute pixel positioning for character sprites; strictly rely on Anchors & Percent-based Offsets for responsive scaling.
- NEVER allow text animations to continue when the player skips; strictly set
visible_ratioto 1.0 instantly on input. - NEVER leave orphaned character sprites; strictly use
queue_free()when actors exit the stage to prevent memory leaks. - NEVER mutate flags before snapshotting rollback state — always push history, then apply the choice.
🛠 Expert Components (scripts/)
MANDATORY before implementing undo / branching / presentation:
- vn_rollback_manager.gd — history stack (flags/backgrounds/index)
- story_manager.gd — flag-aware dialog orchestration
- dialogue_ui.gd — typewriter + choice UI
- visual_novel_patterns.gd — BBCode, choice filtering, sprite layering
Catalog (deduped)
- story_manager.gd - Flag-aware dialog orchestrator with branching logic and character state persistence.
- dialogue_ui.gd - Presentation layer: typewriter tweens (
visible_ratio) and choice-window generation. - vn_rollback_manager.gd - History stack for state rollback (flags/backgrounds/index).
- visual_novel_patterns.gd - Reusable BBCode effects, choice filtering by flags, sprite layering.
Decision Tree: Script Storage vs Plugin
| Approach | When to choose | Notes |
|---|---|---|
| JSON / CSV scripts | Writers edit outside Godot; rapid iteration | Load via FileAccess or threaded ResourceLoader; validate schema in StoryManager |
Custom Resource dialogue trees |
Designer Inspector editing, typed fields | Peer godot-resource-data-patterns; MANDATORY story_manager.gd |
| Dialogic (plugin) | Full VN suite (timelines, characters, themes) with editor tooling | Prefer when shipping a large route graph fast; still keep rollback + flag discipline. Skip building a second StoryManager if Dialogic already owns timelines |
| Build lightweight custom | Tiny kinetic novel / learning project | Use scripts in this skill; do not re-stub StoryManager inline |
Do not paste incomplete JSON StoryManager demos — implement from MANDATORY story_manager.gd.
Golden Path (order matters)
- Snapshot before mutate — On every advance/choice, MANDATORY vn_rollback_manager.gd pushes
{line_index, flags, background, music}before flag writes. - Typewriter + skip — dialogue_ui.gd: Tween
visible_ratio0→1; on skip/advance input setvisible_ratio = 1.0and kill the tween. - Choice filter by flags — Present only options whose
requiresStringName flags pass; apply choice → mutate flags → jump label (visual_novel_patterns.gd + story_manager.gd). - Speaker focus —
move_to_front()on Control actors (notz_indexalone) + dim inactive. - Heavy CG/BG —
ResourceLoader.load_threaded_requestfor backgrounds; never sync-parse huge scripts on the main thread.
# Choice handler shape (flags after snapshot)
func make_choice(choice_id: StringName) -> void:
rollback_manager.push_snapshot() # BEFORE mutate
match choice_id:
&"be_nice":
flags[&"relationship_alice"] = int(flags.get(&"relationship_alice", 0)) + 1
story_manager.jump_to_label(&"alice_happy")
&"be_mean":
flags[&"relationship_alice"] = int(flags.get(&"relationship_alice", 0)) - 1
story_manager.jump_to_label(&"alice_sad")
Common Pitfalls
- Walls of text — Cap dialogue to 3–4 lines.
- Illusion of choice — Always vary lines or flags even on converging plots.
- Missing QoL — Auto / Skip / Backlog / Save are mandatory genre features.
- Broken rollback — Mutating flags before snapshot makes undo lie.
Deep recipes (on demand)
| Topic | Reference / script |
|---|---|
| Story driver & typewriter UI | architecture-overview.md |
| Branching / rollback / focus | key-mechanics.md |
| RichText / async loads | godot-tips.md |
Reference
Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain — do not preload the whole lattice.
Official Documentation
- BBCode in RichTextLabel — shake/wave BBCode and append_text for emotional dialogue without plain Label walls.
- Size and anchors — percent offsets and anchors so character sprites and dialogue boxes scale across resolutions.
- GUI containers — VBox/HBox choice rows and dialogue chrome instead of absolute pixel layouts.
- Background loading — ResourceLoader.load_threaded_request so heavy CG/background swaps never hitch the typewriter.
- Saving games — FileAccess patterns for flags, history stacks, and multi-slot VN saves.
- Resources — typed dialogue/choice Resources as an alternative to brittle hardcoded JSON strings.
- Internationalizing games — tr() / CSV keys so script lines stay localization-ready.
- Audio streams — BGM crossfades and optional voice lines tied to line advances.
- Using InputEvent — skip/advance/ui_accept handling that finishes visible_ratio instantly.
- Singletons (Autoload) — persistent flag/history owners across chapter scene changes.
- Signals — line_advanced / options_presented wiring between StoryManager and DialogueUI.
- Tween — tween_property on RichTextLabel.visible_ratio for frame-independent typewriter reveals.
Related Skills
Prerequisites
- godot-project-foundations — scene tree, autoloads, and import basics before wiring a StoryManager driver.
- godot-ui-rich-text — RichTextLabel BBCode, visible_ratio, and append_text performance for dialogue boxes.
- godot-ui-containers — responsive choice panels and dialogue chrome without absolute pixel placement.
- godot-signal-architecture — typed signals so UI presentation stays decoupled from branching logic.
Complements
- godot-dialogue-system — reusable dialogue runners and line data shapes that genre VNs specialize.
- godot-tweening — typewriter tweens, sprite fades, and background crossfades without AnimationPlayer spam.
- godot-resource-data-patterns — Resource-based dialogue trees and duplicate(true) for mutable flag state.
- godot-input-handling — skip, auto-advance, and backlog input actions without fighting Control focus.
- godot-audio-systems — BGM buses and voice ducking synced to line/choice beats.
- godot-ui-theming — theme type variations for nameplates, choice buttons, and backlog chrome.
- godot-monte-carlo-balancer — simulate affinity thresholds and ending distribution before shipping branch weights.
Downstream / consumers
- godot-save-load-systems — multi-slot, threaded saves for flags/history beyond ConfigFile demos.
- godot-genre-romance — dating-sim affinity loops that reuse VN flags, rollback, and choice filtering.
Master
- godot-master — library router and mirrored module entry for cross-skill discovery.