# 3. Runtime Rebinding
Part of [[Input System]]. Previous: [[2. Control Schemes Devices and Consumption Patterns]]. Next: [[4. UI Input and XR Input]].
Docs: [User rebinding at runtime](https://docs.unity3d.com/Packages/
[email protected]/manual/user-rebinding-runtime.html), [Rebind an action at runtime](https://docs.unity3d.com/Packages/
[email protected]/manual/rebind-action-runtime.html), [Save and load rebinds](https://docs.unity3d.com/Packages/
[email protected]/manual/save-load-rebinds.html).
> [!warning] Verification status
> `PerformInteractiveRebinding`, `SaveBindingOverridesAsJson`, and `LoadBindingOverridesFromJson` match 1.20.1 docs. UI Toolkit / uGUI label refresh details and exact `WithControlsExcluding` path lists were **not** play-tested. Prefer the package sample **Rebinding UI** as a known-good baseline. Scripts were **not** compiled in Unity.
## 3.1 What a rebind actually changes
Interactive rebinding writes **`InputBinding.overridePath`** on an existing binding slot. The asset on disk is unchanged until you serialize overrides yourself. That is what you want: defaults stay in version control; player prefs hold overrides.
## 3.2 Look up and display bindings
```csharp
using System.Linq;
using UnityEngine;
using UnityEngine.InputSystem;
public static class BindingDisplay
{
public static string GetBindingLabel(InputAction action, int bindingIndex, InputBinding.DisplayStringOptions options = default)
{
return action.GetBindingDisplayString(bindingIndex, options);
}
public static int FindBindingIndex(InputAction action, string group)
{
var bindings = action.bindings;
for (int i = 0; i < bindings.Count; i++)
{
if (bindings[i].isComposite)
continue;
if (!string.IsNullOrEmpty(group) && !bindings[i].groups.Split(';').Contains(group))
continue;
return i;
}
return -1;
}
}
```
Use `effectivePath` when you need the path string (override if present, else path). Composite roots are not rebind targets — rebind a **part** index (for example the `Up` part of WASD) or a whole non-composite binding (Space → something else).
## 3.3 Interactive rebind operation
**Always `Dispose()`** the `RebindingOperation` — it owns unmanaged memory.
```csharp
using System;
using UnityEngine;
using UnityEngine.InputSystem;
public sealed class ActionRebinder : MonoBehaviour
{
[SerializeField] InputActionReference actionRef;
[SerializeField] string controlScheme = "Keyboard&Mouse";
InputActionRebindingExtensions.RebindingOperation _op;
public void StartRebind()
{
var action = actionRef.action;
action.Disable();
int bindingIndex = BindingDisplay.FindBindingIndex(action, controlScheme);
if (bindingIndex < 0)
{
Debug.LogWarning("No binding found for scheme " + controlScheme);
action.Enable();
return;
}
_op?.Cancel();
_op?.Dispose();
_op = action.PerformInteractiveRebinding(bindingIndex)
.WithControlsExcluding("<Mouse>/position")
.WithControlsExcluding("<Mouse>/delta")
.WithCancelingThrough("<Keyboard>/escape")
.OnMatchWaitForAnother(0.1f)
.OnComplete(OnComplete)
.OnCancel(OnCancel)
.Start();
}
void OnComplete(InputActionRebindingExtensions.RebindingOperation op)
{
actionRef.action.Enable();
CleanupOp();
// Refresh UI label from GetBindingDisplayString…
BindingOverridesStore.Save(actionRef.action.actionMap.asset);
}
void OnCancel(InputActionRebindingExtensions.RebindingOperation op)
{
actionRef.action.Enable();
CleanupOp();
}
void CleanupOp()
{
_op?.Dispose();
_op = null;
}
void OnDestroy() => CleanupOp();
}
```
Useful configuration hooks from the docs:
- `WithExpectedControlType<TControl>()`
- `WithControlsExcluding(path)`
- `WithCancelingThrough(path)`
- `WithTargetBinding(index)` / `WithBindingGroup(group)` / `WithBindingMask(...)`
Exclude mouse delta/position when rebinding buttons so simply moving the mouse does not steal the bind. For Look rebinds on mouse, you usually expose a **sensitivity** slider (Scale processor magnitude) instead of rebinding delta itself — the package Rebinding UI sample moved that way for Keyboard look.
## 3.4 Save / load overrides
```csharp
using UnityEngine;
using UnityEngine.InputSystem;
public static class BindingOverridesStore
{
const string PrefsKey = "input.rebinds";
public static void Save(InputActionAsset asset)
{
string json = asset.SaveBindingOverridesAsJson();
PlayerPrefs.SetString(PrefsKey, json);
PlayerPrefs.Save();
}
public static void Load(InputActionAsset asset)
{
if (!PlayerPrefs.HasKey(PrefsKey))
return;
string json = PlayerPrefs.GetString(PrefsKey);
// second arg true (default) removes existing overrides first
asset.LoadBindingOverridesFromJson(json);
}
public static void Clear(InputActionAsset asset)
{
asset.RemoveAllBindingOverrides();
PlayerPrefs.DeleteKey(PrefsKey);
}
}
```
Call `Load` once after you construct/clone the asset (generated class constructor, or `PlayerInput.actions`, or your director's Awake) **before** enabling maps.
If you use `PlayerInput`, the docs' snippet is:
```csharp
var rebinds = playerInput.actions.SaveBindingOverridesAsJson();
PlayerPrefs.SetString("rebinds", rebinds);
// …
playerInput.actions.LoadBindingOverridesFromJson(rebinds);
```
## 3.5 Restore defaults per action
```csharp
public static void ResetAction(InputAction action)
{
action.RemoveAllBindingOverrides();
}
```
Or remove a single override with `RemoveBindingOverride(bindingIndex)`.
## 3.6 UX notes that avoid support tickets
1. Disable the action (or whole map) while listening so the rebind press does not also Jump the character.
2. Show "Press a key… (Esc to cancel)" and a timeout.
3. Rebind **per control scheme** so gamepad South does not overwrite keyboard Space in the same slot unexpectedly — target the binding index that belongs to that scheme.
4. Persist after every successful rebind; load on boot.
5. For teaching builds, include a **Reset All** button that calls `RemoveAllBindingOverrides`.
Part 5 wires this into a full Move/Look/Jump/Interact player. Part 4 covers keeping UI navigation alive while the rebind dialog is open.