Menu de pausa no Godot 4: como criar do zero

Aprenda a criar um menu de pausa no Godot 4 com get_tree().paused, process_mode e CanvasLayer. Tutorial passo a passo em GDScript tipado.
Criar um menu de pausa no Godot 4 parece trivial ate a hora que voce clica em Continuar e nada acontece: o botao esta la, o jogo esta congelado, mas o menu tambem congelou junto e ignora todo mundo. Esse e o erro classico de quem esta comecando, e a solucao passa por entender tres pecas que trabalham juntas: get_tree().paused, o process_mode de cada no e o CanvasLayer que segura a interface. Neste tutorial voce vai montar um menu de pausa funcional do zero, com botoes Continuar, Opcoes e Sair, tudo em GDScript tipado e usando a API atual do Godot 4.
A ideia central e simples. Voce congela o jogo inteiro com uma linha, mas diz para o Godot que o menu especifico e uma excecao: ele continua rodando enquanto o resto do mundo dorme. Vamos por partes.
Como o Godot 4 pausa o jogo
O Godot tem um sistema de pausa embutido na arvore de cena. Voce ativa assim:
get_tree().paused = true
Quando paused vira true, o Godot para de chamar _process(), _physics_process() e _input() na maioria dos nos. A fisica congela, as animacoes param, os inimigos ficam parados no ar. Para retomar, basta:
get_tree().paused = false
So que existe um detalhe crucial. Se TUDO congela, como o menu de pausa continua funcionando? Como ele recebe o clique no botao Continuar se get_tree().paused desligou o processamento? A resposta esta no process_mode.
process_mode: quem congela e quem continua
Todo no do Godot 4 tem uma propriedade chamada process_mode que decide como aquele no reage a pausa da arvore. Sao quatro valores relevantes (mais o padrao herdado):
Node.PROCESS_MODE_PAUSABLE: congela quando o jogo pausa. Esse e o comportamento padrao de fato para o gameplay. Seu player, seus inimigos e sua fisica devem ficar assim.Node.PROCESS_MODE_ALWAYS: ignora a pausa e sempre processa. E exatamente o que o menu de pausa precisa, porque ele tem que responder a cliques enquanto o jogo esta congelado.Node.PROCESS_MODE_WHEN_PAUSED: so processa quando o jogo esta pausado. Serve para efeitos que so existem durante a pausa, tipo um fundo escurecido animado.Node.PROCESS_MODE_DISABLED: nunca processa, com pausa ou sem.
Existe ainda o Node.PROCESS_MODE_INHERIT, que e o valor inicial de um no novo: ele simplesmente herda o modo do no pai. Como a raiz da arvore comeca em modo pausavel, por herança quase tudo no seu jogo congela quando voce pausa. Isso e otimo, porque significa que voce so precisa marcar as excecoes.
E a regra de ouro deste tutorial: o menu de pausa fica num CanvasLayer com process_mode = Node.PROCESS_MODE_ALWAYS. Assim o jogo inteiro congela, mas o menu segue vivo, ouvindo teclado e mouse.
Por que usar um CanvasLayer
O CanvasLayer e um no que desenha seus filhos numa camada separada, por cima do mundo do jogo. Ele nao se move com a camera, nao sofre zoom e nao fica atras de nenhum sprite da cena. Isso resolve dois problemas de uma vez: a interface do menu aparece sempre na frente e sempre na mesma posicao da tela, independente de onde o player esteja no mapa.
A estrutura de nos que vamos montar fica assim:
MenuPausa (CanvasLayer) -> process_mode = Always
└── Fundo (ColorRect) cobre a tela e escurece o jogo
└── Painel (VBoxContainer)
├── BotaoContinuar (Button)
├── BotaoOpcoes (Button)
└── BotaoSair (Button)
O CanvasLayer de nome MenuPausa e quem recebe o process_mode como Always. Todos os filhos herdam esse comportamento automaticamente, entao os botoes tambem vao responder mesmo com o jogo pausado.
Montando a cena do menu
Crie uma cena nova com um CanvasLayer na raiz e renomeie para MenuPausa. No Inspector, procure a secao Process e mude o campo Mode de Inherit para Always. So esse clique ja garante que o menu inteiro escapa da pausa.
Dentro dele, adicione um ColorRect esticado para cobrir a tela toda (use o layout Full Rect no editor) e deixe uma cor preta com transparencia, por exemplo alpha 0.5, para dar aquele efeito de escurecer o jogo por tras. Depois adicione um VBoxContainer centralizado com tres nos Button: Continuar, Opcoes e Sair.
Por fim, deixe o CanvasLayer invisivel por padrao. A gente vai controlar a visibilidade por codigo. Uma forma limpa de esconder tudo e mexer na propriedade visible do proprio CanvasLayer.
O script do menu de pausa
Agora o codigo. Anexe este script ao no raiz MenuPausa. Ele controla a variavel de estado, alterna a pausa e liga os botoes. Tudo tipado, como manda o bom estilo no Godot 4:
extends CanvasLayer
var esta_pausado: bool = false
func _ready() -> void:
# Garante que o menu comeca escondido e sempre processa.
process_mode = Node.PROCESS_MODE_ALWAYS
visible = false
func _process(_delta: float) -> void:
# Captura o ESC (acao ui_cancel ja vem mapeada no Godot).
if Input.is_action_just_pressed("ui_cancel"):
alternar_pausa()
func alternar_pausa() -> void:
esta_pausado = not esta_pausado
get_tree().paused = esta_pausado
visible = esta_pausado
Repare em tres coisas. Primeiro, no _ready() eu reforco o process_mode por codigo, mesmo tendo setado no editor, so para deixar a intencao explicita para quem ler o script depois. Segundo, o _process deste no funciona MESMO com o jogo pausado, justamente porque o modo dele e Always. E terceiro, a funcao alternar_pausa() inverte o estado, aplica em get_tree().paused e mostra ou esconde o CanvasLayer de uma vez so. Uma tecla para pausar e a mesma tecla para despausar.
O nome do parametro comeca com sublinhado (_delta) porque nao o usamos aqui, e essa e a convencao do Godot para evitar avisos de variavel nao utilizada.
Ligando os botoes Continuar, Opcoes e Sair
Falta conectar os tres botoes. Voce pode ligar os sinais pressed pelo editor ou pegar as referencias por codigo. Vamos pelo codigo, com @onready e tipagem em cada no:
@onready var botao_continuar: Button = $Fundo/Painel/BotaoContinuar
@onready var botao_opcoes: Button = $Fundo/Painel/BotaoOpcoes
@onready var botao_sair: Button = $Fundo/Painel/BotaoSair
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
visible = false
botao_continuar.pressed.connect(_ao_continuar)
botao_opcoes.pressed.connect(_ao_abrir_opcoes)
botao_sair.pressed.connect(_ao_sair)
func _ao_continuar() -> void:
# Simplesmente retoma o jogo.
esta_pausado = false
get_tree().paused = false
visible = false
func _ao_abrir_opcoes() -> void:
# Aqui voce abriria seu painel de opcoes.
print("Abrir menu de opcoes")
func _ao_sair() -> void:
# Sempre despause antes de trocar de cena, senao a proxima nasce congelada.
get_tree().paused = false
get_tree().change_scene_to_file("res://menus/menu_principal.tscn")
O botao Continuar faz o mesmo que dar ESC de novo: despausa e esconde o menu. O botao Opcoes normalmente abre um segundo painel (som, resolucao, controles), que voce pode montar como outra cena filha. E o botao Sair leva de volta ao menu principal, mas repare no detalhe que derruba muita gente: sempre chame get_tree().paused = false antes de trocar de cena. Se voce sair com o jogo ainda pausado, a proxima cena carrega ja congelada e parece que o jogo travou.
Se voce ainda nao tem uma tela inicial para onde voltar, vale a pena montar uma junto com este menu. O passo a passo esta no nosso guia de como criar um menu principal no Godot, que usa a mesma logica de CanvasLayer e botoes que voce acabou de ver aqui.
Criando uma acao de pausa propria
Usar ui_cancel funciona porque essa acao ja vem no projeto mapeada para o ESC. Mas em muitos jogos voce quer que o botao Start do controle tambem pause, ou prefere uma tecla especifica. Nesse caso, crie sua propria acao no Project Settings, aba Input Map. De o nome de pausar, adicione a tecla ESC e, se quiser, o botao Start do gamepad.
Depois e so trocar a checagem no _process:
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("pausar"):
alternar_pausa()
O Input.is_action_just_pressed() dispara uma unica vez no frame em que a tecla foi apertada, e nao continuamente enquanto ela fica pressionada. E exatamente o que voce quer para alternar pausa: um toque pausa, o proximo toque despausa. Se usasse is_action_pressed(), o jogo ficaria piscando entre pausado e ativo enquanto voce segura a tecla.
Cuidados que evitam dor de cabeca
Alguns pontos que valem ficar de olho depois que o basico funciona:
O primeiro e nao esquecer o process_mode do CanvasLayer. Se o menu aparece mas os botoes nao respondem, quase sempre e porque o modo ficou em Inherit e o menu congelou junto com o jogo. Confira no Inspector se esta em Always.
O segundo e o audio. Sons que estao tocando via AudioStreamPlayer tambem congelam se o no estiver em modo pausavel. Se voce quer que a musica de fundo continue durante a pausa, coloque aquele AudioStreamPlayer especifico em Always. E se quer um som de clique nos botoes do menu, o player desse som tambem precisa estar em Always, senao ele nao toca com o jogo parado.
O terceiro e a transicao de saida. Ao voltar para o menu principal, alem de despausar, e elegante fazer um efeito visual em vez de um corte seco. Da para combinar este menu com uma transicao de cena com fade no Godot para a saida ficar suave.
O quarto, para jogos com varias fases, e a persistencia. Do jeito que montamos, o menu de pausa vive dentro da cena atual e some quando voce troca de fase. Se quiser um menu unico e global, transforme o MenuPausa em um autoload (singleton), assim ele persiste entre as trocas de cena e voce acessa o menu de qualquer fase sem recria-lo toda vez.
Juntando tudo
O script final, enxuto e completo, fica assim:
extends CanvasLayer
var esta_pausado: bool = false
@onready var botao_continuar: Button = $Fundo/Painel/BotaoContinuar
@onready var botao_opcoes: Button = $Fundo/Painel/BotaoOpcoes
@onready var botao_sair: Button = $Fundo/Painel/BotaoSair
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
visible = false
botao_continuar.pressed.connect(_ao_continuar)
botao_opcoes.pressed.connect(_ao_abrir_opcoes)
botao_sair.pressed.connect(_ao_sair)
func _process(_delta: float) -> void:
if Input.is_action_just_pressed("ui_cancel"):
alternar_pausa()
func alternar_pausa() -> void:
esta_pausado = not esta_pausado
get_tree().paused = esta_pausado
visible = esta_pausado
func _ao_continuar() -> void:
esta_pausado = false
get_tree().paused = false
visible = false
func _ao_abrir_opcoes() -> void:
print("Abrir menu de opcoes")
func _ao_sair() -> void:
get_tree().paused = false
get_tree().change_scene_to_file("res://menus/menu_principal.tscn")
Com esse arquivo anexado a um CanvasLayer em modo Always, mais o ColorRect e os tres botoes, voce tem um menu de pausa completo. Aperta ESC, o mundo congela, o menu aparece e responde. Aperta de novo ou clica em Continuar, e o jogo volta exatamente de onde parou.
O pulo do gato foi sempre o mesmo: get_tree().paused congela a arvore inteira, e o process_mode = Always do menu abre a excecao para que ele continue ouvindo voce. Entendida essa dupla, dava para fazer HUDs que reagem a pausa, telas de game over e ate um menu de opcoes que ajusta o som em tempo real. Se seu jogo salva progresso, o proximo passo natural e integrar o botao Sair com um sistema de save e load no Godot, para o jogador nunca perder o que conquistou ao pausar e fechar. Bons projetos, e continue construindo com a gente aqui no CursoGame.Dev.
Perguntas frequentes
Como pausar o jogo inteiro no Godot 4?
Use get_tree().paused = true para congelar a arvore de nos. Isso interrompe _process, _physics_process e a maioria dos sinais dos nos que estao no modo PAUSABLE. Para voltar, defina get_tree().paused = false.
Por que meu menu de pausa nao responde aos cliques quando o jogo esta pausado?
Porque o menu tambem congelou junto com o resto. Voce precisa definir o process_mode do no do menu como Node.PROCESS_MODE_ALWAYS, assim ele continua processando entrada e cliques mesmo com get_tree().paused ativo.
Qual a diferenca entre PAUSABLE, ALWAYS, WHEN_PAUSED e DISABLED?
PAUSABLE congela quando o jogo pausa (padrao para o gameplay). ALWAYS ignora a pausa e sempre processa (ideal para o menu). WHEN_PAUSED so processa quando o jogo esta pausado. DISABLED nunca processa.
Preciso de um CanvasLayer para o menu de pausa?
E altamente recomendado. O CanvasLayer desenha a interface por cima de tudo, ignora a camera do jogo e nao se move com o mundo. Coloque o menu de pausa dentro de um CanvasLayer com process_mode = Always.
Como capturar a tecla ESC para pausar no Godot 4?
Use Input.is_action_just_pressed("ui_cancel") dentro de _process ou _input. A acao ui_cancel ja vem mapeada para o ESC por padrao. Voce tambem pode criar uma acao propria chamada pausar no Input Map.
O menu de pausa some sozinho quando eu troco de cena?
Sim, se ele estiver dentro da cena atual. Se voce quer um menu de pausa unico e global para todas as fases, coloque-o em um autoload (singleton) para que ele persista entre as trocas de cena.


