doc 3 of 5

Part 02 — Pausing, and the menu that pauses itself

You will build: A pause menu that opens on Esc over a frozen game, with Resume, Restart, and Quit to Menu buttons that all work. The toggle un-pauses as reliably as it pauses, and the menu is completely inert while you are playing.

Part 01 got you between scenes. This part stops one in place.

Pausing looks like the smallest feature in the game. One boolean. get_tree().paused = true. There is a property for it, you set it, done.

You are about to spend an hour on that one boolean, and it is an hour worth spending, because the two bugs waiting for you are the same bug seen from opposite sides, and once you have felt both you will never be confused by Godot's pause system again.

Build the broken version first. Do not skip ahead to the fix. The fix means nothing if you have not watched the thing fail.

The naive pause menu#

You need something to pause. Any scene with a moving player works — the platformer from the Precision Platformer curriculum, a top-down prototype, or a single CharacterBody2D that drifts across the screen. What matters is that you can see, at a glance, whether the game is running.

First, an input action. Open Project → Project Settings → Input Map, add an action called pause, and bind Escape to it. Do not reuse ui_cancel for this. ui_cancel is one of the built-in UI actions the focus system uses to navigate menus, and the GUI navigation docs are direct about it: the built-in ui_* actions should not be driving gameplay code. You will want them intact in Part 04 when the menu learns to work on a gamepad.

Now the menu scene. Create a new scene with this shape:

PauseMenu           (CanvasLayer)
└── Panel           (Panel)
    └── VBoxContainer
        ├── ResumeButton   (Button, text "Resume")
        ├── RestartButton  (Button, text "Restart")
        └── QuitButton     (Button, text "Quit to Menu")

CanvasLayer is the root for a reason. A CanvasLayer draws independently of the 2D camera, so the menu stays fixed on screen no matter where the player's camera has wandered. Its children are laid out in screen space. If you have ever built a HUD that slid off-screen when the camera moved, this is the node you were missing.

Give the Panel some size and centre it — anchors preset "Center" in the toolbar above the viewport, then set a custom minimum size on the VBoxContainer so the buttons are not hairlines. Exact numbers do not matter yet. Being able to see the buttons does.

Save it as pause_menu.tscn, attach a script to the PauseMenu root, and write this:

extends CanvasLayer


func _ready() -> void:
	hide()


func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("pause"):
		_toggle_pause()
		get_viewport().set_input_as_handled()


func _toggle_pause() -> void:
	var pausing: bool = not get_tree().paused
	get_tree().paused = pausing
	visible = pausing


func _on_resume_button_pressed() -> void:
	_toggle_pause()


func _on_restart_button_pressed() -> void:
	get_tree().paused = false
	get_tree().reload_current_scene()


func _on_quit_button_pressed() -> void:
	get_tree().paused = false
	get_tree().change_scene_to_file("res://scenes/main_menu.tscn")

Two details before you run it.

_unhandled_input rather than _input: unhandled input is what is left after the UI has had its turn. A Button that consumes a key press does so before this ever fires. That ordering will matter later; for now it is a good habit. set_input_as_handled() marks the event consumed so nothing further down the chain also reacts to the same Escape press.

The two paused = false lines in Restart and Quit exist because SceneTree.paused is a property of the tree, not of the scene. Change the scene while paused and the new scene arrives already frozen, with no menu open to un-freeze it. That is a different bug from the two you are about to hit, and it is cheap to prevent, so prevent it now.

Connect the three pressed signals to their functions in the Node dock. Then instance pause_menu.tscn into your game scene as a child of the root, and run.

Trap one: the buttons are dead#

Press Escape. The menu appears. The game stops. Everything looks correct.

Click Resume. Nothing.

Click it again. Still nothing. No error in the output panel, no red text, no warning. The button does not even show its hover highlight. The menu is drawn, perfectly, and it is completely deaf.

Sit with that for a second and try to name the cause before reading on.

The menu is a node in the tree. get_tree().paused = true paused the tree. The menu is in the tree. You froze your own escape hatch.

Every node you create in Godot starts with process_mode set to PROCESS_MODE_INHERIT — the inspector shows it as "Inherit" — which means it takes its answer from its parent. Walk up from ResumeButton: inherit, inherit, inherit, all the way to the root of the scene tree, which is PROCESS_MODE_PAUSABLE. Pausable means "stop when the tree is paused". So that is what your button does. Its input callbacks stop being delivered, and a button that receives no input is a picture of a button.

Nothing errored because nothing was wrong. The engine did exactly what you asked.

What pausing actually does#

Before you fix anything, get the model right, because the fix will otherwise be a spell you cast rather than a decision you make.

The SceneTree.paused docs spell out the behaviour:

2D and 3D physics will be stopped, as well as collision detection and related signals. Depending on each node's Node.process_mode, their Node._process(), Node._physics_process() and Node._input() callback methods may not called anymore.

Read that as two separate claims, because they are.

Claim one is unconditional. The physics servers stop. Bodies do not move, collisions are not detected, and the signals that come from collisions — body_entered, area_entered, and their friends — do not fire. This applies tree-wide. There is no per-node opt-out.

Claim two is conditional, and the condition is per node. Whether a given node keeps running _process, _physics_process and its input callbacks depends entirely on that node's process_mode.

So pausing is not a global freeze. It is a global flag plus a per-node policy. The flag is the half you already got right. The policy is where your bugs live.

The five answers to one question#

Node.process_mode has five values, and the cleanest way to hold them is as five deliberate answers to the question "should this node run while the tree is paused?"

ModeInspector labelThe answer it gives
PROCESS_MODE_INHERITInherit"Ask my parent." Default for every node you create.
PROCESS_MODE_PAUSABLEPausable"No — I stop when paused." Default at the root, so the default in practice.
PROCESS_MODE_WHEN_PAUSEDWhen Paused"Only while paused. Inert during play."
PROCESS_MODE_ALWAYSAlways"Yes, in both states. Ignore the pause entirely."
PROCESS_MODE_DISABLEDDisabled"Never, in either state."

Two of these deserve more than a table row.

Inherit is not a mode, it is a deferral. A node set to Inherit has no opinion; it resolves to whatever its nearest non-inheriting ancestor says. This is why the whole tree behaves as Pausable by default — every node defers upward until it hits the root, which is Pausable. It also means one setting can govern an entire branch, which is the property you are about to exploit.

When Paused is the interesting one. A pause menu should not merely tolerate being paused, it should be inert whenever the game is running. A menu that keeps polling input during play is a menu that can eat a key press it has no business seeing. When Paused makes the menu's dormancy structural instead of something you maintain with if visible: guards.

Note the spelling: PROCESS_MODE_WHEN_PAUSED, with the underscore. The inspector label reads "When Paused" as two words, which is where the common misspelling PROCESS_MODE_WHENPAUSED comes from. That constant does not exist.

If you find Godot 3 tutorials while searching this, they will talk about pause_mode and PAUSE_MODE_STOP / PAUSE_MODE_PROCESS. All of it is gone. The property is process_mode, the constants are PROCESS_MODE_*, and the old PAUSE_MODE_PROCESS maps to PROCESS_MODE_ALWAYS — not to anything with "process" in the readable part of the name.

Fixing trap one#

Select the PauseMenu root node. In the inspector, find the Process section, and set Process Mode to When Paused.

That is the entire fix. One property, one node.

It works for the whole menu because the Panel, the VBoxContainer and all three buttons are still on Inherit, so they defer upward and land on your new answer. The official pausing tutorial describes exactly this recipe: set the mode on the menu's root node, and "all its children and grandchildren will inherit that process mode."

This is also the argument for the menu having its own root node rather than being a handful of loose Control nodes parented into your HUD. One node owning the branch means one property to set and no chance of a button being left behind. If you had six buttons scattered under the HUD, you would be setting this six times and forgetting it on the seventh.

Run again. Press Escape.

Nothing. The menu does not appear at all.

Before you touch that, confirm the button fix actually landed, because you are about to stop being able to see it. Set the PauseMenu root to Always for one run. Press Escape, click Resume. The menu closes and the game moves. Trap one is fixed. Now set the root back to When Paused and read on, because the mode that repaired the buttons is the mode that killed the key.

Trap two: now Escape does nothing at all#

With the root on When Paused, press Escape from a running game as many times as you like. The game keeps running. There is no menu to close, because there was never a menu to open.

Same first impression as trap one — a key that does nothing, no errors — and a different cause. This is why the two are worth hitting in sequence. If you only ever learn one of them, the other will read as a mystery.

Walk it through. _unhandled_input lives on PauseMenu, which you just set to When Paused. When the game is running, the tree is not paused, so the menu is not processing, so its input callback never fires. Escape reaches nothing.

There is no first press that sneaks through, either. The Node.can_process() docs are exact about When Paused: it returns true when the game is paused. While the game plays, the menu's answer is false from the first frame, so the very first Escape of the run fails the same way as the fiftieth.

The shape of the problem is worth stating in the general form, because it survives every refactor you will ever do:

The node that toggles the pause must run in both states. It is the only thing in the game with a foot on both sides of the flag. There is no single mode that describes "runs when paused" and "runs when playing" other than Always.

You have two honest ways to arrange this, and they are both correct.

Option A: split the toggle from the menu#

Give the pause menu one job — being a menu — and put the key listener on a separate node set to PROCESS_MODE_ALWAYS.

extends Node


@export var pause_menu_path: NodePath

@onready var _pause_menu: CanvasLayer = get_node(pause_menu_path)


func _unhandled_input(event: InputEvent) -> void:
	if not event.is_action_pressed("pause"):
		return
	toggle_pause()
	get_viewport().set_input_as_handled()


func toggle_pause() -> void:
	var pausing: bool = not get_tree().paused
	get_tree().paused = pausing
	_pause_menu.visible = pausing

Set that node's Process Mode to Always, point its pause_menu_path at the menu, and strip _unhandled_input out of the menu script.

Note @onready with the @ — Godot 4 spelling. A bare onready is Godot 3 and will not parse.

Option B: keep the toggle on the menu, mode it Always, guard the children#

Leave _unhandled_input where it is, set the PauseMenu root to Always, and set the Panel beneath it to When Paused. The root now runs in both states so it can always hear Escape; everything visible under it is still inert during play.

Both designs work. The deciding question is how many things need to survive a pause. If the pause key is the only always-on behaviour you have, Option B is fewer nodes and there is nothing wrong with it. If you already have — or expect — a music manager, a scene transition node, an event bus that must keep delivering, then you have a growing set of "runs regardless" nodes, and Option A's separation is what keeps that set from being scattered through your menus. In practice, that set grows. Most projects end up with an autoloaded node on Always doing exactly this.

Whichever you pick, run it and confirm Escape now toggles both directions, repeatedly, without any pattern of it degrading after a few presses.

can_process() is the debugging call#

You will hit "why isn't this node running while paused" again on a node buried four levels deep in a scene you built two weeks ago. Do not reason about the inherit chain. Ask.

func _on_something_suspicious() -> void:
	print("can_process: ", can_process(), "  paused: ", get_tree().is_paused())

Node.can_process() resolves the whole chain — every Inherit hop, the ancestor that finally has an opinion, and the tree's current pause state — into one boolean. It is the honest answer to the question the inspector only lets you guess at, because the inspector shows you one node's declared mode, not the mode it resolves to.

One spelling trap in that snippet: the getter is is_paused(), not get_paused(). You assign with get_tree().paused = true but read it back with get_tree().paused or get_tree().is_paused(). If you type get_paused() you get a method-not-found error, which at least fails loudly.

Put can_process() in your reflexes now. It turns a twenty-minute inspector archaeology session into one print statement.

Two leaks that make a working pause look broken#

You will eventually watch something move, tick or fire during a pause you are certain you set correctly. Neither of these is a bug in your pause. Both are documented behaviour that surprises everyone once.

Signals do not care about process_mode#

Straight from the pausing tutorial:

signals still work and cause their connected function to run, even if that function's script is attached to a node that is not currently being processed.

A node being unprocessed means its _process, _physics_process and input callbacks stop being called. It does not mean the node is deaf. Any signal connected to a method on that node will still fire that method during the pause.

Which signals can still fire while paused? Not the physics ones — collision detection is off tree-wide, so body_entered and company go quiet on their own. But a Timer node on Always, a pressed signal, an emit from your own code, or anything coming from a node you deliberately kept running will all reach their targets.

The failure this produces is subtle. A health system connected to a signal keeps applying damage. A score counter keeps counting. From the outside it reads as "the pause didn't take", and you go looking at process_mode when the culprit is a connection you set up in _ready and forgot about.

If a signal handler must not run during the pause, guard it explicitly:

func _on_damage_dealt(amount: int) -> void:
	if get_tree().paused:
		return
	_health -= amount

Explicit, boring, and it survives someone else reading the code.

SceneTreeTimer keeps ticking by default#

SceneTree.create_timer() has a parameter you have probably never passed:

SceneTreeTimer create_timer(time_sec: float, process_always: bool = true, ...)

process_always defaults to true. A timer created with get_tree().create_timer(3.0) keeps counting through your pause and fires its timeout on schedule as though nothing happened.

# Keeps ticking through the pause.
await get_tree().create_timer(3.0).timeout

# Freezes with the game.
await get_tree().create_timer(3.0, false).timeout

This default cuts both ways, and knowing which way you want is the point. A three-second respawn delay that quietly expires while the player is reading the pause menu is a bug. A menu fade animation that has to run while paused is served perfectly by the default — but it is served by accident, not by your design, and accidents stop working when the defaults change under you. Pass the argument you mean.

While you are here: await is the Godot 4 spelling. If a tutorial shows you yield(get_tree().create_timer(3.0), "timeout"), that is Godot 3 and it will not parse.

The opposite confusion: Always and still frozen#

The mirror-image bug. You set a node to PROCESS_MODE_ALWAYS. You confirmed with can_process() that it is running. Its _physics_process is firing, you can print from inside it. And the node does not move a pixel.

From the pausing tutorial:

even if a node is processing while the game is paused physics will NOT work for it by default... because the physics servers are turned off

process_mode governs whether your callbacks run. It has nothing to say about whether the physics servers are running. Those are off tree-wide the instant paused becomes true, and no per-node property brings them back.

So move_and_slide() inside a _physics_process on an Always node does nothing useful. The body's velocity is being computed, the call is being made, and the server that would resolve it into movement is asleep.

If something genuinely must move during a pause — a menu element sliding in, a cursor, a background parallax that keeps drifting — move it yourself, in _process, by writing to position directly:

extends Node2D


@export var drift_speed: float = 40.0


func _process(delta: float) -> void:
	position.x += drift_speed * delta

That is a Node2D, not a physics body, and it is deliberate. You are stepping around the physics server rather than fighting it. The moment you want pause-time movement, stop reaching for CharacterBody2D.

(While you are here: CharacterBody2D is the Godot 4 node. KinematicBody2D was Godot 3 and does not exist any more. Old pause tutorials are full of it.)

What the engine handles for you#

Do not write code to solve problems Godot already solved. The pausing tutorial lists the freebies:

Animation nodes will pause their current animation, audio nodes will pause their current audio stream, and particles will pause. These resume automatically when the game is no longer paused.

AnimationPlayer, audio stream players and particle systems all stop and restart on their own, at the frame they left off, with no work from you. Your run cycle freezes mid-stride. Music holds. Sparks hang in the air. Un-pause and everything picks up.

This is worth stating explicitly because the instinct after fighting process_mode for an hour is to distrust the whole system and start manually calling stop() on everything. Do not. You will end up with an animation that restarts from frame zero on every un-pause, and you will have written code to make your game worse.

Wiring the three buttons properly#

Your buttons should now work. Two of them deserve a closer look, because they touch scene flow from Part 01.

Restart uses reload_current_scene(), which replaces the current scene with a fresh instance of its original PackedScene. No path hardcoded, so the same pause menu works dropped into any level. It returns an ErrorERR_UNCONFIGURED if there is no current scene, ERR_CANT_OPEN if the scene cannot load. You can ignore the return in a menu button and usually should, but know it exists.

Quit to Menu calls change_scene_to_file(). Part 01 covered the order-of-operations trap in detail, so this is only the reminder: the outgoing scene is removed from the tree immediately, and the new scene does not arrive until the end of the frame. Any line you write after that call, expecting the new scene to exist, is running in a gap where get_tree().current_scene is null. This matters here because a pause menu is exactly where you are tempted to write "change scene, then reset the pause state, then update the save file."

The safe ordering is: un-pause first, change scene last. That is what the code above does, and the reason is not stylistic — after the call, the node you are standing on has already left the tree, and get_tree() on it returns null. get_tree().paused = false on the far side of that call is a crash waiting for the right timing.

Checkpoint — definition of done#

  • Pressing Esc during play freezes the game and shows the menu; pressing it again resumes, and you can toggle it at least ten times in a row without it sticking.
  • With the menu open, the player character does not move, animations are frozen mid-frame, and any looping audio has held rather than stopped.
  • Resume closes the menu and returns control to the player, with no leftover input swallowed on the frame after.
  • Restart reloads the level with the player back at the start and the tree un-paused — pressing Esc immediately after a restart still opens the menu.
  • Quit to Menu reaches the main menu and the main menu is responsive, not silently frozen.
  • The pause menu is invisible and inert during play: temporarily add a print() to the menu's _process and confirm nothing prints while you are playing.
  • Calling can_process() from inside the menu prints false during play and true while paused.
  • A get_tree().create_timer(3.0, false).timeout await started before a pause has not fired when you un-pause three seconds later — it resumes counting instead.
  • You can explain: why setting process_mode to Always on a CharacterBody2D does not let it move during a pause, when the same setting does let a Node2D move.

Stretch (no instructions)#

  • Blur or dim the game behind the menu. A ColorRect with a low-alpha black modulate under the Panel is the cheap version; a BackBufferCopy with a shader is the expensive one. Both are legitimate.
  • Make the menu fade in over 0.15 seconds instead of appearing instantly. You will need a tween that runs while paused — think about which node owns it and what its process mode has to be.
  • Add a "pause on focus lost" behaviour so alt-tabbing away pauses the game. Look up the notification callbacks on Node and the window focus notifications in the class reference; do not guess at the constant name.
  • Make the pause key also work on a gamepad Start button. This is a one-line Input Map change and it will expose a problem Part 04 exists to solve.

If you get stuck#

  • Menu appears, buttons do nothing, no errors → the menu root's Process Mode is still Inherit or Pausable. Set the root to When Paused and leave the children on Inherit. Confirm with can_process() from a button script rather than trusting the inspector.
  • Esc pauses but never un-pauses → the node running _unhandled_input cannot run while paused. It needs Always. Check the node that actually owns the input callback, not the node you think owns it.
  • Buttons work with the mouse, arrow keys do nothing → nothing has focus. That is Part 04's subject, not a pause bug. Nothing is broken.
  • The game keeps updating something during the pause → a signal connection, not a process callback. Signals fire on unprocessed nodes. Find the connect or the Node-dock connection and guard the handler with if get_tree().paused: return.
  • A delayed action fires while the menu is opencreate_timer with process_always left at its default true. Pass false as the second argument.
  • A node on Always still will not move → it is a physics body, and physics servers are off tree-wide. Move a Node2D by writing position in _process instead.
  • get_paused() is not declared → the getter is is_paused(). The property assignment get_tree().paused = true is unaffected.
  • Invalid call. Nonexistent function 'change_scene' → Godot 3 spelling. It is change_scene_to_file(path) in Godot 4.
  • Parse error on PROCESS_MODE_WHENPAUSED → underscore between the words: PROCESS_MODE_WHEN_PAUSED. The inspector's two-word label is the cause of this one.
  • Everything works in the editor, but after quitting to the main menu the menu itself is frozen → you changed scenes while paused was still true. The flag belongs to the tree and survives the scene change. Un-pause before you change.
  • Escape both closes the menu and triggers a button under the cursor → you are missing get_viewport().set_input_as_handled(), or you are using _input where _unhandled_input would let the UI consume the event first.

Next: Part 03 — Settings that survive a restart. Read it once your pause menu is solid — settings live behind a pause menu as often as behind a title screen, and the volume slider you are about to build needs somewhere to sit.