UE5 Enhanced Input: Quick Notes
Contents
Enhanced Input replaced the old input bindings. After a few years of use, here are the notes I should have written down much earlier.
The core idea of Enhanced Input is fully decoupling physical keys (Raw Input) from abstract intent (Input Action), and processing input logic through a pipelined data flow.
1. Modifiers stack — order matters
A Modifier is essentially a linear processing pipeline (pipelined vector processing): input data flows through each Modifier function top to bottom.
Recommended canonical Modifier chain
To avoid distorted diagonal movement or broken dead zones, follow this ordering rule:
- Swizzle YXZ: axis rearranging (e.g. mapping a 1D key input onto the Y axis of a Vector2D).
- Negate: sign flipping.
- Dead Zone: filtering mechanical jitter of physical sticks/keys.
- Scale / FOV Scaling: value scaling or sensitivity factors.
Key pitfalls:
-
Dead Zone vs Scale order:
- Scale before Dead Zone (wrong): if the raw input is
0.15,Scale(2.0)turns it into0.3. With a dead zone of0.2, jitter that should have been filtered now crosses the threshold. - Dead Zone before Scale (correct): the dead zone only truncates the raw normalized input (0–1) linearly/radially; once it is clean, multiply by sensitivity or scaling.
- Scale before Dead Zone (wrong): if the raw input is
-
Swizzle vs Negate order:
- When mapping WASD to a 2D vector:
Sgoes throughNegateto flip the Y axis;AneedsSwizzle YXZfirst (moving the X-axis input onto the Y axis), thenNegate. Swapping the order scrambles the directional mapping.
- When mapping WASD to a 2D vector:
2. One action, many triggers (the Trigger lifecycle)
A single InputAction can host multiple Triggers (Pressed, Hold, Pulse…), dispatching callbacks through a state-machine model at runtime.
Trigger event lifecycle
[key down] ──> Started ──> Ongoing (held / charging) ──> Triggered (condition met) ──> Completed (released)
└── (released midway) ──> Canceled
| Event (ETriggerEvent) | When it fires | Typical use |
|---|---|---|
Started | Key just pressed / detection begins | Play wind-up SFX, open charge UI |
Ongoing | Evaluating (called every frame) | Update charge progress, measure hold duration |
Triggered | Condition fully met (per frame or once) | Fire attack, continuous movement, full-charge release |
Completed | Key released after a successful trigger | Throw projectile, tap-release actions |
Canceled | Interrupted before conditions were met | Charge interrupted / manually canceled |
Design pattern
Instead of creating three Input Actions for “start charging / charging / release”, a single IA_ChargeAttack suffices — branch on GetTriggerEvent() inside the C++ / Blueprint callback:
void AMyCharacter::OnChargeAttack(const FInputActionInstance& ActionInstance)
{
switch (ActionInstance.GetTriggerEvent())
{
case ETriggerEvent::Started:
// 1. play charge SFX / open VFX
break;
case ETriggerEvent::Ongoing:
// 2. update charge progress: ActionInstance.GetElapsedTime()
break;
case ETriggerEvent::Triggered:
// 3. fully charged, release the heavy attack
break;
case ETriggerEvent::Canceled:
// 4. interrupted midway, reset UI and state
break;
}
}
3. Dynamic IMC injection and priority
Dynamically stacking multiple contexts via UEnhancedInputLocalPlayerSubsystem is Enhanced Input’s killer feature over the legacy system.
Priority stacking rules
- High priority overrides: when several IMCs are stacked, the higher-Priority context processes key mappings first.
- Consume Input: with
Consume Inputenabled on an IMC or IA, once a high-priority context responds to a key (e.g.Ein a UI menu), the event is not forwarded to lower-priority contexts (e.g. world interaction onE).
A typical priority architecture:
[Priority 10] IMC_UI / IMC_Menu (Consume Input on — blocks character control)
▲
[Priority 2] IMC_Aiming / IMC_Vehicle (overrides specific actions, e.g. RMB aim / vehicle camera)
▲
[Priority 0] IMC_Default (base movement, jumping, camera)
4. C++ binding pitfalls
- Module dependency: add
"EnhancedInput"to your.Build.csand include:
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
- Safe casting:
if (UEnhancedInputComponent* PEI = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
{
PEI->BindAction(IA_Move, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
}
- Callback signature choice:
- Only need the current axis/key value:
void Move(const FInputActionValue& Value); - Need full metadata (TriggerEvent, elapsed time…):
void Move(const FInputActionInstance& ActionInstance);
- Only need the current axis/key value:
5. Going further: custom Modifiers & Triggers
- Custom Modifier: inherit
UInputModifier, overrideModifyRaw_Implementation. Great for “scale Pitch sensitivity by camera pitch” or “non-linear smoothing while aiming down sights”. - Custom Trigger: inherit
UInputTrigger, overrideUpdateState_Implementation. Great for double-tap, chord keys, and other game-specific trigger conditions.