Navegar no Menu com Controle na Unity (EventSystem)
Para um menu feito com uGUI responder ao controle (a busca "unity menu navigation with controller"), você precisa de três coisas: um EventSystem com o InputSystemUIInputModule, um botão selecionado desde o início e uma navegação entre botões que faça sentido. Com isso, o direcional e o analógico movem a seleção e o botão sul confirma.
Parece pouco, e é aí que mora o problema: o menu funciona na primeira tela e morre na segunda, o mouse rouba a seleção, o jogador volta do painel de opções e nada está selecionado. Este guia monta um menu que funciona no controle do começo ao fim, com código C# para Unity 6.3 LTS e o Input System.
Unity menu navigation with controller: o que precisa
O uGUI (Canvas, Button, Slider, Toggle) tem navegação embutida. Cada Selectable sabe quem são os vizinhos acima, abaixo e dos lados. O EventSystem guarda qual objeto está selecionado agora e, quando chega um evento de mover (direcional, analógico, setas do teclado), passa a seleção para o vizinho naquela direção. Quando chega Submit, ele "clica" no selecionado.
Daí vem a regra que resolve metade dos problemas: sem objeto selecionado, não existe navegação. O mouse não precisa de seleção (ele aponta), o controle precisa.
EventSystem e InputSystemUIInputModule
Ao criar um Canvas (GameObject > UI > Canvas), a Unity cria também um objeto EventSystem. Com o Input System ativo, ele deve ter o componente Input System UI Input Module. Se aparecer o Standalone Input Module antigo, o Inspector mostra um botão para substituir pelo módulo do Input System; clique nele.
O módulo traz um conjunto de actions de UI: Point, Left Click, Middle Click, Right Click, Scroll Wheel, Move, Submit, Cancel e duas de dispositivo rastreado (para VR). As que importam para controle são Move (direcional e analógico; no map UI do asset padrão ela se chama Navigate), Submit (botão sul) e Cancel (botão leste).
No campo Actions Asset do módulo, aponte para o seu InputSystem_Actions, que tem um map UI com essas actions. A documentação do módulo indica as project-wide actions como o jeito de ter actions de UI editáveis desde o início. A vantagem prática: se o jogador remapear Submit na tela de opções, o menu respeita.
Dois campos do módulo que mudam a sensação do menu:
- Move Repeat Delay e Move Repeat Rate: segurar o direcional espera o delay e depois repete a cada intervalo. Ajuste se a lista é longa e a rolagem parece lenta.
- Deselect On Background Click: ligado por padrão. Clicar com o mouse fora de qualquer elemento limpa a seleção. Guarde isso, porque é a causa de um dos bugs da seção de erros.
First Selected e SetSelectedGameObject
Para o menu da primeira cena, selecione o objeto EventSystem e arraste o botão "Jogar" no campo First Selected. Ao carregar a cena, ele já começa selecionado e o controle funciona sem uma linha de código.
Para tudo que abre depois (pausa, opções, inventário, diálogo de confirmação), selecione por código:
Arquivo MenuPanel.cs:
using UnityEngine;
using UnityEngine.EventSystems;
public class MenuPanel : MonoBehaviour
{
[SerializeField] private GameObject firstSelected;
private void OnEnable()
{
SelectFirst();
}
public void SelectFirst()
{
if (EventSystem.current == null || firstSelected == null)
{
return;
}
EventSystem.current.SetSelectedGameObject(null);
EventSystem.current.SetSelectedGameObject(firstSelected);
}
}
O SetSelectedGameObject(null) antes do objeto real não é enfeite. Se o botão já era o selecionado de uma abertura anterior, chamar de novo com o mesmo objeto não dispara os eventos de seleção, e o destaque visual pode não aparecer. Limpar primeiro garante a transição.
Coloque um MenuPanel em cada painel e preencha o firstSelected com o botão mais provável: "Continuar" na pausa, "Voltar" num diálogo destrutivo (para o jogador não apagar o save apertando A sem querer).
Navigation automática vs explícita
Selecione um botão e veja o campo Navigation no Inspector. As opções:
- Automatic: a Unity escolhe o vizinho pela posição na tela. Funciona bem em lista vertical simples.
- Horizontal / Vertical: automática, mas só em um eixo.
- Explicit: você arrasta os vizinhos em Select On Up, Down, Left e Right.
- None: o elemento não participa da navegação (bom para um texto clicável decorativo).
O botão Visualize desenha setas amarelas na Scene view mostrando para onde cada elemento leva. Ligue sempre ao montar menu; erros de navegação aparecem na hora.
Quando usar Explicit: para dar a volta (de "Sair", embaixo, para "Jogar", em cima), para grades irregulares (inventário com slots de tamanhos diferentes) e quando a automática pula um elemento por estar um pouco desalinhado. Sliders merecem atenção: em navegação automática, esquerda e direita mudam o valor do slider em vez de sair dele, o que é o esperado para volume. Mantenha os sliders numa coluna e use cima e baixo para entrar e sair deles.
Menu jogável no controle é uma daquelas partes que separam protótipo de jogo lançável: ninguém elogia quando funciona, todo mundo reclama quando não funciona. Junto com pausa, salvamento, opções e troca de cena, é a camada que transforma mecânicas soltas num produto, e é ela que o curso de Unity monta passo a passo dentro de projetos completos.
Trocar de painel sem perder a seleção
Fluxo típico: menu principal, jogador seleciona "Opções", ajusta algo, aperta Cancel para voltar. O certo é voltar com "Opções" selecionado, não com "Jogar". Para isso, guarde quem estava selecionado ao sair e restaure ao voltar.
Arquivo MenuNavigator.cs:
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
public class MenuNavigator : MonoBehaviour
{
[SerializeField] private MenuPanel rootPanel;
private readonly Stack<MenuPanel> panelStack = new Stack<MenuPanel>();
private readonly Stack<GameObject> returnSelection = new Stack<GameObject>();
private InputAction cancelAction;
private void Awake()
{
cancelAction = InputSystem.actions.FindAction("UI/Cancel", true);
}
private void Start()
{
rootPanel.gameObject.SetActive(true);
panelStack.Push(rootPanel);
}
private void Update()
{
if (cancelAction.WasPressedThisFrame())
{
Back();
}
}
public void Open(MenuPanel panel)
{
GameObject current = EventSystem.current.currentSelectedGameObject;
returnSelection.Push(current);
panelStack.Peek().gameObject.SetActive(false);
panel.gameObject.SetActive(true);
panelStack.Push(panel);
}
public void Back()
{
if (panelStack.Count <= 1)
{
return;
}
MenuPanel closing = panelStack.Pop();
closing.gameObject.SetActive(false);
MenuPanel previous = panelStack.Peek();
previous.gameObject.SetActive(true);
GameObject restore = returnSelection.Pop();
EventSystem.current.SetSelectedGameObject(null);
if (restore != null && restore.activeInHierarchy)
{
EventSystem.current.SetSelectedGameObject(restore);
}
else
{
previous.SelectFirst();
}
}
}
No botão "Opções", ligue On Click () a MenuNavigator.Open passando o painel de opções. A pilha permite submenus dentro de submenus (Opções > Controles > Remapear) e o Cancel volta um nível por vez. O previous.gameObject.SetActive(true) dispara o OnEnable do MenuPanel, que seleciona o primeiro botão; logo em seguida o navegador sobrescreve com o botão de onde o jogador saiu. Se esse botão sumiu (foi desativado), cai no primeiro.
Destaque visual do botão selecionado
No Button, em Transition: Color Tint, existem cores separadas para Highlighted (mouse em cima) e Selected (selecionado pelo EventSystem). Muita gente só ajusta a Highlighted, testa com mouse e lança. No controle, o Selected é o único sinal de onde o jogador está; dê a ele contraste forte, não um cinza 10% mais claro.
Para destaque mais rico (escala, moldura, som), use os eventos de seleção:
Arquivo SelectedHighlight.cs:
using UnityEngine;
using UnityEngine.EventSystems;
public class SelectedHighlight : MonoBehaviour, ISelectHandler, IDeselectHandler, IPointerEnterHandler
{
[SerializeField] private GameObject frame;
[SerializeField] private float selectedScale = 1.08f;
private Vector3 baseScale;
private void Awake()
{
baseScale = transform.localScale;
frame.SetActive(false);
}
public void OnSelect(BaseEventData eventData)
{
frame.SetActive(true);
transform.localScale = baseScale * selectedScale;
}
public void OnDeselect(BaseEventData eventData)
{
frame.SetActive(false);
transform.localScale = baseScale;
}
public void OnPointerEnter(PointerEventData eventData)
{
if (EventSystem.current != null)
{
EventSystem.current.SetSelectedGameObject(gameObject);
}
}
}
O OnPointerEnter resolve o "dois botões acesos": com ele, passar o mouse sobre um botão também o seleciona, então mouse e controle compartilham um único destaque. Alterne entre os dois no meio do menu e o jogador nunca fica em dúvida de onde está.
Nunca deixar a seleção vazia
Mesmo com tudo acima, a seleção pode ficar nula: um botão é destruído, um painel some por animação, um clique cai no fundo. Um guarda pequeno no EventSystem resolve todos esses casos de uma vez.
Arquivo SelectionKeeper.cs:
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
public class SelectionKeeper : MonoBehaviour
{
private GameObject lastSelected;
private InputAction navigateAction;
private void Awake()
{
navigateAction = InputSystem.actions.FindAction("UI/Navigate", true);
}
private void Update()
{
EventSystem eventSystem = EventSystem.current;
if (eventSystem == null)
{
return;
}
GameObject current = eventSystem.currentSelectedGameObject;
if (current != null && current.activeInHierarchy)
{
lastSelected = current;
return;
}
bool navigating = navigateAction.ReadValue<Vector2>().sqrMagnitude > 0.25f;
if (navigating && lastSelected != null && lastSelected.activeInHierarchy)
{
eventSystem.SetSelectedGameObject(lastSelected);
}
}
}
Coloque no mesmo objeto do EventSystem. Ele lembra o último selecionado válido e, quando a seleção some e o jogador mexe o direcional, devolve a seleção para lá. O jogador de mouse não percebe nada; o de controle nunca fica com o menu morto. No asset padrão da Unity 6, a action de mover do map UI se chama Navigate; se o seu asset usa outro nome, ajuste a string.
Erros comuns
O controle para de funcionar depois de clicar com o mouse. É o Deselect On Background Click: o clique fora dos botões limpou a seleção. Desligue a opção no módulo ou adicione um script que, ao detectar input de navegação com seleção nula, reseleciona o último botão.
Menu de pausa não navega. O jogo desligou o action map UI junto com o Player ao pausar, ou nenhum botão foi selecionado ao abrir. Desligue só o Player e use o MenuPanel.
Menu pausado não anda. Se o seu código de pausa zera Time.timeScale e algum script de animação do menu usa Time.deltaTime, o destaque ou a transição congela, e parece que a navegação travou. Use Time.unscaledDeltaTime nas animações de UI e, no Animator dos botões, Update Mode em Unscaled Time.
A seleção vai parar num botão escondido. Navegação automática enxerga botões ativos de outros painéis sobrepostos. Desative os painéis que não estão abertos, em vez de só deixá-los transparentes.
UI Toolkit não respeita a Navigation do Inspector. Ela é do uGUI. UI Toolkit tem foco e navegação próprios (elementos focáveis e eventos de navegação), configurados de outra forma.
Próximo passo
Monte o menu principal com Jogar, Opções e Sair, um painel de Opções com dois sliders e um toggle, e jogue só com o controle, sem encostar no mouse. Para a parte visual de menus e HUD, o post de UI e UX em jogos traz os princípios de legibilidade. A montagem do menu inicial com troca de cena está em menu principal na Unity, e quem usa Godot encontra o mesmo tema em suporte a controle e joystick na Godot.
Perguntas frequentes
Por que o controle não navega no meu menu da Unity?
Quase sempre porque nenhum botão está selecionado. A navegação do uGUI parte do objeto selecionado no EventSystem; se ele é nulo, o direcional não tem de onde sair. Preencha First Selected no EventSystem ou chame SetSelectedGameObject ao abrir o menu.
O que é o First Selected do EventSystem?
É o campo do componente EventSystem que define qual objeto começa selecionado quando a cena carrega. Funciona para o primeiro menu; para painéis que abrem depois, como opções ou pausa, você precisa selecionar por código com EventSystem.current.SetSelectedGameObject.
Qual a diferença entre navegação automática e explícita na Unity?
Na automática, a Unity escolhe o vizinho pela posição dos elementos na tela. Na explícita, você arrasta no Inspector qual botão fica acima, abaixo, à esquerda e à direita. A automática resolve listas simples; a explícita é necessária para dar a volta no fim da lista e para layouts em grade irregular.
UI Toolkit usa EventSystem para navegação?
UI Toolkit tem sistema próprio de foco e navegação, baseado em elementos focáveis e eventos de navegação, e não usa o Navigation dos botões do uGUI. Este guia trata do uGUI (Canvas, Button, EventSystem), que é o caminho mais usado em menus de jogo.



