Input Action Maps & Rebinding
An Input Action Map describes what the player can do (actions) and which keys, buttons and sticks do it (bindings). Actions are grouped into contexts such as Gameplay, Vehicle or UI, and contexts can be switched on and off at runtime. Players can rebind any binding marked as rebindable. Their changes are saved per user, layered on top of the asset, and survive later updates to the asset.
The map sits on top of the input slot layer. Devices, slot names such as keyboard_w or controller0_button_a, and dead zones on slots work as before. Legacy input sets from RuntimeConfigs/InputConfig.ddl keep working alongside a map.
Concepts
Contexts
A context is a group of actions with a priority. Contexts are evaluated from the highest priority to the lowest. When a context has Consume Input enabled and is active, every slot it binds is hidden from lower-priority contexts. For example, a UI context with priority 100 that binds keyboard_space to Submit stops Space from also making the character jump. Active By Default decides whether the context starts enabled.
Context-priority diagram. Only slots bound by the active consuming context are hidden from lower-priority contexts.
Actions
An action has a name, an optional display name and a value type. The value is always a 3D vector, and the value type decides which components are used.
| Value type | Value | Typical use |
|---|---|---|
| Button | x = strength of the strongest binding | Jump, Fire, Interact |
| Axis1D | x in [-1, 1] | Throttle, Zoom |
| Axis2D | x, y | Move, Look, UI navigation |
| Axis3D | x, y, z | Free-fly camera |
Bindings
A binding connects slots to an action. Its kind decides how many slots it uses:
| Kind | Slots (in order) | Example |
|---|---|---|
| Simple | one slot | keyboard_space |
| Composite1D | negative, positive | keyboard_s, keyboard_w |
| Composite2D | -x, +x, -y, +y | A, D, S, W or a thumbstick's four half-axes |
| Composite3D | -x, +x, -y, +y, -z, +z | WASD + Q/E |
Modifiers are extra slots that must be held for the binding to work, such as Ctrl for Ctrl + S. While a binding with modifiers is active, plain bindings on the same slot in the same context are ignored, so Ctrl + S saves without also moving the character backwards.
Gamepad slots are written without a controller number, for example controller_button_a. Such a slot follows the player's controller: it means controller0_button_a for player 0 and controller1_button_a for player 1. The editor lists gamepad slots only in this form. A numbered slot such as controller2_button_a can still be typed in and always means that one physical controller. Importing InputConfig.ddl turns controller0_ slots into the player form.
Processors
Processors change a binding's value in the order they are listed. Axes limits Invert, Scale, Sensitivity, Clamp and Power to the selected axes.
| Processor | Value / Value2 | Effect |
|---|---|---|
| DeadZone | lower / upper | Radial: length below lower becomes 0 and length above upper becomes 1, and values in between are remapped. The user value DeadZone.<Scheme> replaces lower. |
| Invert | n/a | Negates the axes. |
| Scale | factor | Multiplies the axes. |
| Sensitivity | factor | Like Scale, and also multiplied by the user value <Action>.Sensitivity. |
| Normalize | n/a | Limits the vector length to 1 (prevents diagonals from moving faster). |
| Clamp | min / max | Clamps the axes. |
| Power | exponent | sign(v) · abs(v)^exponent, for response curves. |
Triggers
A binding counts as actuated while the length of its value is at least the trigger's Threshold. The trigger type decides on which frames the action is reported as triggered.
| Trigger | Triggered | Uses |
|---|---|---|
| Press | on the frame it becomes actuated | n/a |
| Down | every frame while actuated | n/a |
| Release | on the frame it stops being actuated | n/a |
| Hold | once, after being actuated for Time | Time |
| Tap | on release, if held for at most Time | Time |
| DoubleTap | on a second press that starts within Time of the first release | Time |
| Repeat | on press, then every Repeat Interval after the initial delay Time | Time, Repeat Interval |
Besides the trigger, each action reports a key state (Up, Pressed, Down, Released) that follows actuation. It also reports a phase (Started, Ongoing, Triggered or Canceled) for the binding that currently drives it. A Hold released too early ends as Canceled.
Schemes
Each binding belongs to a scheme: Keyboard & Mouse, Gamepad, XR or Touch. The scheme is usually derived from the slot name. The engine tracks which scheme the player used last, and button prompts and the Controls menu use it.
Setting up an input action map
- In the asset browser, create an Input Action Map asset. Project › Input Configuration offers to create one, importing the project's existing
InputConfig.ddl. - Add contexts, then add actions to each context and choose their value types.
- Select an action, add bindings, and click a slot to capture a key or mouse button. Press
Escto cancel the capture. - Set triggers and processors in the property grid. Clear Rebindable on bindings that players must not change, such as the pause menu key.
- Click Validate to find duplicate slots within a context.
- Reference the asset in the project's startup settings (
Startup.ddl, entryInputActionMap). The Player and Play-in-Editor activate it for player 0 at startup.
Each simple binding without modifiers is also written into a legacy input set with the context's name, up to three per action. Code that calls plInputManager::GetInputActionState("Gameplay", "Jump") therefore sees the map's bindings too.
Reading input
Input Component
Set the component's Input Context to a context of the map. The component then sends plMsgInputActionTriggered for that context's actions. The message has Value (vec3), Triggered and TriggerPhase, alongside KeyPressValue and TriggerState. A message is also sent on every frame where a trigger fires, for example when a hold completes. Forward To Blackboard writes a float for Button and Axis1D actions, a vec2 for Axis2D and a vec3 for Axis3D.
| Property | Default | Description |
|---|---|---|
| Input Set | n/a | Legacy input set. Used when Input Context is empty. |
| Input Context | n/a | Context in the player's action map. |
| Player | 0 | Which player's map to read (0–3). |
| Granularity | PressOnly | Which key-state changes send messages. |
| Forward To Blackboard | off | Mirrors action values into the owner's blackboard. |
Scripts
AngelScript and Visual Script expose the Input class:
Input::GetActionValue("Move") // vec3
Input::GetActionAxis("Throttle") // float
Input::IsActionDown("Sprint")
Input::WasActionPressed("Jump")
Input::WasActionTriggered("Interact") // respects Hold / Tap / DoubleTap
Input::PushContext("Vehicle") // applied at the next input update
Input::PopContext("Vehicle")
Input::GetActionPrompt("Interact") // "E" or "X" depending on the active scheme
C# uses Plasma.InputBindings. C++ uses plInputActionMap::GetPlayer(0).GetAction("Move").
Runtime rebinding
plGameApplication::GetInputBindings() owns player 0's map and the user's changes. The game UI Controls menu and C# use its Execute command interface:
| Command | Arguments | Result |
|---|---|---|
| Query | Scheme? | Actions (name, display name, context, bindings with slot labels, Primary / Secondary prompts), Values |
| BeginRebind | Action, Binding or BindingIndex, Scheme, SlotIndex, Conflict, Timeout | Starts listening |
| Poll | n/a | Rebind: Status, SlotLabel, ConflictAction, SecondsRemaining |
| Resolve | Resolution = Swap | Replace | Reject | Finishes a rebind that stopped on a conflict |
| Cancel / Reset / Save | Reset: Action? | n/a |
| SetValue | Key, Value | Tuning values, such as Look.Sensitivity.Gamepad or Look.InvertY |
| Prompt | Action, Scheme? | Prompt |
How a rebind works
- When listening starts, every input that is already held is ignored until it has been released.
- The next key or button press, or an axis pushed past the threshold, is captured. The cancel keys (Esc and the gamepad Menu button by default) end the rebind without changes.
- If another rebindable binding of the same scheme in the same context already uses that slot, the Conflict rule applies. Ask stops and waits for
Resolve. Swap gives the other binding this binding's old slot. Replace removes the slot from the other binding. - The captured key does not trigger its new action until it has been released once.
- The change is saved on the next frame.
While a rebind is active, every action reports released.
Where changes are stored
Changes are saved to <appdata>/<project>/Settings/<user>/InputBindings.ddl, next to GameSettings.ddl, with an InputBindings.last-good.ddl backup. When the signed-in platform user changes, the file of the new user is loaded. The file stores only the differences from the asset, keyed by binding and action GUIDs. Renaming an action in the asset therefore keeps the player's bindings, while deleting a binding drops its override.
Button prompts
Prompt labels come from :base/RuntimeConfigs/InputGlyphs.ddl, then :project/RuntimeConfigs/InputGlyphs.ddl:
Glyph { string %Slot{"controller_button_a"} string %Label{"A"} string %Sprite{"UI/Glyphs/XboxA"} }
Slots without an entry use the display name that the device registered. Composite bindings are shown in W/A/S/D order, and modifiers as Ctrl + S.
Troubleshooting
| Symptom | Cause |
|---|---|
| An action never fires | Its context is not active. Check Active By Default and your PushContext calls. Also check whether a higher, consuming context binds the same slot. |
| A gameplay action fires while a menu is open | The UI context does not consume input, or it does not bind that slot. |
| A stick action jitters around zero | Add a DeadZone processor, or raise the trigger threshold. |
| A rebind never completes | The key is excluded, belongs to another scheme than the request, or conflicts with Reject. |
| Rebinds are lost after an update | The binding was deleted and recreated in the asset, which gives it a new GUID. |
| "deserialized A of B bytes" for plInputComponent | The asset cache is stale after the component moved to version 4. Delete the project's AssetCache. |