Voltar para o Blog
Quest Log

Remapear Controles na Unity: Tela de Rebinding

Menu de opções de um jogo com lista de ações e teclas, uma delas piscando à espera de uma nova tecla

Unity rebinding com o Input System: tela de remapear controles com PerformInteractiveRebinding, cancelar com Esc, tecla duplicada e salvar em JSON.

Ferramenta:UnityC#

Remapear Controles na Unity: Tela de Rebinding

Unity rebinding é a tela em que o jogador escolhe a própria tecla para cada ação. Com o Input System, o núcleo é uma chamada: action.PerformInteractiveRebinding(índice).Start(). Ela espera o próximo botão apertado e grava esse botão como override do binding, sem mexer no asset original. O resto do trabalho é fazer a tela se comportar bem: cancelar com Esc, não pegar o movimento do mouse, avisar tecla duplicada e lembrar a escolha na próxima vez que o jogo abrir.

Este post monta essa tela completa em C# para Unity 6.3 LTS, mostra onde o sample oficial Rebinding UI ajuda e explica os erros que fazem a tela travar ou gravar a tecla errada.

Unity rebinding: o que o Input System oferece

O sistema de override é o que torna o remapeamento simples. Cada binding tem um path (o original, definido no asset, como <Keyboard>/space) e pode ter um overridePath (o que o jogador escolheu). Quando existe override, ele vale; o asset nunca é alterado. Isso dá três propriedades boas:

  • Restaurar padrão é trivial: remover o override devolve o binding original.
  • Salvar é pequeno: você salva só o que mudou, em JSON.
  • Atualizar o jogo não quebra o save: se você adicionar uma action nova no patch, ela vem com o binding padrão e os overrides antigos continuam valendo.

As peças da API, todas em InputActionRebindingExtensions:

  • PerformInteractiveRebinding(bindingIndex) cria a operação que espera input.
  • ApplyBindingOverride e RemoveBindingOverride aplicam e removem override de um binding.
  • SaveBindingOverridesAsJson e LoadBindingOverridesFromJson salvam e carregam.
  • GetBindingDisplayString(bindingIndex) devolve o texto para mostrar ("Space", "W", "Button South").

O sample Rebinding UI

Antes de escrever a tela do zero, olhe o exemplo oficial. Abra Window > Package Management > Package Manager, selecione Input System, aba Samples, e clique em Import em Rebinding UI. Ele aparece em Assets/Samples/Input System/<versão>/Rebinding UI.

O sample traz um componente que você coloca num botão da UI, aponta para uma action e um binding, e ele cuida do texto, da espera e do overlay "aperte uma tecla". É uma boa base se o seu menu é simples. Eu prefiro entender o que ele faz e escrever uma versão enxuta, porque é ali que entram as regras do seu jogo (tecla duplicada, teclas proibidas, controle separado de teclado). É essa versão que vem a seguir.

PerformInteractiveRebinding passo a passo

Monte na tela de opções uma linha por ação: um texto com o nome ("Pular"), um botão que mostra a tecla atual e um painel "Aperte uma tecla..." escondido. O script vai no botão.

Arquivo RebindButton.cs:

using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;

public class RebindButton : MonoBehaviour
{
    [SerializeField] private InputActionReference actionReference;
    [SerializeField] private int bindingIndex;
    [SerializeField] private TMP_Text bindingLabel;
    [SerializeField] private GameObject waitingOverlay;
    [SerializeField] private TMP_Text warningLabel;

    private InputActionRebindingExtensions.RebindingOperation rebindOperation;

    private void OnEnable()
    {
        RefreshLabel();
        waitingOverlay.SetActive(false);
        warningLabel.text = string.Empty;
    }

    private void OnDisable()
    {
        if (rebindOperation != null)
        {
            // Menu fechado no meio da espera: descarta a operação e religa a action.
            CleanUp();
            actionReference.action.Enable();
            waitingOverlay.SetActive(false);
        }
    }

    public void StartRebind()
    {
        InputAction action = actionReference.action;
        if (bindingIndex < 0 || bindingIndex >= action.bindings.Count)
        {
            Debug.LogError("Índice de binding inválido em " + name, this);
            return;
        }

        action.Disable();
        waitingOverlay.SetActive(true);
        warningLabel.text = string.Empty;

        rebindOperation = action.PerformInteractiveRebinding(bindingIndex)
            .WithControlsExcluding("<Mouse>/position")
            .WithControlsExcluding("<Mouse>/delta")
            .WithControlsExcluding("<Pointer>/position")
            .WithCancelingThrough("<Keyboard>/escape")
            .OnMatchWaitForAnother(0.1f)
            .OnCancel(operation => FinishRebind(action))
            .OnComplete(operation => OnRebindComplete(action))
            .Start();
    }

    private void OnRebindComplete(InputAction action)
    {
        if (RebindRules.HasDuplicate(action, bindingIndex, out string conflictName))
        {
            action.RemoveBindingOverride(bindingIndex);
            warningLabel.text = "Já usada em " + conflictName;
            FinishRebind(action);
            return;
        }

        FinishRebind(action);
        RebindStorage.Save();
    }

    private void FinishRebind(InputAction action)
    {
        CleanUp();
        action.Enable();
        waitingOverlay.SetActive(false);
        RefreshLabel();
    }

    private void CleanUp()
    {
        if (rebindOperation != null)
        {
            rebindOperation.Dispose();
            rebindOperation = null;
        }
    }

    public void RefreshLabel()
    {
        bindingLabel.text = actionReference.action.GetBindingDisplayString(bindingIndex);
    }
}

No botão da UI, em On Click (), ligue RebindButton.StartRebind. No Inspector do script, arraste a action no campo actionReference e defina o bindingIndex.

O índice merece atenção. Abra a action no editor de actions e conte os bindings de cima para baixo começando em zero. Jump costuma ter <Keyboard>/space no índice 0 e <Gamepad>/buttonSouth no 1. Composite conta também: em Move, o composite WASD ocupa um índice (o "2D Vector") e cada parte (Up, Down, Left, Right) ocupa o seu. Para remapear só o "frente", use o índice da parte Up, nunca o do composite.

O que cada linha da cadeia faz:

  • action.Disable(): o Input System recusa remapear uma action habilitada. Esquecer isso é o erro número um.
  • WithControlsExcluding: sem isso, mexer o mouse para ir até o botão já conta como "nova tecla" e o binding vira Mouse/position.
  • OnMatchWaitForAnother(0.1f): espera um décimo de segundo depois do primeiro match para ver se outro controle com sinal mais forte aparece. Evita pegar o gatilho analógico quando o jogador queria o botão.
  • Dispose(): a documentação pede para descartar toda operação, porque ela usa memória não gerenciada.

Cancelar e excluir o mouse

WithCancelingThrough("<Keyboard>/escape") faz o Esc cancelar a operação em vez de virar a nova tecla. O efeito colateral é que Esc nunca pode ser escolhido, o que normalmente é desejável, porque o Esc é a tecla de pausa e de voltar.

Para gamepad, faça o mesmo com um botão de cancelar, por exemplo <Gamepad>/start. Se a sua tela tem colunas separadas para teclado e controle, restrinja cada coluna ao próprio dispositivo:

  • Na coluna de teclado: .WithControlsExcluding("<Gamepad>") e .WithControlsExcluding("<Mouse>") (se clique do mouse não é permitido).
  • Na coluna de controle: .WithControlsExcluding("<Keyboard>") e .WithControlsExcluding("<Mouse>").

Sem essa separação, o jogador que clica com o controle na mão no botão de remapear teclado acaba com o botão A do gamepad no lugar do espaço.

Uma tela de rebinding é uma peça de um menu de opções, que é uma peça de um jogo que precisa salvar, carregar, pausar e navegar com controle. Essas partes conversam o tempo todo, e é a conversa entre elas que costuma quebrar quando o projeto cresce. O curso de Unity trabalha exatamente isso: sistemas de jogo que se encaixam até virar um jogo publicado, em vez de demos isoladas.

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

Detectar tecla duplicada

O jogador põe "pular" no E, que já era "interagir". Agora as duas ações disparam juntas. A checagem compara o caminho efetivo do binding novo com os outros do mesmo action map:

Arquivo RebindRules.cs:

using UnityEngine.InputSystem;

public static class RebindRules
{
    public static bool HasDuplicate(InputAction action, int bindingIndex, out string conflictName)
    {
        conflictName = string.Empty;
        InputBinding newBinding = action.bindings[bindingIndex];

        foreach (InputBinding binding in action.actionMap.bindings)
        {
            if (binding.id == newBinding.id)
            {
                continue;
            }
            if (binding.isComposite)
            {
                continue;
            }
            if (binding.effectivePath == newBinding.effectivePath)
            {
                conflictName = binding.action;
                return true;
            }
        }
        return false;
    }
}

effectivePath é o overridePath quando existe, senão o path, que é exatamente o que está valendo. Pular os bindings de composite evita comparar com o "2D Vector", que não é uma tecla.

No RebindButton, a reação ao conflito é desfazer e avisar. Duas alternativas que alguns jogos usam: trocar (a ação que tinha o E fica com a tecla antiga desta, via ApplyBindingOverride no índice do conflito) ou permitir e marcar em vermelho. Trocar é o mais amigável; desfazer é o mais simples de implementar sem bug.

Salvar e carregar com JSON

Arquivo RebindStorage.cs:

using UnityEngine;
using UnityEngine.InputSystem;

public class RebindStorage : MonoBehaviour
{
    private const string PrefsKey = "input_rebinds";

    private void Awake()
    {
        Load();
    }

    public static void Load()
    {
        string json = PlayerPrefs.GetString(PrefsKey, string.Empty);
        if (!string.IsNullOrEmpty(json))
        {
            InputSystem.actions.LoadBindingOverridesFromJson(json);
        }
    }

    public static void Save()
    {
        string json = InputSystem.actions.SaveBindingOverridesAsJson();
        PlayerPrefs.SetString(PrefsKey, json);
        PlayerPrefs.Save();
    }

    public static void ResetAll()
    {
        InputSystem.actions.RemoveAllBindingOverrides();
        PlayerPrefs.DeleteKey(PrefsKey);
    }
}

Coloque o RebindStorage num objeto da primeira cena do jogo, para carregar os overrides antes de qualquer gameplay. O botão "Restaurar padrão" do menu chama RebindStorage.ResetAll() e depois RefreshLabel() em cada RebindButton da tela. O JSON gerado guarda só os overrides com o id de cada binding, então é curto e você pode salvar no mesmo arquivo de configurações que já guarda volume e resolução, se preferir.

Atenção à fonte das actions: o código usa InputSystem.actions, as project-wide actions. Se o seu personagem lê actions de uma instância da classe C# gerada (new InputSystem_Actions()), os overrides aplicados aqui não chegam nele, porque é outra cópia. Use a mesma fonte em todo o jogo.

Achar o índice sem contar na mão

Índice fixo no Inspector quebra no dia em que alguém reordena os bindings no asset. Se o seu asset tem control schemes (o padrão da Unity 6 tem Keyboard&Mouse, Gamepad, Touch e outros), deixe o código achar o binding pelo grupo:

int keyboardIndex = action.GetBindingIndex(InputBinding.MaskByGroup("Keyboard&Mouse"));
int gamepadIndex = action.GetBindingIndex(InputBinding.MaskByGroup("Gamepad"));

GetBindingIndex devolve o primeiro binding que casa com a máscara, ou -1 se nenhum casar. Para actions simples como Jump e Attack, isso basta: o RebindButton pode ter um campo de texto com o nome do grupo em vez do número, e calcular o índice no OnEnable. Para partes de composite, a busca precisa olhar o nome da parte (binding.name == "up") dentro do grupo, então percorra action.bindings com um for e teste isPartOfComposite, name e groups.

O mesmo grupo serve para mostrar o texto certo em cada coluna. GetBindingDisplayString aceita uma máscara também, e devolve a tecla do teclado numa coluna e o botão do controle na outra, sem índice nenhum. Se você quiser ícones no lugar de texto, existe uma sobrecarga que devolve, além do texto, o layout do dispositivo e o caminho do controle (buttonSouth, por exemplo); com esses dois valores você escolhe o sprite certo de um atlas de ícones de Xbox ou PlayStation.

Erros comuns

InvalidOperationException ao iniciar. Action habilitada. Chame action.Disable() antes de PerformInteractiveRebinding.

O binding vira "Position Mouse". Faltou excluir <Mouse>/position e <Mouse>/delta.

A tela trava em "aperte uma tecla". O OnCancel não esconde o overlay, ou o objeto foi desativado no meio da operação sem Dispose. O OnDisable do script cobre esse caso.

Remapeou mas o jogo não mudou. Fontes de actions diferentes (project-wide numa ponta, classe gerada na outra), ou o binding remapeado é de outro control scheme.

Texto mostra "W/A/S/D" em vez de uma tecla. Você passou o índice do composite. Use o índice da parte.

Próximo passo

Monte a tela com três ações (pular, atacar, interagir) para teclado e controle, teste conflito e restauração, e feche e abra o jogo para conferir o carregamento. Para o lado visual da tela, o post de UI e UX em jogos ajuda a organizar menus legíveis. Na Godot o mesmo problema se resolve pelo InputMap, como mostra o post do Input Map da Godot. E para a parte de controle físico, veja gamepad na Unity.

Perguntas frequentes

Como deixar o jogador trocar as teclas na Unity?

Com o Input System, chame PerformInteractiveRebinding na action, com a action desabilitada, e Start na operação. Ela espera o próximo botão apertado e grava o caminho dele como override do binding. Depois salve os overrides com SaveBindingOverridesAsJson.

Existe exemplo pronto de rebinding na Unity?

Sim. No Package Manager, selecione o pacote Input System, abra a aba Samples e importe o Rebinding UI. Ele traz um componente de botão de rebind e uma cena de exemplo que servem de base para a sua tela de opções.

Como salvar as teclas remapeadas na Unity?

Chame SaveBindingOverridesAsJson no asset de actions, guarde a string em PlayerPrefs ou num arquivo, e na abertura do jogo chame LoadBindingOverridesFromJson com ela. Só os overrides são salvos, então o arquivo é pequeno e sobrevive a atualizações do asset.

Por que dá erro ao chamar PerformInteractiveRebinding?

O motivo mais comum é a action estar habilitada. O Input System não deixa remapear uma action ligada; desabilite antes de começar e habilite de novo quando a operação terminar ou for cancelada.