Como Criar um Menu de Pause no Godot 4: process_mode, CanvasLayer e Volume

Aprenda a criar um menu de pause godot 4: pausar com get_tree().paused, process_mode certo, UI em CanvasLayer, ESC pra abrir e fechar e slider de volume.
Como Criar um Menu de Pause no Godot 4: process_mode, CanvasLayer e Volume
Menu de pause parece o tipo de coisa que se resolve em cinco minutos, até você tentar. Aí descobre que o jogo pausou mas o menu pausou junto, que o botão de "continuar" não responde a clique nenhum, ou que o ESC abre o menu mas nunca mais fecha. Todo mundo que faz um menu de pause godot pela primeira vez tropeça em pelo menos um desses três, e os três têm a mesma raiz: o sistema de pausa do Godot congela tudo por padrão, inclusive o código que deveria despausar.
A boa notícia é que o Godot 4 resolve isso com uma única propriedade, o process_mode, e entender ela é 80% do trabalho. Nesse tutorial eu monto um menu de pause completo: pausar a SceneTree com get_tree().paused, desenhar a UI por cima de tudo com CanvasLayer, abrir e fechar com ESC, e de bônus um slider de volume funcionando dentro do pause, porque é o ajuste que todo jogador espera encontrar ali. Todo código é GDScript do Godot 4.x.
Como a pausa funciona no Godot 4
Pausar o jogo no Godot é uma linha:
get_tree().paused = true
Quando paused vira true, a SceneTree para de chamar _process, _physics_process, _input e os demais callbacks de todos os nodes. Física congela, animações param, timers seguram a contagem. E é exatamente aí que mora o problema: o seu menu de pause também é um node da árvore, então ele congela junto. O botão de "continuar" não recebe clique, o ESC não é lido, e o jogo fica preso pausado pra sempre.
A saída é o process_mode, uma propriedade que todo node tem e que define como ele se comporta em relação à pausa. São cinco valores, mas na prática você usa três:
PROCESS_MODE_INHERIT: o padrão. O node copia o comportamento do pai. Como a raiz pausa, tudo pausa.PROCESS_MODE_PAUSABLE: o node para quando a árvore pausa. É o comportamento que você quer pro mundo do jogo.PROCESS_MODE_WHEN_PAUSED: o node só processa quando a árvore está pausada. Curioso, mas raro de usar.PROCESS_MODE_ALWAYS: o node processa sempre, pausado ou não. É esse que o menu de pause precisa.PROCESS_MODE_DISABLED: o node nunca processa.
A regra de ouro: o jogo fica em INHERIT (que herda pausável da raiz) e o menu de pause fica em ALWAYS. Assim, quando você pausa a árvore, o mundo congela e o menu continua vivo, recebendo input e cliques. Você configura isso no Inspector, na seção Node, Process, Mode, ou por código:
process_mode = Node.PROCESS_MODE_ALWAYS
Importante: process_mode se propaga pros filhos via INHERIT. Então basta marcar ALWAYS no node raiz do menu que os botões e sliders dentro dele funcionam também.
Estrutura da cena: CanvasLayer por cima de tudo
O menu de pause precisa aparecer na frente do jogo inteiro, sem depender de z-index nem de onde a câmera está. O node certo pra isso é o CanvasLayer: ele desenha numa camada própria, fixa na tela, acima do mundo 2D ou 3D. É o mesmo node que você usaria pra HUD, e se você já montou um menu principal no Godot, a lógica de Control nodes é idêntica, só muda o contexto.
A estrutura que eu uso:
PauseMenu (CanvasLayer)
└── Panel (Control, ancorado em tela cheia)
└── VBoxContainer (centralizado)
├── TitleLabel (Label, "Pausado")
├── ResumeButton (Button, "Continuar")
├── VolumeLabel (Label, "Volume")
├── VolumeSlider (HSlider)
└── QuitButton (Button, "Sair pro menu")
Dois ajustes no editor antes de qualquer script:
- No
PauseMenu(o CanvasLayer), marque Process, Mode comoAlwaysno Inspector. Sem isso, nada do que vem a seguir funciona. - No
Panel, use o preset de âncora Full Rect pra cobrir a tela inteira. Um Panel escuro semitransparente por trás dos botões deixa claro que o jogo está pausado e ainda melhora a leitura do texto.
Salve essa cena como pause_menu.tscn e instancie ela na sua cena principal (ou num autoload de UI, se o projeto já tiver um). Por ser CanvasLayer, tanto faz onde ela entra na árvore: ela sempre desenha por cima.
Abrir e fechar com ESC
Primeiro, crie a ação no Input Map: Project Settings, Input Map, adicione uma ação chamada pause e vincule a tecla Escape. Se quiser suporte a controle, vincule também o botão Start. Eu já escrevi sobre como organizar o Input Map e os controles no Godot, e a regra de lá vale aqui: nunca cheque tecla direto no código, sempre passe por ação, porque remapear depois vira trivial.
O script do menu inteiro:
extends CanvasLayer
@onready var resume_button = $Panel/VBoxContainer/ResumeButton
@onready var quit_button = $Panel/VBoxContainer/QuitButton
func _ready():
visible = false
resume_button.pressed.connect(_on_resume_pressed)
quit_button.pressed.connect(_on_quit_pressed)
func _unhandled_input(event):
if event.is_action_pressed("pause"):
toggle_pause()
get_viewport().set_input_as_handled()
func toggle_pause():
if get_tree().paused:
despausar()
else:
pausar()
func pausar():
get_tree().paused = true
visible = true
func despausar():
get_tree().paused = false
visible = false
func _on_resume_pressed():
despausar()
func _on_quit_pressed():
get_tree().paused = false
get_tree().change_scene_to_file("res://main_menu.tscn")
Alguns detalhes que valem explicação:
Por que _unhandled_input e não _input? Porque o _unhandled_input só recebe eventos que a UI não consumiu. Se um dia você tiver um campo de texto aberto, o ESC dele não vai vazar pro pause. E o set_input_as_handled() garante que mais ninguém reaja ao mesmo evento depois do menu.
Por que o ESC funciona com o jogo pausado? Porque o CanvasLayer está em PROCESS_MODE_ALWAYS, então o _unhandled_input dele continua sendo chamado mesmo com a árvore pausada. É o mesmo motivo pelo qual os botões respondem a clique. Se o seu menu pausa e não despausa, em 99% dos casos o process_mode ficou no padrão Inherit.
Por que despausar antes do change_scene_to_file? Porque a pausa é da SceneTree, não da cena. Trocar de cena com paused = true te leva pra um menu principal congelado, onde nada responde. Sempre zere a pausa antes de sair.
Slider de volume dentro do pause
Ajustar volume é o caso de uso número um de um menu de pause, e no Godot isso passa pelos audio buses. O bus Master existe em todo projeto por padrão, e mexer no volume dele afeta todo o áudio do jogo. O detalhe que derruba iniciante é a unidade: o mixer trabalha em decibéis, que são uma escala logarítmica, e o slider trabalha numa escala linear de 0 a 1. A conversão entre os dois é feita por linear_to_db e db_to_linear.
Configure o VolumeSlider no Inspector: Min Value em 0.0, Max Value em 1.0, Step em 0.01, Value em 1.0. Depois, adicione ao script do menu:
@onready var volume_slider = $Panel/VBoxContainer/VolumeSlider
var master_bus := AudioServer.get_bus_index("Master")
func _ready():
visible = false
resume_button.pressed.connect(_on_resume_pressed)
quit_button.pressed.connect(_on_quit_pressed)
volume_slider.value_changed.connect(_on_volume_changed)
# Sincroniza o slider com o volume atual ao abrir o jogo.
volume_slider.value = db_to_linear(AudioServer.get_bus_volume_db(master_bus))
func _on_volume_changed(value: float):
AudioServer.set_bus_volume_db(master_bus, linear_to_db(value))
Com isso o slider controla o volume geral em tempo real, inclusive com o jogo pausado, porque os Control nodes do menu estão vivos via process_mode. Se o projeto tiver buses separados de música e efeitos, é só trocar o nome em get_bus_index e duplicar o slider. Eu detalho essa separação em buses e a mixagem completa no artigo sobre audio bus e mixagem no Godot.
Uma decisão de design que você precisa tomar: a música pausa junto com o jogo ou continua tocando? Por padrão, o AudioStreamPlayer pausa com a árvore. Se quiser que a música siga durante o pause (a maioria dos jogos faz isso, ou troca pra uma versão abafada), marque o node da música com PROCESS_MODE_ALWAYS também.
Armadilhas clássicas e como evitar
O timer que conta durante a pausa. O get_tree().create_timer(2.0) nasce com process_always = true, então ele ignora a pausa por padrão. Se um inimigo usa esse timer pra atacar, ele ataca com o jogo pausado. Passe false no segundo argumento (create_timer(2.0, false)) pra ele respeitar a pausa.
Input do player vazando. Se o player lê input em _unhandled_input e por algum motivo está em ALWAYS (acontece quando alguém copia configuração sem entender), ele se move com o jogo pausado. O mundo do jogo inteiro deve ficar em Inherit ou Pausable. O ALWAYS é exceção, não regra.
Pausar dentro de _physics_process de um node pausável. Se a lógica que chama toggle_pause() mora num node que congela com a pausa, ela nunca roda de novo pra despausar. Por isso o toggle vive no próprio menu, que está em ALWAYS.
Menu invisível mas bloqueando cliques. Se você esconde o menu mexendo só em modulate ou posição, os Controls continuam interceptando o mouse. Use visible = false, que desativa o desenho e o input de uma vez. Pra casos mais finos existe o mouse_filter, mas pro pause o visible resolve.
Tween que congela no meio. Tweens criados com create_tween() respeitam a pausa por padrão. Se uma animação do próprio menu de pause precisa rodar (um fade de entrada, por exemplo), configure tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) pra ela ignorar a pausa.
Fechando
Menu de pause no Godot 4 se resume a três peças: get_tree().paused congela o mundo, PROCESS_MODE_ALWAYS no CanvasLayer mantém o menu vivo, e _unhandled_input com uma ação do Input Map cuida do ESC. O slider de volume entra de graça em cima disso, porque o AudioServer não liga pra pausa.
Meu conselho pra fixar: monte o menu numa cena separada e teste os três caminhos, abrir com ESC, fechar com ESC e fechar pelo botão. Depois quebre de propósito, volte o process_mode pra Inherit e veja o menu travar. Entender por que ele trava vale mais do que copiar a configuração certa, porque o sistema de pausa aparece de novo em cutscene, em tela de inventário e em qualquer lugar onde o mundo precisa congelar e a UI não.


