5.8 KiB
F1 Debug Menu Design
Overview
An F1-toggled debug menu overlay that works regardless of the current scene. Introduces a new root scene (game.tscn) that hosts both the debug menu and the active game scene as children. The game pauses underneath while the debug menu is open.
Scene Tree Structure
Game (Node, process_mode = PROCESS_MODE_ALWAYS)
├── DebugMenu (CanvasLayer, layer 100)
│ ├── Panel (PanelContainer)
│ │ ├── SceneList (VBoxContainer of Buttons)
│ │ └── CommandInput (LineEdit)
│ └── ResultLabel (Label, brief flash for command output)
└── ActiveSceneContainer (Node)
└── <currently loaded scene>
Gameis the new main scene, replacingstrategy_phase.tscnas the entry point inproject.godot.DebugMenuuses a highCanvasLayer(layer 100) so it always renders above the active scene.ActiveSceneContaineris a plainNodewhose child gets swapped when changing scenes.Gamehasprocess_mode = PROCESS_MODE_ALWAYSso it can receive F1 input even while the tree is paused.
Responsibilities
Game (game.gd)
Thin shell only:
- F1 input toggles
DebugMenuvisibility. - When debug menu opens: sets
ActiveSceneContainer.process_mode = PROCESS_MODE_DISABLED. - When debug menu closes: sets
ActiveSceneContainer.process_mode = PROCESS_MODE_INHERIT. - Loads the default scene (strategy_phase) into
ActiveSceneContainerat startup. - Passes a reference to
ActiveSceneContainertoDebugMenuat_ready().
DebugMenu (debug_menu.gd)
Owns all debug functionality:
- Scene registry: hardcoded array of scene entries.
- Scene swapping: frees old child of
ActiveSceneContainer, instantiates new scene, applies setup if present. - Command console: parses input, dispatches to registered commands or falls back to expression eval.
- Command registration: holds an explicit
Array[ConsoleCommand]populated at_ready().
Scene Swapping
Registry Format
A hardcoded array in DebugMenu:
var scene_registry: Array = [
{ "name": "Strategy Phase", "path": "res://scenes/strategy_phase.tscn" },
{ "name": "Strategy Phase (Test Data)", "path": "res://scenes/strategy_phase.tscn", "setup": "test_data" },
{ "name": "Main Menu", "path": "res://scenes/main_menu.tscn" },
{ "name": "Visual Novel", "path": "res://scenes/vn_scene.tscn" },
{ "name": "Dialogue", "path": "res://scenes/dialogue_scene.tscn" },
]
Swap Flow
- Free the current child of
ActiveSceneContainer. - Instantiate the new scene from
pathand add as child. - If the entry has a
"setup"key, call a setup method onDebugMenuthat applies that configuration to the new scene instance. - Close the debug menu and signal
Gameto unpause.
Setup Hooks
Setup functions live in DebugMenu, dispatched by matching the "setup" string key. This keeps test configurations self-contained within the debug system.
Command Console
Input
A LineEdit at the bottom of the debug panel. On Enter:
- Split input text — first token is the command name, rest are arguments.
- Check against registered commands by matching
get_command_name(). - If found, call
run()with args and context. - If no match, fall back to Godot's
Expressionclass (evaluated with the active scene as base instance). - Show the result (or error) in
ResultLabel, then hide it after 2 seconds using aTimerorcreate_tween().
Command Pattern
Base class:
class_name ConsoleCommand
func get_command_name() -> String:
return ""
func get_help_text() -> String:
return ""
func run(args: Array, context: Dictionary) -> String:
return ""
Each command extends ConsoleCommand and overrides these three methods. The context dictionary provides access to the active scene container, debug menu reference, and other shared state without coupling commands to specific nodes.
Commands are explicitly registered in an Array[ConsoleCommand] in DebugMenu._ready().
Starter Commands
help— lists all registered commands with their help text.swap <name>— scene swap by name (matches against registry entry names).list_scenes— prints available scene registry entries.
Expression Eval Fallback
Uses Godot's Expression class. The expression executes with the current active scene as the base instance, allowing direct access to nodes and properties in the loaded scene. Errors are displayed in ResultLabel.
File Organization
nodes/
├── game.gd # Thin shell
├── debug_menu.gd # Scene registry, command console, swap logic
resources/resource_definitions/
├── console_command.gd # Base class
resources/console_commands/
├── swap_command.gd
├── help_command.gd
├── list_scenes_command.gd
scenes/
├── game.tscn # New root scene (new main_scene in project.godot)
prefabs/
├── debug_menu.tscn # CanvasLayer with panel, buttons, input, result label
Existing scenes are unchanged — they are loaded as children of ActiveSceneContainer instead of being the root.
Key Decisions
- No autoload: The debug menu is a UI node, not a headless service. The
XXXServerautoload pattern is reserved for game services like a futureSceneManagerServer. - Explicit command registration: Commands are listed in an array rather than auto-discovered from a folder. Simpler and more readable for a small number of commands. Can be swapped to auto-discovery later if needed.
- Single-line output:
ResultLabelshows one result at a time with a brief display. No scrollable history for now. - Hardcoded scene list: Entries can carry setup data/context, which auto-discovery from
.tscnfiles wouldn't support.