How-To: Rebindable Keys
Goal: give the player a working key-rebinding list, and decide how every bound key prints - full name, platform glyph, or abbreviation.
1. Mark which actions are rebindable
An action appears in the rebind list only if it carries a UPlayerMappableKeySettings on its mapping - Enhanced Input's own mechanism. There is no second list of "rebindable actions" for you to keep in step: the decision lives on the mapping, next to the binding it governs.
Open the Input Mapping Context, expand the mapping, and under Setting Behavior choose a Player Mappable Key Settings class, then give it a Name (this is the stable identity - see the warning below) and a Display Name and Display Category (which is how the list groups rows).
UPlayerMappableKeySettings::Name is the key Enhanced Input saves a rebind under, the name CriticalMappingNames matches against, and the name a conflict warning reports. Renaming it discards every rebind a player has already made for that action.2. Build the rows
K2_RegisterKeybindingSettings(LocalPlayer, Collection, bGamepad) builds one row per rebindable action and adds them to the collection you hand it. Call it once per device. The two input builders already do this - reach for it directly only when you want the list somewhere else.
In your registry Blueprint: Event K2_RegisterInputSettings (LocalPlayer) -> Create Collection (Outer = self, DevName = KeyboardBindings, DisplayName = "Keybindings") -> K2 Register Keybinding Settings (Local Player = the event pin, Collection = the new collection, Gamepad = false) -> K2 Register Setting (the collection).
Repeat with a second collection, DevName GamepadBindings, Gamepad = true.
UCrimsonSettingKeyboardInput addresses everything by BUTTON INDEX - 0 primary, 1 secondary - and resolves the slot each one means per device.SaveChanges() persists the Enhanced Input user settings alongside everything else.3. Author the row widget
The row class is UCrimsonSettingsListEntrySetting_KeyboardInput. Subclass it as a Widget Blueprint and give it these binds:
| Widget name | Required? | Purpose |
|---|---|---|
Button_PrimaryKey | Yes | The primary key pill. Clicking it starts listening for a new key. |
Button_SecondaryKey | Yes | The secondary pill. |
Button_Clear | Yes | Unbinds. Tooltip comes from ClearButtonTooltip. |
Button_ResetToDefault | Yes | Restores the mapping's authored key. Tooltip from ResetButtonTooltip. |
ActionWidget_PrimaryKey | Optional | Glyph slot for the primary pill on keyboard and mouse rows. |
ActionWidget_SecondaryKey | Optional | Glyph slot for the secondary pill on keyboard and mouse rows. |
ActionWidget_PrimaryKey_Gamepad | Optional | Glyph slot for the primary pill on gamepad rows. |
ActionWidget_SecondaryKey_Gamepad | Optional | Glyph slot for the secondary pill on gamepad rows. |
All four glyph slots are BindWidgetOptional and typed UCommonActionWidget, so a project places whatever UCommonActionWidget subclass it likes and this plugin never learns about it. The row hands the pair matching its own device the row's UInputAction and collapses the other pair. What a key looks like is therefore your decision, not the plugin's.
UCommonActionWidget on its own resolves for whatever device the player is currently holding, which is the wrong question here - so the pair a row does not edit is collapsed, and the pair it does edit should be pinned to that device.Subclass
UCommonActionWidget once and fix all three there, then place your subclass in the four slots. It needs: a key-slot property, so a widget in a secondary slot asks for the second key rather than the first; an input-type override, so the two unsuffixed widgets are pinned to mouse and keyboard and the two _Gamepad ones to gamepad regardless of what the player is holding; and a fallback that reads the Enhanced Input user settings rather than the applied mapping contexts. The last one is the fix for the menu case: UEnhancedInputLocalPlayerSubsystem::GetUserSettings() -> UEnhancedInputUserSettings::GetActiveKeyProfile() gives you FPlayerKeyMapping entries whose GetSlot(), GetCurrentKey() and GetAssociatedInputAction() answer the question without any applied mapping context, and no input mode filters that profile.UCrimsonSettingKeyboardInput exposes the virtual hooks OnBindingApplied, OnBindingResetToDefault, DoesMappingConflict, OnConflictingBindingCleared, GetKeyTextForButton and IsMappingCustomized, the list entry exposes BeginKeyCapture, PushModalPanel, GatherConflictingActions, GetPendingKeyDisplayText, ApplyPendingBinding and ClearPendingConflicts, and UCrimsonSettingRegistry::KeybindingSettingClass selects your subclass. (CrimsonCore - requires all Crimson plugins) UCrimsonCoreSettingKeyboardInput and UCrimsonCoreSettingsListEntry_KeyboardInput are the reference: they store chords through the input plugin and draw the modifier glyphs ahead of the key.4. Choose how keys print
UCrimsonSettingsDeveloperSettings::KeybindDisplayMode decides how every keybind pill in the project reads. It is one project setting rather than a per-row property, so the screen cannot disagree with itself - and the rows are authored assets a consumer would otherwise edit one at a time.
| Mode | Editor label | The pill shows |
|---|---|---|
Normal | Full Names | The key's full name, chord included - Shift+Right Mouse Button. Longest, but unambiguous. |
Glyphs | Platform Glyphs | A platform glyph, drawn by the row's action widget. The shipped default - an icon is read faster than a name and survives a narrow pill. Falls back to abbreviated text wherever a glyph cannot be drawn. |
Short | Abbreviated | The abbreviated name - Shift+RMB. Fits a narrow pill without truncating. |
Short mode asks GetAbbreviatedKeyName(Key), which returns the configured override when the key has one and otherwise falls through to the engine's own short name (FKey::GetDisplayName(bLongDisplayName=false)). That fallback is already right for letters, digits and the mouse buttons - KeyAbbreviations exists for the keys where it disappoints, which is the gamepad triggers, bumpers and stick clicks: the engine renders those at almost full length, exactly where a narrow pill runs out of room.
DefaultGame.ini if you disagree with any of it.Glyphs mode the pill's text is cleared and the action widget is shown; in every other mode - and for an unbound key, an unresolvable action, a key whose platform data carries no art, or the critical UNBOUND state - the action widget is collapsed and the pill shows text. The row asks the widget's own GetIcon() whether it resolved anything before choosing glyph over text, so a missing icon is never an empty box. A row set to Glyphs with no bind for the device it edits logs one warning per row class and falls back to Short.Full Names prints Right Mouse Button; Abbreviated prints RMB; Platform Glyphs draws the icon.5. Warn about cleared critical bindings
CriticalMappingNames lists the mappings the game genuinely needs - the ones a player who clears them is probably about to file a bug about. Names match UPlayerMappableKeySettings::Name. It ships empty, so nothing warns until a project opts in.
Project Settings -> Crimson -> Crimson Settings -> Critical Bindings -> Critical Mapping Names. Add one entry per mapping name. The panel writes DefaultGame.ini for you.
A listed row's primary key button (button index 0 only) warns while that row's own device has no key at all - ShouldWarnUnbound() is IsCriticalMapping() && IsUnboundOnThisDevice(). The button shows CriticalUnboundText (UNBOUND when the row class leaves it empty) with CriticalUnboundTooltip, and SetWarningState(true) plays the row's Anim_Warning.
What the warning looks like is content, not code. UCrimsonSettingsButtonBase only sequences the animations; Anim_Warning is authored on your Widget Blueprint. Warning replaces idle only, so a button warning about a missing binding still highlights normally when the player reaches for it.
See also
- How-To: Use the Ready-Made Collections - the two input tabs that already carry a rebind list.
- API Reference -> Input & rebinding - the full
UCrimsonSettingKeyboardInputsurface, and what the four glyph slots expect.