Voltar para o Blog
Quest Log

Menu Principal na Unity: Jogar, Opções e Sair

Tela de título de um jogo com três botões empilhados sobre um cenário de fantasia ao entardecer

Unity main menu funcional: botões Jogar, Opções e Sair, troca de painéis, Application.Quit que fecha na build e no editor e navegação por teclado e controle.

Ferramenta:UnityC#

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) e PainelOpcoes. 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

  1. Crie a cena: File > New Scene, salve como MenuPrincipal em Assets/Scenes.
  2. 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.
  3. 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.
  4. Dentro dele, três botões (GameObject > UI > Button - TextMeshPro): BotaoJogar, BotaoOpcoes, BotaoSair. Cada um com 400 x 90 e o texto correspondente.
  5. Crie PainelOpcoes, também esticado, com o conteúdo de opções e um BotaoVoltar. Desligue esse painel (checkbox ao lado do nome no Inspector).
  6. 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 = 1f no 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.
  • MostrarPainel com 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.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 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 Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

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 = false para 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.

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.