Why Enhanced Input?
The legacy input system in UE5 (axis mappings, action mappings in Project Settings) works but doesn't scale. Enhanced Input provides:
- Context-based bindings: Different controls for on-foot, in-vehicle, swimming, in-menu
- Input modifiers: Dead zones, sensitivity curves, inversion — all configurable per-action
- Complex triggers: Hold, tap, double-tap, chord (multiple buttons) — built-in
- Runtime remapping: Players can rebind controls at runtime
- Multiple devices: Gamepad, keyboard, mouse, touch — handled cleanly
The legacy system is deprecated. New projects should use Enhanced Input exclusively, and existing projects should migrate before the legacy system is removed.
Core Concepts
Input Actions (IA)
An Input Action represents a gameplay action — not a button. "Jump," "Move," "Look," "Fire" are Input Actions. They're decoupled from physical inputs.
IA_Move → FInputActionValue (Axis2D: X/Y movement vector)
IA_Jump → FInputActionValue (Bool: pressed or not)
IA_Look → FInputActionValue (Axis2D: camera delta)
IA_Fire → FInputActionValue (Bool: pressed or not)
IA_Zoom → FInputActionValue (Axis1D: zoom amount)
Create Input Actions as assets: right-click Content Browser → Input → Input Action.
Value Type is critical:
- Bool: On/off (Jump, Fire, Interact)
- Axis1D: Single float (Zoom, Throttle)
- Axis2D: Two floats (Move direction, Look direction)
- Axis3D: Three floats (VR hand position)
Input Mapping Contexts (IMC)
Mapping Contexts bind physical inputs to Input Actions. You can have multiple contexts active simultaneously with priority ordering.
IMC_OnFoot (Priority: 1)
├── IA_Move → WASD / Left Stick
├── IA_Jump → Space / Face Button Bottom
├── IA_Look → Mouse / Right Stick
├── IA_Fire → Left Mouse / Right Trigger
└── IA_Interact → E / Face Button Left
IMC_Vehicle (Priority: 2)
├── IA_Steer → A/D / Left Stick X
├── IA_Accelerate → W / Right Trigger
├── IA_Brake → S / Left Trigger
└── IA_ExitVehicle → E / Face Button Left
IMC_Menu (Priority: 3)
├── IA_Navigate → WASD / Left Stick / D-Pad
├── IA_Confirm → Enter / Face Button Bottom
├── IA_Cancel → Escape / Face Button Right
└── IA_TabSwitch → Q/E / Bumpers
When the player enters a vehicle, add IMC_Vehicle and remove IMC_OnFoot. Menu opens? Add IMC_Menu with highest priority.
Modifiers
Modifiers transform raw input before it reaches your gameplay code:
- Dead Zone: Ignore small stick movements (essential for gamepads)
- Scalar: Multiply input value (sensitivity)
- Negate: Invert axis (invert Y look)
- Swizzle Input Axis Values: Remap axes (swap X and Y)
- Smooth: Interpolate input over frames
- Response Curve: Non-linear sensitivity curves (acceleration)
Apply modifiers per-binding in the Mapping Context. Different devices can have different modifiers for the same action.
Triggers
Triggers define when the action fires:
- Down: Fires while button is held
- Pressed: Fires once when button is first pressed
- Released: Fires once when button is released
- Hold: Fires after holding for a threshold time
- Hold and Release: Fires when released after holding
- Tap: Fires on quick press (under threshold time)
- Pulse: Fires repeatedly at an interval while held
- Combo: Fires when a sequence of actions is performed
- Chorded Action: Fires only when another action is also active (e.g., Shift+Click)
Setting Up Enhanced Input
Step 1: Create Input Actions
For a basic character controller, create these Input Action assets:
| Asset Name | Value Type | Description |
|---|---|---|
| IA_Move | Axis2D | WASD / stick movement |
| IA_Look | Axis2D | Mouse / right stick camera |
| IA_Jump | Bool | Jump button |
| IA_Sprint | Bool | Sprint modifier |
| IA_Interact | Bool | Interaction button |
| IA_Fire | Bool | Primary attack |
Step 2: Create a Mapping Context
Create an Input Mapping Context asset (IMC_Default) and bind:
IA_Move:
- W → Y=1.0 (add Swizzle modifier: YXZ, then Negate modifier on Y)
- S → Y=-1.0 (add Swizzle modifier: YXZ)
- A → X=-1.0 (add Negate modifier)
- D → X=1.0
- Gamepad Left Stick → Axis2D (add Dead Zone modifier: 0.2)
IA_Look:
- Mouse XY 2D-Axis → Axis2D
- Gamepad Right Stick → Axis2D (add Dead Zone: 0.15, Scalar: 2.5)
IA_Jump:
- Space Bar → Pressed trigger
- Gamepad Face Button Bottom → Pressed trigger
Step 3: Bind in C++
// In your character header
void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override;
void Move(const FInputActionValue& Value);
void Look(const FInputActionValue& Value);
void StartJump(const FInputActionValue& Value);
void StopJump(const FInputActionValue& Value);
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<UInputMappingContext> DefaultMappingContext;
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<UInputAction> MoveAction;
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<UInputAction> LookAction;
UPROPERTY(EditDefaultsOnly, Category = "Input")
TObjectPtr<UInputAction> JumpAction;
// In your character source
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PC = Cast<APlayerController>(Controller))
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
{
Subsystem->AddMappingContext(DefaultMappingContext, 0);
}
}
}
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
UEnhancedInputComponent* EnhancedInput = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);
EnhancedInput->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
EnhancedInput->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyCharacter::Look);
EnhancedInput->BindAction(JumpAction, ETriggerEvent::Started, this, &AMyCharacter::StartJump);
EnhancedInput->BindAction(JumpAction, ETriggerEvent::Completed, this, &AMyCharacter::StopJump);
}
void AMyCharacter::Move(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
const FRotator Rotation = Controller->GetControlRotation();
const FRotator YawRotation(0, Rotation.Yaw, 0);
const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
AddMovementInput(ForwardDirection, MovementVector.Y);
AddMovementInput(RightDirection, MovementVector.X);
}
void AMyCharacter::Look(const FInputActionValue& Value)
{
FVector2D LookVector = Value.Get<FVector2D>();
AddControllerYawInput(LookVector.X);
AddControllerPitchInput(LookVector.Y);
}
Step 4: Blueprint Alternative
In your Character Blueprint:
- Add Enhanced Input Component (usually automatic)
- In BeginPlay, get the Enhanced Input Local Player Subsystem and call Add Mapping Context
- In SetupPlayerInputComponent, use Enhanced Input Action bindings
The Blueprint nodes mirror the C++ API exactly.
Switching Contexts at Runtime
The power of Enhanced Input is context switching:
void AMyCharacter::EnterVehicle(AVehicle* Vehicle)
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = GetInputSubsystem())
{
Subsystem->RemoveMappingContext(OnFootMappingContext);
Subsystem->AddMappingContext(VehicleMappingContext, 0);
}
}
void AMyCharacter::ExitVehicle()
{
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = GetInputSubsystem())
{
Subsystem->RemoveMappingContext(VehicleMappingContext);
Subsystem->AddMappingContext(OnFootMappingContext, 0);
}
}
For menus, add a high-priority context rather than removing the gameplay context — this way the transition back is seamless.
Runtime Rebinding
Allow players to remap controls at runtime:
// Create a player-specific mapping context from a template
UInputMappingContext* PlayerContext = DuplicateObject(DefaultMappingContext, this);
// Modify a specific binding
FEnhancedActionKeyMapping& JumpMapping = PlayerContext->MapKey(JumpAction, EKeys::SpaceBar);
// Change to player's preferred key
PlayerContext->UnmapKey(JumpAction, EKeys::SpaceBar);
PlayerContext->MapKey(JumpAction, PlayerPreferredJumpKey);
// Apply the modified context
Subsystem->RemoveMappingContext(DefaultMappingContext);
Subsystem->AddMappingContext(PlayerContext, 0);
Save player bindings to a config file and restore them on game launch.
Migration from Legacy Input
Step-by-Step Migration
- Inventory your existing bindings: List every Action Mapping and Axis Mapping in Project Settings
- Create Input Actions: One IA per unique gameplay action
- Create Mapping Contexts: Group bindings by game state (on-foot, vehicle, menu)
- Update code: Replace
InputComponent->BindAction/BindAxiswith Enhanced Input bindings - Test each binding: Verify all inputs work with both keyboard and gamepad
- Remove legacy bindings: Delete old mappings from Project Settings
Common Migration Pitfalls
Forgetting to add the Mapping Context: Enhanced Input actions won't fire without an active context. This is the #1 migration bug.
Wrong trigger event: Legacy BindAction uses IE_Pressed/IE_Released. Enhanced Input uses ETriggerEvent::Started/Completed. They're conceptually similar but not identical — Started fires on the trigger threshold, which may differ from a simple button press depending on your triggers.
Missing dead zones on gamepad: Legacy input had implicit dead zones. Enhanced Input requires explicit Dead Zone modifiers on stick bindings. Without them, your camera will drift.
Axis scaling differences: Legacy axis mappings had a Scale property. In Enhanced Input, use Scalar and Negate modifiers instead.
Enhanced Input is a significant upgrade over the legacy system. The initial setup is more work, but the flexibility for runtime remapping, context switching, and complex input handling pays for itself throughout development.