doc 1 of 5

Main Menus — the game around the game

The menu is the first thing a stranger touches and the last thing you build. That order is why menus are usually bad. You wire a Start button, hit a paused menu that pauses itself, save settings to a path that turns read-only the moment you export, and ship a screen nobody can operate without a mouse. None of those are hard problems. They are problems with sharp, specific failure modes that look like something else — a frozen button looks like a broken signal, a silent volume slider looks like a broken audio bus, a dead D-pad looks like a broken input map. This shelf builds the four pieces of a menu system in the order that lets each one break in front of you first: scene flow, pausing, settings that survive a restart, and navigation that works with the mouse unplugged.

What this shelf teaches#

PartYou buildThe idea
01A menu and game scene, via change_scene_to_file, then a root swapping a child.Changing scenes is half-immediate: the old leaves now, the new arrives next frame.
02A pause menu on Esc, with Resume, Restart, Quit, and a reliable toggle.Pausing is a global flag plus a per-node policy, not a freeze.
03A settings screen (volume, fullscreen) written to ConfigFile in user://, applied on boot.Settings live in two places, and the bug is one of them not updating.
04A full menu flow on keyboard and gamepad alone, with visible, deliberate focus.Focus is real state that starts as null, and nothing moves from null.

Part 01 plays music across the scene swap to prove the difference — every line you write after the change runs in the gap before the new scene arrives. Part 02's toggle works because the two nodes that must opt out of the pause are the menu itself and whatever is listening for the key that opens it. Part 04's flow runs main menu, settings, back, and start end to end, with a sound on every focus change.

Where reasonable people differ#

These are the forks you will hit. Both sides are shipped games. The deciding question matters more than the verdict.

One-liner scene changes, or a persistent root that swaps a child. change_scene_to_file is two lines and no architecture. A root Main node that frees and refills a container child costs you a scene you must run every time and a bit of setup, and buys you a place for music, transitions and settings to live above the swap. Deciding question: does anything need to survive the transition? If the answer is no — a jam build, a two-screen prototype — the one-liner is not technical debt. It is the right tool. If the answer is yes, adding persistence later means touching every transition you already wrote.

Persist the slider value, or persist decibels. Save the raw 0.01.0 slider position and convert on apply, or save the dB value and run db_to_linear to place the slider on load. Both are correct and both are one conversion. Deciding question: which number does the rest of your code want to read? Pick one and write it down somewhere, because mixing them produces a slider that jumps on every launch — a bug that only appears after a restart, which is the last thing anyone tests.

Settings applied by the menu, or applied by an autoload the menu calls. The tempting shape is a settings scene whose _on_value_changed handlers talk straight to AudioServer and DisplayServer. It works perfectly while the menu is open. Deciding question: what applies the setting when the menu never opens? If nothing does, the game boots with the scene file's defaults and sounds wrong until the player visits a screen they had no reason to visit.

Every menu is its own scene, or the menu is one scene with swapped panels. Separate scenes keep each screen small and independently runnable. A single scene with visible toggled per panel keeps focus, theme and background continuous, and makes transitions between screens free. Deciding question: how many screens, and do they share a background? Two or three screens over one shared backdrop argue for panels; a dozen screens with distinct layouts argue for scenes.

Prerequisites#

You should be comfortable with:

  • Making a scene, adding nodes, and running it with F6 — and knowing which scene F5 runs.
  • Connecting a signal to a script, either through the Node dock or in code with connect.
  • Typed GDScript: var volume: float = 0.8, func apply_settings() -> void:. Every snippet on this shelf is typed. If that syntax is new, read Typed GDScript first.
  • Basic Control nodes and containers — Button, Panel, VBoxContainer. You do not need to understand anchors deeply; the parts that need layout will say so.

You do not need a finished game. A scene with a coloured rectangle you can move is enough to pause, and enough to prove that pausing worked.

Godot 4.7, 2D, Godot Physics throughout. Where a class has more surface than a part uses, the part names the concept and points you at the class reference rather than listing methods you will not call.

Where this shows up in the projects#

Precision Platformer needs all four parts. Its restart-on-death loop is Part 01's reload_current_scene question in miniature — whether the level reloads or the root swaps a child decides whether your death timer survives. Its pause menu meets Part 02's second trap immediately, because a platformer's input handler is exactly the kind of node that pauses itself.

The state machine shelf and this one meet at the pause boundary: a machine whose states run in _physics_process stops advancing when the tree pauses, which is usually what you want and occasionally a surprise. Part 02 explains why. If your settings screen grows past two options, Save and Load covers the question Part 03 deliberately answers narrowly — what counts as the authoritative copy of a value that exists both in a file and in memory.


Next: Part 01 — Getting from one scene to another. Read it before you build any menu, because the shape you choose there determines how much of Parts 02 through 04 you get for free.