Voltar para o Blog
Quest Log

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

Cena de jogo congelada ao fundo com um menu de pause em primeiro plano mostrando botões e um slider de 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.

Ferramenta:Godot

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 montar um HUD de vida e munição no Godot, 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:

  1. No PauseMenu (o CanvasLayer), marque Process, Mode como Always no Inspector. Sem isso, nada do que vem a seguir funciona.
  2. 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: Button = $Panel/VBoxContainer/ResumeButton
@onready var quit_button: Button = $Panel/VBoxContainer/QuitButton

func _ready() -> void:
    visible = false
    resume_button.pressed.connect(_on_resume_pressed)
    quit_button.pressed.connect(_on_quit_pressed)

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

func toggle_pause() -> void:
    if get_tree().paused:
        despausar()
    else:
        pausar()

func pausar() -> void:
    get_tree().paused = true
    visible = true

func despausar() -> void:
    get_tree().paused = false
    visible = false

func _on_resume_pressed() -> void:
    despausar()

func _on_quit_pressed() -> void:
    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.

Curso de Godot
Quer fazer seu primeiro jogo na Godot?

No curso de Godot da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Godot
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

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: HSlider = $Panel/VBoxContainer/VolumeSlider

var master_bus: int = AudioServer.get_bus_index("Master")

func _ready() -> void:
    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) -> void:
    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.

Fade de entrada que respeita a pausa

Um detalhe que deixa o menu com cara de produção é uma animação curta de entrada. O problema é que tween criado com create_tween() respeita a pausa por padrão, então ele congela junto com o mundo e a animação nunca roda. A correção é dizer ao tween pra ignorar a pausa:

func pausar() -> void:
    get_tree().paused = true
    visible = true
    modulate.a = 0.0
    var tween: Tween = create_tween()
    tween.set_pause_mode(Tween.TWEEN_PAUSE_PROCESS)
    tween.tween_property(self, "modulate:a", 1.0, 0.15)

O modulate.a = 0.0 começa o menu invisível, e o set_pause_mode(Tween.TWEEN_PAUSE_PROCESS) faz a animação rodar mesmo com a árvore pausada. Em 0,15 segundo o menu surge suave em vez de aparecer de uma vez. É o tipo de polimento barato que muda a percepção do jogo inteiro.

Checklist do menu de pause

Antes de considerar o menu pronto, passe por esta lista:

  • process_mode do CanvasLayer em Always (sem isso, nada funciona).
  • Ação pause no Input Map vinculada ao ESC (e ao Start, se tiver controle).
  • Abrir com ESC, fechar com ESC e fechar pelo botão, os três testados.
  • get_tree().paused = false antes de qualquer change_scene_to_file.
  • Mundo do jogo em Inherit ou Pausable, nunca em Always.
  • Menu escondido com visible = false, não só com modulate ou posição.
  • Slider de volume sincronizando com o AudioServer ao abrir o jogo.

Esse mesmo esqueleto reaparece em cutscene, inventário e qualquer tela que congela o mundo, então vale dominá-lo de vez. Para seguir montando a interface e o resto do jogo na engine, as ferramentas e guias de Godot do CursoGame.Dev reúnem o caminho completo.

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.