Menu Principal na Unity: Jogar, Opções e Sair
Um unity main menu funcional é uma cena própria com um Canvas, três botões e um script que faz três coisas: carrega a cena do jogo com SceneManager.LoadScene, troca para o painel de opções e fecha o jogo com Application.Quit. O que separa um menu de protótipo de um menu de jogo lançado são os detalhes: o Sair que precisa de tratamento no editor, a cena que precisa estar na lista de build e a navegação por controle, que quebra se ninguém estiver selecionado.
Aqui você monta o menu do zero no Unity 6.3 LTS, com um script que cuida dos painéis, um Sair que funciona no editor e na build e navegação por teclado e gamepad.
Estrutura de um menu principal na Unity
Antes de abrir a Unity, decida a estrutura. A que funciona para a maioria dos jogos pequenos e médios:
- Uma cena só para o menu (
MenuPrincipal), no índice 0 da lista de build. É a primeira coisa que o jogo abre. - Um Canvas com um painel por tela:
PainelPrincipal(Jogar, Opções, Sair) ePainelOpcoes. Só um painel fica ligado por vez. - Um script (
MenuPrincipal.cs) que conhece os painéis e os botões. - A cena do jogo separada (
Fase01), carregada pelo Jogar.
Por que cena própria e não um painel por cima da fase? Porque a cena de menu é leve, abre rápido e não deixa inimigos, física e áudio da fase rodando escondidos atrás da UI. E porque o botão de "voltar ao menu" do jogo vira só um LoadScene("MenuPrincipal").
Montando o Canvas e os botões
- Crie a cena: File > New Scene, salve como
MenuPrincipalemAssets/Scenes. - GameObject > UI > Canvas. No Canvas Scaler, mude UI Scale Mode para Scale With Screen Size, Reference Resolution 1920 x 1080 e Match 0.5. Isso faz o menu manter a proporção em monitores diferentes.
- Dentro do Canvas, crie um objeto vazio
PainelPrincipal, esticado na tela inteira. Adicione nele um Vertical Layout Group com Spacing 24, Child Alignment Middle Center e desmarque Child Force Expand de altura. O layout empilha os botões sozinho. - Dentro dele, três botões (GameObject > UI > Button - TextMeshPro):
BotaoJogar,BotaoOpcoes,BotaoSair. Cada um com 400 x 90 e o texto correspondente. - Crie
PainelOpcoes, também esticado, com o conteúdo de opções e umBotaoVoltar. Desligue esse painel (checkbox ao lado do nome no Inspector). - Confira se a cena tem um EventSystem. Se você criou o Canvas pelo menu, ele veio junto.
Um título com a logo do jogo e uma imagem de fundo fecham a tela. Para decidir hierarquia visual, tamanho de botão e o que fica em destaque, o guia de UI e UX em jogos é a referência antes de gastar tempo com arte.
Script do menu: Jogar com SceneManager
Agora o script que comanda tudo. Ele fica num objeto vazio Menu na cena.
Arquivo MenuPrincipal.cs:
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class MenuPrincipal : MonoBehaviour
{
[Header("Cenas")]
[SerializeField] private string cenaDoJogo = "Fase01";
[Header("Painéis")]
[SerializeField] private GameObject painelPrincipal;
[SerializeField] private GameObject painelOpcoes;
[Header("Botões")]
[SerializeField] private Button botaoJogar;
[SerializeField] private Button botaoOpcoes;
[SerializeField] private Button botaoSair;
[SerializeField] private Button botaoVoltar;
private void OnEnable()
{
botaoJogar.onClick.AddListener(Jogar);
botaoOpcoes.onClick.AddListener(AbrirOpcoes);
botaoSair.onClick.AddListener(Sair);
botaoVoltar.onClick.AddListener(VoltarAoPrincipal);
}
private void OnDisable()
{
botaoJogar.onClick.RemoveListener(Jogar);
botaoOpcoes.onClick.RemoveListener(AbrirOpcoes);
botaoSair.onClick.RemoveListener(Sair);
botaoVoltar.onClick.RemoveListener(VoltarAoPrincipal);
}
private void Start()
{
MostrarPainel(painelPrincipal, botaoJogar.gameObject);
#if UNITY_WEBGL || UNITY_IOS
botaoSair.gameObject.SetActive(false);
#endif
}
private void Jogar()
{
Time.timeScale = 1f;
SceneManager.LoadScene(cenaDoJogo);
}
private void AbrirOpcoes()
{
MostrarPainel(painelOpcoes, botaoVoltar.gameObject);
}
private void VoltarAoPrincipal()
{
MostrarPainel(painelPrincipal, botaoOpcoes.gameObject);
}
private void MostrarPainel(GameObject painel, GameObject primeiroSelecionado)
{
painelPrincipal.SetActive(painel == painelPrincipal);
painelOpcoes.SetActive(painel == painelOpcoes);
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(primeiroSelecionado);
}
}
private void Sair()
{
#if UNITY_EDITOR
UnityEditor.EditorApplication.isPlaying = false;
#else
Application.Quit();
#endif
}
}
Arraste painéis e botões para os campos. Algumas decisões que valem explicar:
Time.timeScale = 1fno Jogar. Se o jogador voltou ao menu a partir de um menu de pausa que deixou o tempo em zero, a fase nova abre congelada. Garantir o tempo normal antes de carregar evita um bug que só aparece no segundo jogo.- Nome da cena num campo serializado. Trocar a fase inicial vira mudança no Inspector, sem recompilar. Se preferir não depender de texto, carregue por índice (
SceneManager.LoadScene(1)), mas aí mudar a ordem da lista muda o que o botão abre. MostrarPainelcom o botão que recebe seleção. É o que faz o controle funcionar, explicado mais abaixo.
Para a cena carregar, ela precisa estar na lista de build. Abra File > Build Profiles e, na seção Scene List, arraste MenuPrincipal e Fase01 da janela Project para a lista (ou abra a cena e use Add Open Scenes). Deixe o MenuPrincipal em primeiro: o índice 0 é a cena que a build abre. Em tutoriais mais antigos essa janela aparece com o nome Build Settings.
Painel de opções e voltar
O painel de opções aqui só tem o Voltar, de propósito. O conteúdo de verdade (resolução, tela cheia, volume, qualidade e salvar tudo) é assunto grande o bastante para um post próprio: o menu de opções na Unity monta essa tela completa e encaixa no PainelOpcoes que você criou agora.
Uma dica de estrutura: se o painel de opções vai ser usado também no menu de pausa durante o jogo, transforme PainelOpcoes num prefab com o próprio script. Assim o menu principal e a pausa abrem a mesma tela, e você corrige um bug num lugar só.
Até aqui, cada peça é curta. O desafio aparece quando o menu precisa conversar com o resto do jogo: as opções salvas que a fase tem que ler, a música que não pode reiniciar ao trocar de cena, a pausa que volta para o menu sem deixar o tempo congelado. Ligar tudo isso num jogo que sai do editor e vira build é onde quem estuda sozinho mais trava.
Sair do jogo na build e no editor
O Application.Quit() é ignorado no editor. Você clica em Sair, nada acontece e parece bug. Não é: dentro do editor, quem controla o ciclo é o Play Mode. Por isso o script usa compilação condicional:
#if UNITY_EDITOR: esse trecho só existe quando o código roda no editor. Lá,UnityEditor.EditorApplication.isPlaying = falsepara o Play Mode, igual a clicar no botão de Play.#else: na build,Application.Quit()fecha o jogo.
O nome completo UnityEditor.EditorApplication é proposital. Se você colocar using UnityEditor; no topo do arquivo sem o #if, a build falha, porque o namespace UnityEditor não existe fora do editor.
Cada plataforma trata o Quit de um jeito. Em Windows, Mac e Linux ele fecha o executável. Na Web, para o player mas a página continua aberta. No iOS, a documentação da Unity avisa que fechar por código pode parecer um crash para o usuário. Por isso o Start esconde o botão Sair em WebGL e iOS. No Android, a recomendação oficial é evitar lógica própria de saída e deixar o sistema cuidar.
Navegação por teclado e controle com Selectable
Todo Button herda de Selectable, que tem o campo Navigation. Em Automatic, a Unity descobre o vizinho de cima e de baixo pela posição na tela. Clique em Visualize no Inspector de qualquer botão e a Scene mostra setas amarelas ligando os botões. Se uma seta aponta para o lugar errado, troque para Explicit e defina Select On Up e Select On Down na mão.
O Input System UI Input Module lê o mapa UI do arquivo de ações: setas, WASD, D-pad e analógico movem a seleção (Navigate), Enter e o botão sul do controle confirmam (Submit), Esc e o botão leste voltam (Cancel).
Mas existe uma regra que pega todo mundo: sem nada selecionado, o controle não faz nada. O mouse clica em qualquer lugar, o teclado e o gamepad dependem de um botão em foco. Por isso o MostrarPainel chama EventSystem.current.SetSelectedGameObject ao abrir cada painel. Você também pode definir o First Selected no componente EventSystem, mas ele só vale para o início da cena, não para a troca de painel.
Outro detalhe: quando o jogador clica com o mouse fora de um botão, a seleção some. Se ele pegar o controle depois, nada responde. Uma correção simples é guardar o último selecionado e, no Update, se EventSystem.current.currentSelectedGameObject for null e houver input de navegação, reselecionar.
Erros comuns: botão sair que não sai e cena que não carrega
Sair não faz nada. Você está no editor. Use o bloco #if UNITY_EDITOR do script, ou teste o Sair numa build.
Erro no Console dizendo que a cena não pode ser carregada porque não foi adicionada à build. A cena não está na Scene List. Abra File > Build Profiles e adicione. Confira também se o checkbox ao lado dela está marcado: cena desmarcada fica na lista, mas fica fora da build.
Cena com nome certo que não carrega. O nome é sensível a maiúsculas: fase01 e Fase01 são diferentes. Se a cena tem espaço no nome, o texto no script precisa do espaço também.
Fase abre escura. Quando você abre a cena direto pelo editor ela parece normal, mas carregada pelo menu fica sem iluminação ambiente. Abra a fase, vá em Window > Rendering > Lighting e clique em Generate Lighting para gerar os dados de luz da cena.
Botões não clicam. Confira se há EventSystem na cena de menu e se nenhuma imagem de fundo com Raycast Target está por cima dos botões.
Próximo passo: opções e pausa
Com o menu abrindo a fase, o próximo passo é dar conteúdo ao PainelOpcoes e criar o caminho de volta: um menu de pausa na fase com um botão "Voltar ao menu". Se você também desenvolve no Godot, o post de menu principal no Godot monta a mesma tela com Control nodes e é um bom exercício para comparar como as duas engines organizam UI.
Perguntas frequentes
Por que o botão Sair não fecha o jogo na Unity?
Porque Application.Quit é ignorado dentro do editor. Para testar, pare o Play Mode com UnityEditor.EditorApplication.isPlaying = false dentro de um bloco #if UNITY_EDITOR. Na build de Windows, Mac ou Linux o Application.Quit fecha normalmente.
Por que o botão Jogar não carrega a cena?
A cena precisa estar na Scene List do Build Profiles (File > Build Profiles), e o nome passado para SceneManager.LoadScene precisa bater exatamente com o nome do arquivo da cena, com maiúsculas e minúsculas.
Como fazer o menu funcionar com controle na Unity?
Use botões com Navigation em Automatic ou Explicit, o EventSystem com o Input System UI Input Module e defina o primeiro botão selecionado com EventSystem.current.SetSelectedGameObject ao abrir cada painel.
Preciso de uma cena só para o menu principal?
É o mais comum e o mais simples: uma cena MenuPrincipal no índice 0 da Scene List, que carrega a cena do jogo ao clicar em Jogar. Assim o menu não carrega nada da fase na memória.



