Voltar para o Blog
Quest Log

Botão na Unity: OnClick, AddListener e Botão que Não Clica

Botão de interface destacado num editor de jogo com um cursor clicando e linhas ligando o botão a um script

Unity button OnClick na prática: ligue o botão pelo Inspector e por AddListener, passe parâmetros e resolva o botão que não clica com um checklist de causas.

Ferramenta:UnityC#

Botão na Unity: OnClick, AddListener e Botão que Não Clica

O unity button onclick funciona assim: o componente Button dispara o evento onClick quando o jogador aperta e solta o botão em cima dele, e você liga esse evento a um método pelo Inspector ou por código com AddListener. Se o ponteiro sair do botão antes de soltar, o evento não dispara. Até aí é simples. O que trava a maioria das pessoas é o botão que aparece na tela, muda de cor no hover (às vezes nem isso) e não chama nada.

Aqui você liga um botão das duas formas, passa parâmetros, entende quando usar lambda e sai com um checklist que resolve quase todo caso de botão que não clica no Unity 6.3 LTS, incluindo o detalhe do Input System que pega muita gente em projeto novo.

Como funciona o OnClick do Button na Unity

O Button faz parte do uGUI, o sistema de interface baseado em Canvas. Para um clique chegar no seu método, quatro peças precisam estar no lugar:

  1. Um Canvas com o componente Graphic Raycaster (ele vem junto quando você cria o Canvas pelo menu).
  2. Um EventSystem na cena, com um Input Module que leia o input que o seu projeto usa.
  3. Um Graphic com Raycast Target ligado no objeto do botão (normalmente a Image de fundo), para o raio do mouse ter onde bater.
  4. O componente Button com Interactable marcado e algum método ligado no On Click ().

O fluxo é: o Input Module lê a posição do ponteiro, o Graphic Raycaster descobre qual Graphic está embaixo dele (o mais de cima na ordem de desenho), e o EventSystem entrega o clique para o Button daquele objeto. Se qualquer uma dessas peças falhar, o clique some sem erro no Console. Por isso o checklist mais abaixo segue exatamente essa ordem.

Para criar um botão do zero: GameObject > UI > Button - TextMeshPro. Se a cena ainda não tem Canvas e EventSystem, a Unity cria os dois junto. Esse é o jeito mais seguro, porque o EventSystem criado assim já vem com o módulo certo para o input ativo do projeto.

Ligando pelo Inspector

O caminho visual serve bem para menus pequenos e protótipos. Crie um script com um método público e sem retorno:

Arquivo MenuInicial.cs:

using UnityEngine;

public class MenuInicial : MonoBehaviour
{
    public void Jogar()
    {
        Debug.Log("Clicou em Jogar");
    }

    public void TocarSom(int indiceDoSom)
    {
        Debug.Log("Tocar som " + indiceDoSom);
    }
}

Agora monte a ligação:

  1. Coloque o MenuInicial num GameObject da cena (pode ser o próprio Canvas ou um objeto vazio chamado Menu).
  2. Selecione o botão e, no componente Button, clique no + da lista On Click ().
  3. Arraste para o campo de objeto o GameObject que tem o script, não o arquivo .cs da pasta Project. Esse é o erro número um: arrastar o script do Project faz a lista de funções aparecer vazia ou sem o seu método.
  4. No dropdown de funções, escolha MenuInicial > Jogar ().
  5. Deixe o primeiro dropdown em Runtime Only, que é o padrão.

O método só aparece na lista se for public, retornar void e tiver zero ou um parâmetro de um tipo que o Inspector sabe desenhar: int, float, string, bool ou uma referência de Object. O TocarSom(int) aparece com um campo numérico para você digitar o valor. Método com dois parâmetros não aparece.

A vantagem do Inspector é ver a ligação sem abrir código. A desvantagem é que ela fica escondida na cena: renomeou o método, a ligação quebra em silêncio e o Console só avisa quando o botão é clicado, com um "Missing" no lugar da função.

Ligando por código com AddListener

Em projeto que cresce, ligar por código é mais fácil de manter. O método pode ser privado, você enxerga todas as ligações num lugar só e funciona com botão criado em tempo de execução.

Arquivo MenuBotoes.cs:

using UnityEngine;
using UnityEngine.UI;

public class MenuBotoes : MonoBehaviour
{
    [SerializeField] private Button jogarButton;
    [SerializeField] private Button opcoesButton;
    [SerializeField] private GameObject painelOpcoes;

    private void OnEnable()
    {
        jogarButton.onClick.AddListener(Jogar);
        opcoesButton.onClick.AddListener(AbrirOpcoes);
    }

    private void OnDisable()
    {
        jogarButton.onClick.RemoveListener(Jogar);
        opcoesButton.onClick.RemoveListener(AbrirOpcoes);
    }

    private void Jogar()
    {
        Debug.Log("Começar o jogo");
    }

    private void AbrirOpcoes()
    {
        painelOpcoes.SetActive(true);
    }
}

Arraste os botões para os campos no Inspector e pronto. Repare no par OnEnable/OnDisable. Adicionar em OnEnable e remover em OnDisable garante que, se o menu for desligado e ligado de novo, o método não fica registrado duas vezes. Se você adicionar no Start sem remover, não há duplicata nesse caso, mas basta mover o AddListener para outro lugar que roda mais de uma vez para o clique começar a chamar o método em dobro.

O onClick é um UnityEvent comum, então tudo que vale para eventos vale aqui. Se quiser entender a base de lógica por trás de eventos e chamadas de método, o post de lógica de programação para jogos cobre isso sem engine nenhuma.

Passando parâmetros e usando lambda

Por código, você não fica preso a um parâmetro. Use uma lambda que chama o método com o que quiser:

Arquivo SeletorDeFase.cs:

using UnityEngine;
using UnityEngine.UI;

public class SeletorDeFase : MonoBehaviour
{
    [SerializeField] private Button[] botoesDeFase;

    private void Start()
    {
        for (int i = 0; i < botoesDeFase.Length; i++)
        {
            int indiceDaFase = i;
            botoesDeFase[i].onClick.AddListener(() => EscolherFase(indiceDaFase, "normal"));
        }
    }

    private void EscolherFase(int indice, string dificuldade)
    {
        Debug.Log("Fase " + (indice + 1) + " na dificuldade " + dificuldade);
    }
}

A linha int indiceDaFase = i; é o detalhe que importa. A lambda captura a variável, não o valor. Se você usar i direto, todos os botões guardam a mesma variável do for, que no fim do loop vale botoesDeFase.Length, e todo botão chama a fase errada (ou estoura índice). Copiar para uma local dentro do loop cria uma variável nova a cada volta.

Outro detalhe: lambda não dá para remover com RemoveListener, porque cada () => ... é um objeto novo. Se você precisa remover depois, guarde a lambda numa variável do tipo UnityAction ou use RemoveAllListeners(). Este último remove só os listeners adicionados por código; o que foi configurado no Inspector continua lá.

Montar um menu que funciona é a parte fácil. O que dá trabalho de verdade é quando o botão faz parte de um jogo inteiro: o menu chama a cena, a cena tem HUD, o HUD tem botão de pausa, a pausa congela o tempo, e um clique perdido num desses pontos deixa você uma tarde caçando o porquê sem um erro no Console para ajudar.

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

Botão que não clica: checklist de causas

Quando o botão não responde, siga esta ordem. Ela vai do mais comum para o mais raro.

  1. Tem EventSystem na cena? Olhe a Hierarchy. Sem EventSystem, nenhum botão funciona. Ele some muito quando você monta a UI numa cena e copia só o Canvas para outra. Crie com GameObject > UI > Event System.
  2. Algo está por cima do botão? Uma Image de fundo esticada, um painel transparente, um TextMeshPro grande, até um texto de título com a caixa maior que o texto visível. Qualquer Graphic com Raycast Target ligado que esteja na frente rouba o clique. Com o jogo rodando, selecione o EventSystem: o painel de preview no rodapé do Inspector mostra qual objeto está embaixo do ponteiro (pointerEnter). Se não for o seu botão, achou o culpado. Desmarque Raycast Target em textos e imagens decorativas.
  3. Existe um CanvasGroup no caminho? Um CanvasGroup no pai com Interactable ou Blocks Raycasts desmarcado desliga todos os botões filhos. É comum em painel que fez fade out e ficou com esses campos em falso.
  4. O Button está Interactable? Parece óbvio, mas script de "desabilitar botão até carregar" que nunca reabilita é causa frequente.
  5. O Canvas tem Graphic Raycaster? Canvas criado à mão como objeto vazio + componente Canvas não ganha o raycaster sozinho.
  6. Canvas em World Space tem Event Camera? Se o Canvas está em World Space, o campo Event Camera precisa apontar para a câmera que renderiza a cena.
  7. O Input Module bate com o input do projeto? Esse merece seção própria.

EventSystem e Input System UI Input Module

Projetos novos no Unity 6 vêm com o Input System ativo. O EventSystem precisa usar o Input System UI Input Module (InputSystemUIInputModule), que é o substituto do Standalone Input Module para o pacote novo.

O sintoma clássico: você importou uma cena, um prefab de menu de um asset ou copiou um EventSystem de projeto antigo, e ele vem com o Standalone Input Module. Com Active Input Handling configurado só para o Input System, esse módulo tenta ler a classe Input antiga e o Console enche de InvalidOperationException dizendo que você trocou o input ativo. O botão não clica.

A correção:

  1. Selecione o EventSystem.
  2. No componente Standalone Input Module, a Unity mostra um botão para substituir pelo módulo do Input System. Clique nele. Se não aparecer, remova o componente e adicione Input System UI Input Module pelo Add Component.
  3. Confira em Edit > Project Settings > Player > Other Settings > Active Input Handling qual input o projeto usa. Se estiver em Both, os dois módulos funcionam, mas o recomendado para projeto novo é ficar só no Input System.

O Input System UI Input Module usa as ações do mapa UI do arquivo de ações do projeto (Point, Click, Navigate, Submit, Cancel). Se você editou esse mapa e apagou a ação Click, o mouse para de clicar nos botões. Se o projeto não tem nada configurado, o módulo usa as ações padrão.

Erros comuns: listener duplicado e clique atravessando a UI

Clique chamando o método duas vezes. Quase sempre é uma destas: o método está ligado no Inspector e por AddListener; o AddListener roda em OnEnable sem o RemoveListener correspondente e o painel liga e desliga; ou o script está em dois objetos da cena. Procure o nome do script na barra de busca da Hierarchy (t:MenuBotoes) para ver quantas cópias existem.

Clique no botão também atira no jogo. O jogador clica em "Inventário" e o personagem dispara junto, porque o script de tiro lê o mouse sem saber que a UI está embaixo. A correção é perguntar ao EventSystem antes de agir:

Arquivo ArmaDoJogador.cs:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;

public class ArmaDoJogador : MonoBehaviour
{
    private void Update()
    {
        if (Mouse.current == null)
        {
            return;
        }

        if (Mouse.current.leftButton.wasPressedThisFrame)
        {
            bool ponteiroSobreUi = EventSystem.current != null && EventSystem.current.IsPointerOverGameObject();
            if (ponteiroSobreUi)
            {
                return;
            }

            Atirar();
        }
    }

    private void Atirar()
    {
        Debug.Log("Pow");
    }
}

Chame IsPointerOverGameObject() dentro do Update, como acima. Chamar dentro do callback de uma InputAction gera um aviso no Console com o Input System, porque nesse momento a UI ainda não processou o frame e a resposta pode vir desatualizada.

Botão some ou não clica depois de trocar de cena. Se você mantém o Canvas vivo entre cenas e a cena nova traz outro EventSystem, a Unity avisa que há mais de um EventSystem e só um funciona. Deixe um EventSystem só, junto com a UI persistente.

Próximo passo: um menu completo

Com o botão respondendo, o passo natural é juntar vários num menu de verdade, com painéis que abrem e fecham e navegação por controle. O post de menu principal na Unity parte exatamente daqui e liga Jogar, Opções e Sair. E se você quer que esses botões façam sentido para o jogador, com hierarquia visual e feedback de clique, leia o guia de UI e UX em jogos antes de desenhar a próxima tela.

Perguntas frequentes

Por que meu botão da Unity não clica?

As causas mais comuns são falta de EventSystem na cena, uma Image ou texto com Raycast Target ligado por cima do botão, um CanvasGroup com Interactable ou Blocks Raycasts desligado e o EventSystem usando o Standalone Input Module num projeto que só tem o Input System ativo. Confira nessa ordem.

Qual a diferença entre ligar o OnClick pelo Inspector e por AddListener?

O Inspector cria um listener persistente, salvo na cena, bom para coisas simples. O AddListener cria um listener em tempo de execução, que você controla por código, pode remover quando o objeto desliga e funciona com botões criados por script.

Como passar parâmetro no OnClick do botão na Unity?

Pelo Inspector, o método pode ter um único parâmetro do tipo int, float, string, bool ou Object. Por código, use uma lambda: botao.onClick.AddListener(() => Escolher(2)). Dentro de um for, copie o índice para uma variável local antes de usar na lambda.

RemoveAllListeners remove o que eu configurei no Inspector?

Não. RemoveAllListeners só remove os listeners adicionados por código. Os eventos persistentes do Inspector continuam lá e só saem editando o componente ou a cena.