# 2. Binding Display Strings and Device Changes Part of [[Context-Sensitive Prompts and Onboarding]]. Prev: [[1. Prompt Design and Leave Training for Last]]. Next: [[3. Glyph Prompts in UI Toolkit]]. The Input System already knows how to print a binding. Do not maintain a parallel dictionary of "Interact = E." The official 1.20 manual is [Display bindings](https://docs.unity3d.com/Packages/[email protected]/manual/display-bindings.html). This part wraps that API so a UI view can subscribe to it. Runtime rebinding itself lives in [[3. Runtime Rebinding]]; here we only *read* the result. ## 2.1 Display strings ```csharp using UnityEngine.InputSystem; public static class BindingPrompt { public static string ForAction(InputAction action, string controlScheme = null) { if (action == null) return string.Empty; if (!string.IsNullOrEmpty(controlScheme)) { int index = action.GetBindingIndex(InputBinding.MaskByGroup(controlScheme)); if (index >= 0) return action.GetBindingDisplayString(index); } // Whole-action string: the currently active binding(s), including overrides. return action.GetBindingDisplayString(); } } ``` `GetBindingDisplayString()` on the action "takes into account which bindings are currently active and the actual controls bound to the action" (official wording). After `PerformInteractiveRebinding` and a saved override, this is the string you want. Pass a control scheme name (`Keyboard&Mouse`, `Gamepad`, whatever you authored in the asset - see [[2. Control Schemes Devices and Consumption Patterns]]) when you want the prompt for *that* scheme rather than "whatever is active." A HUD that always shows both ("E / A") can call the method twice. A context prompt usually shows one: the scheme the player is using now. ## 2.2 Device layout and control path, for icons The same manual documents an overload that also returns the device layout and the control path, so you can pick a sprite reliably (`buttonSouth` rather than a localized "A"): ```csharp using UnityEngine; using UnityEngine.InputSystem; public readonly struct BindingVisual { public readonly string displayString; public readonly string deviceLayout; public readonly string controlPath; public BindingVisual(string displayString, string deviceLayout, string controlPath) { this.displayString = displayString; this.deviceLayout = deviceLayout; this.controlPath = controlPath; } } public static class BindingPrompt { public static BindingVisual ForBinding(InputAction action, int bindingIndex) { if (action == null || bindingIndex < 0) return new BindingVisual(string.Empty, string.Empty, string.Empty); string text = action.GetBindingDisplayString(bindingIndex, out string layout, out string path); return new BindingVisual(text, layout, path); } public static BindingVisual ForScheme(InputAction action, string controlScheme) { int index = action.GetBindingIndex(InputBinding.MaskByGroup(controlScheme)); if (index < 0) index = 0; return ForBinding(action, index); } public static bool IsGamepad(string deviceLayout) { return !string.IsNullOrEmpty(deviceLayout) && InputSystem.IsFirstLayoutBasedOnSecond(deviceLayout, "Gamepad"); } } ``` Map `controlPath` values you care about (`buttonSouth`, `buttonWest`, `leftTrigger`, `dpad/up`, ...) onto your own sprites. There is no official Unity-supplied icon pack you are required to use; bring art, or fall back to `displayString`. Keyboard layouts can change the printed key at runtime (official note). Subscribe to changes (below) so a French AZERTY player does not keep seeing a QWERTY prompt. ## 2.3 When to refresh Refresh the prompt when any of these happen: - A device is added or removed (`InputSystem.onDeviceChange`). - The player's control scheme changes (`PlayerInput.onControlsChanged` if you use `PlayerInput`). - A binding override is applied or cleared (after your rebind UI finishes - hook the same place [[3. Runtime Rebinding]] writes overrides). - You enable or disable an action map. ```csharp using System; using UnityEngine; using UnityEngine.InputSystem; /// <summary> /// Raises Refreshed when display strings may have changed. /// NOT compiled in Unity. /// </summary> public sealed class PromptBindingWatcher : MonoBehaviour { [SerializeField] PlayerInput playerInput; // optional public event Action Refreshed; void OnEnable() { InputSystem.onDeviceChange += OnDeviceChange; if (playerInput != null) playerInput.onControlsChanged += OnControlsChanged; } void OnDisable() { InputSystem.onDeviceChange -= OnDeviceChange; if (playerInput != null) playerInput.onControlsChanged -= OnControlsChanged; } void OnDeviceChange(InputDevice _, InputDeviceChange change) { switch (change) { case InputDeviceChange.Added: case InputDeviceChange.Removed: case InputDeviceChange.Disconnected: case InputDeviceChange.Reconnected: case InputDeviceChange.UsageChanged: case InputDeviceChange.ConfigurationChanged: Refreshed?.Invoke(); break; } } void OnControlsChanged(PlayerInput _) => Refreshed?.Invoke(); // Call this from your rebind UI when an override is saved or reset. public void RaiseRefreshed() => Refreshed?.Invoke(); } ``` `InputDeviceChange` values used above are the long-standing Input System enum. If a value is renamed in a later 1.20 patch, the switch will fail to compile - drop the unknown case; the others are enough. If you do not use `PlayerInput`, keep a current scheme string on your own input service and call `RaiseRefreshed` when it changes. Do not poll `GetBindingDisplayString` every frame. ## 2.4 Exercise Print `GetBindingDisplayString()` for Interact on keyboard, then plug in a gamepad and print it again, then rebind Interact in the UI from [[5. Worked Example Remappable Player]] and print a third time. If all three strings are "E", you are not reading the live action. ## Sources - Display bindings (1.20): https://docs.unity3d.com/Packages/[email protected]/manual/display-bindings.html - [[3. Runtime Rebinding]]