Voltar para o Blog
Quest Log

Como Criar um Menu Principal no Godot 4: Passo a Passo Completo

Tela de menu principal de um jogo sendo montada no editor do Godot 4

Aprenda a criar um menu principal no Godot 4 passo a passo: cena com Control nodes, botões, sinais, fade de transição para a fase e navegação por controle.

Como Criar um Menu Principal no Godot 4: Passo a Passo Completo

O menu principal é a primeira coisa que o jogador vê e, curiosamente, uma das últimas que a maioria dos devs faz. O problema é que montar um menu principal no Godot 4 do jeito errado vira dívida: botão que desalinha quando muda a resolução, transição que trava, navegação que só funciona com mouse. Aí você refaz tudo na véspera do lançamento.

A boa notícia: fazer certo não dá mais trabalho do que fazer errado. O Godot tem um sistema de UI baseado em Control nodes e containers que resolve alinhamento e responsividade sozinho, contanto que você monte a árvore de cena do jeito que ele espera.

Nesse tutorial eu monto um menu completo do zero: cena, botões de Jogar, Opções e Sair, sinais, transição com fade para a fase e navegação por teclado e controle. Todo código é GDScript do Godot 4.x e roda como está.

A estrutura da cena: Control nodes e containers

Antes de arrastar botão pra tela, vale entender a regra que organiza UI inteira no Godot: quem posiciona elemento de interface é container, não você.

Se você posicionar cada botão na mão, com coordenada fixa, o menu quebra na primeira resolução diferente. Em vez disso, você usa containers (VBoxContainer, HBoxContainer, CenterContainer, MarginContainer) e deixa eles calcularem posição e tamanho dos filhos. Mudou a resolução? O container realinha tudo.

A árvore do nosso menu fica assim:

MainMenu (Control)
├── Background (TextureRect)
├── CenterContainer
│   └── VBoxContainer
│       ├── TitleLabel (Label)
│       ├── StartButton (Button)
│       ├── OptionsButton (Button)
│       └── QuitButton (Button)
└── FadeRect (ColorRect)

Cada peça tem um papel:

  • MainMenu (Control): a raiz da cena. Crie uma cena nova, escolha "User Interface" como tipo de raiz e salve como main_menu.tscn.
  • Background (TextureRect): a arte de fundo.
  • CenterContainer: centraliza o que estiver dentro dele, em qualquer resolução.
  • VBoxContainer: empilha os filhos na vertical, um embaixo do outro, com espaçamento configurável.
  • FadeRect (ColorRect): um retângulo preto que vamos usar na transição. Fica por último na árvore de propósito, porque no Godot o que vem depois desenha por cima.

Montando o menu principal no Godot 4

Com a teoria fora do caminho, mãos no editor.

Passo 1: raiz e fundo

Crie a cena com raiz Control e renomeie pra MainMenu. No Inspector, com a raiz selecionada, clique no ícone de âncoras (Anchor Preset) na barra acima da viewport e escolha Full Rect. Isso faz o Control ocupar a tela inteira.

Adicione um TextureRect como filho, arraste sua arte de fundo pra propriedade Texture e também aplique Full Rect. Em Expand Mode, use Ignore Size, e em Stretch Mode, Keep Aspect Covered: a imagem cobre a tela sem distorcer, cortando as bordas se a proporção não bater. É o mesmo comportamento do background-size: cover do CSS, se você vem do web.

Sem arte ainda? Usa um ColorRect com uma cor sólida e segue o tutorial. Menu funcional primeiro, bonito depois.

Passo 2: containers e botões

Adicione um CenterContainer como filho da raiz, com Full Rect também. Dentro dele, um VBoxContainer. Dentro do VBox:

  1. Um Label chamado TitleLabel com o nome do jogo
  2. Três Button: StartButton, OptionsButton, QuitButton, com os textos "Jogar", "Opções" e "Sair"

Rode a cena (F6) e repare: tudo centralizado, sem você ter posicionado nada. Redimensiona a janela e o menu se mantém no centro. Esse é o ganho dos containers.

Dois ajustes de acabamento no VBoxContainer:

  • Em Theme Overrides > Constants > Separation, suba pra algo entre 12 e 20 pixels pra dar respiro entre os botões.
  • Os botões vêm com a largura do próprio texto, o que deixa cada um de um tamanho. Pra uniformizar, selecione os três e defina Custom Minimum Size com x em torno de 200. Agora todos têm a mesma largura.

Passo 3: o retângulo de fade

Adicione um ColorRect chamado FadeRect como último filho da raiz, Full Rect, cor preta. Duas configurações importantes:

  • Mouse > Filter: mude pra Ignore. Sem isso, o retângulo invisível bloqueia os cliques nos botões embaixo dele.
  • No script, ele começa transparente. Vamos cuidar disso já já.

Conectando os botões com sinais

Botão no Godot emite o sinal pressed quando clicado. Dá pra conectar pelo editor (aba Node > Signals), mas eu prefiro conectar por código no menu: fica tudo visível num lugar só e sobrevive a renomeação de nodes na cena com menos sustos.

Adicione um script na raiz MainMenu:

extends Control

@onready var start_button = $CenterContainer/VBoxContainer/StartButton
@onready var options_button = $CenterContainer/VBoxContainer/OptionsButton
@onready var quit_button = $CenterContainer/VBoxContainer/QuitButton
@onready var fade_rect = $FadeRect

func _ready():
    # Fade começa transparente; só escurece na transição.
    fade_rect.modulate.a = 0.0

    start_button.pressed.connect(_on_start_pressed)
    options_button.pressed.connect(_on_options_pressed)
    quit_button.pressed.connect(_on_quit_pressed)

func _on_start_pressed():
    get_tree().change_scene_to_file("res://scenes/fase_1.tscn")

func _on_options_pressed():
    print("abrir opções")  # vamos preencher mais adiante

func _on_quit_pressed():
    get_tree().quit()

O change_scene_to_file() descarrega a cena atual e carrega a nova. Ajuste o caminho pra cena real da sua fase. Se o caminho estiver errado, o Godot avisa no console na hora, então esse é o primeiro lugar pra olhar se "o botão não faz nada".

Um detalhe sobre o botão Sair: em jogo web (HTML5), fechar a aplicação não faz sentido e em algumas plataformas nem funciona. O padrão comum é esconder o botão nesse caso:

func _ready():
    if OS.has_feature("web"):
        quit_button.hide()
Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

Transição para a fase com fade

Trocar de cena seca, com corte instantâneo, funciona. Mas meio segundo de fade pra preto muda completamente a percepção de polimento. É aqui que entra o FadeRect e o sistema de Tween do Godot 4:

func _on_start_pressed():
    start_button.disabled = true  # evita clique duplo durante o fade

    var tween = create_tween()
    tween.tween_property(fade_rect, "modulate:a", 1.0, 0.5)
    tween.tween_callback(_trocar_de_cena)

func _trocar_de_cena():
    get_tree().change_scene_to_file("res://scenes/fase_1.tscn")

O que acontece: o tween anima a transparência do retângulo preto de 0 a 1 em meio segundo e, como passos de um tween rodam em sequência, o tween_callback só dispara quando o fade termina. Aí sim a cena troca, já com a tela preta.

Desabilitar o botão na primeira linha evita o bug clássico do jogador clicar duas vezes e disparar duas transições.

Pra fechar o ciclo, vale fazer o caminho inverso na cena da fase: começar com um ColorRect preto cobrindo a tela e animar de 1 pra 0 no _ready(). Entrada e saída suaves, e o jogo inteiro parece mais caro.

Aqui mora o detalhe que separa menu amador de menu decente: muita gente testa só com mouse e publica um menu que não funciona com controle. Pra quem joga no gamepad, ou pra um futuro port de console, o menu precisa ser navegável por direcional.

O Godot já faz quase tudo: botões dentro de um VBoxContainer ganham navegação por setas automaticamente, porque cada Control tem vizinhos de foco calculados pelo container. O que falta é só dar o foco inicial, porque sem nenhum elemento focado, apertar seta não faz nada:

func _ready():
    start_button.grab_focus()

Rode a cena e teste: setas pra cima e pra baixo trocam o botão focado, Enter (ou o botão de aceitar do controle, que já vem mapeado na ação ui_accept) ativa. Você ganhou suporte a teclado e gamepad com uma linha.

O botão focado ganha um contorno visual do tema padrão. Quando você estilizar o menu com um Theme próprio, customize o estilo focus dos botões pra esse contorno combinar com a arte. Só não remova o feedback visual: jogador de controle precisa ver onde o cursor está.

Painel de opções com volume

Opções completas (resolução, vsync, remapeamento de teclas) rendem um artigo próprio. Mas dá pra entregar o essencial, controle de volume, sem sair dessa cena. Em vez de criar outra cena, usamos um painel escondido.

Adicione na raiz um PanelContainer chamado OptionsPanel (centralize com as âncoras ou coloque dentro de outro CenterContainer), e dentro dele um VBoxContainer com um Label "Volume", um HSlider chamado VolumeSlider e um Button BackButton. No HSlider, configure Min Value 0, Max Value 1 e Step 0.05.

No script:

@onready var options_panel = $OptionsPanel
@onready var volume_slider = $OptionsPanel/VBoxContainer/VolumeSlider
@onready var back_button = $OptionsPanel/VBoxContainer/BackButton

func _ready():
    options_panel.hide()
    volume_slider.value = 1.0
    volume_slider.value_changed.connect(_on_volume_changed)
    back_button.pressed.connect(_on_back_pressed)

func _on_options_pressed():
    options_panel.show()
    volume_slider.grab_focus()

func _on_back_pressed():
    options_panel.hide()
    options_button.grab_focus()

func _on_volume_changed(value: float):
    var bus = AudioServer.get_bus_index("Master")
    AudioServer.set_bus_volume_db(bus, linear_to_db(value))

A conversão com linear_to_db() é o ponto que mais derruba iniciante aqui. Volume em decibéis não é linear: se você jogar o valor do slider direto em set_bus_volume_db, o slider vai parecer que só funciona no comecinho do curso. O linear_to_db() converte a escala de 0 a 1 do slider pra escala logarítmica que o ouvido espera, e o controle fica natural do início ao fim.

Repare também nos grab_focus() ao abrir e fechar o painel: é isso que mantém a navegação por controle coerente quando a UI muda de contexto.

Definindo o menu como cena inicial

Falta o jogo abrir no menu. Em Project Settings > Application > Run > Main Scene, aponte pra main_menu.tscn. Agora F5 roda o projeto começando pelo menu, e o build exportado também.

E pra voltar da fase pro menu (botão de pause, tela de game over), é o mesmo change_scene_to_file no sentido contrário:

get_tree().change_scene_to_file("res://scenes/main_menu.tscn")

Fechando

Recapitulando o caminho: Control como raiz, containers cuidando do layout, sinais conectados por código, fade com Tween antes do change_scene_to_file, foco inicial pra navegação por controle e linear_to_db no volume. É um menu completo, responsivo e navegável em qualquer entrada, com menos de cem linhas de script.

O que eu faria a seguir, na ordem: um Theme próprio pra tirar a cara de tema padrão do Godot, som de hover e de clique nos botões, e uma animação sutil no fundo (um parallax lento ou partículas resolvem). Nenhum desses muda a estrutura que você montou aqui, só veste ela.

E um conselho de quem já refez menu em cima da hora: monta o seu cedo no projeto, mesmo feio. Cena de menu pronta desde o início te dá um lugar natural pra pendurar opções, save e créditos conforme o jogo cresce, em vez de virar gambiarra no final.