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.ApplyBindingOverrideeRemoveBindingOverrideaplicam e removem override de um binding.SaveBindingOverridesAsJsoneLoadBindingOverridesFromJsonsalvam 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 viraMouse/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.
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.



