# 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.