Replacing Legacy Input with Enhanced Input in Unreal Engine 5
Learn how Unreal Engine 5's Enhanced Input System decouples physical keys from game intent using Input Actions and Mapping Contexts, with a C++ setup, cleanup rules, and common pitfalls.
08 Apr 2026, 17:21 UTC

In the legacy input system, a key press calls a gameplay function directly. Rebinding means editing code, and switching control schemes—such as moving a character versus driving a vehicle—requires manual state tracking. The practical fix is Unreal Engine 5's Enhanced Input System, which separates the physical key from the game intent. You define a UInputAction asset for what the player wants to do (for example, IA_Move or IA_Jump), and a UInputMappingContext asset that maps keys or axes to those actions. Rebinding and context switching then become data changes rather than code changes.
How the pieces fit together
Enhanced Input introduces three main objects:
- Input Action (
UInputAction): a semantic label such as Move or Jump. It carries a value type—digital (bool), axis 1D (float), axis 2D (Vector2D), or axis 3D (Vector)—so gameplay code can read the correct shape. - Input Mapping Context (
UInputMappingContext): a data asset that maps one or more keys, buttons, or axes to an Input Action. You can add modifiers (for example, negate or scale) and triggers (for example, pressed, held, tapped) to shape the raw signal. - Enhanced Input Component (
UEnhancedInputComponent): the runtime component on a Pawn that receives events from the subsystem and dispatches them to bound functions.
A Local Player Subsystem (UEnhancedInputLocalPlayerSubsystem) tracks which mapping contexts are active and at what priority. When a key is pressed, the subsystem finds the highest-priority context that maps that key, applies any modifiers or triggers, and forwards the resulting action event to the bound function on the input component.
Project settings and a worked C++ example
Before binding in C++, tell the engine to use the Enhanced Input component. Open Project Settings > Engine > Input and set the default input component class to the Enhanced Input variant. The exact class name and menu path have shifted across UE5 minor releases, so verify the label against your engine build rather than copying an older tutorial.
Assume you have created two assets: IA_Move (value type Axis2D) and IA_Jump (value type Digital), plus a mapping context IMC_Default that maps W/A/S/D to IA_Move and Space to IA_Jump. In your Pawn or Character header, declare the action properties and callback functions:
// MyPawn.h
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> JumpAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputMappingContext> DefaultMappingContext;
void Move(const FInputActionValue& Value);
void JumpStarted(const FInputActionValue& Value);
In the source file, override SetupPlayerInputComponent to bind the actions, and use BeginPlay to inject the mapping context:
// MyPawn.cpp
void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
if (UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(PlayerInputComponent))
{
EIC->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyPawn::Move);
// Use Started for a one-shot action so it does not fire every frame while held.
EIC->BindAction(JumpAction, ETriggerEvent::Started, this, &AMyPawn::JumpStarted);
}
}
void AMyPawn::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PC = Cast<APlayerController>(GetController()))
{
if (ULocalPlayer* LP = PC->GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsys = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(LP))
{
Subsys->AddMappingContext(DefaultMappingContext, 0);
}
}
}
}
In Move, read the value as a 2D vector rather than assuming a float:
void AMyPawn::Move(const FInputActionValue& Value)
{
const FVector2D Axis = Value.Get<FVector2D>();
// Use Axis.X and Axis.Y for movement.
}
Assign the assets in the Details panel or in a derived Blueprint. The priority argument in AddMappingContext controls which context wins when multiple contexts map the same key; higher numbers take precedence.
Cleanup and context switching
Mapping contexts are not automatically removed when a Pawn loses possession. If you leave a context active after unpossess, the next Pawn may receive duplicate or stuck inputs because the old context still routes events. Remove the context when the Pawn is unpossessed or destroyed:
void AMyPawn::UnPossessed()
{
if (APlayerController* PC = Cast<APlayerController>(GetController()))
{
if (ULocalPlayer* LP = PC->GetLocalPlayer())
{
if (UEnhancedInputLocalPlayerSubsystem* Subsys = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(LP))
{
Subsys->RemoveMappingContext(DefaultMappingContext);
}
}
}
Super::UnPossessed();
}
For vehicles or menus, add a second mapping context with a higher priority and remove it when the mode ends. Because the mapping lives in data, a rebinding widget can swap keys inside the context asset without changing C++.
Limits and common mistakes
Do not mix legacy and Enhanced Input on the same component. Calling legacy BindAction or BindAxis on a UEnhancedInputComponent silently fails. Choose one path per input component.
Match the trigger event to the action type. Using ETriggerEvent::Triggered for a digital action such as Jump fires repeatedly while the key is held. Use Started for one-shot presses and Completed for release logic.
Check the action's value type. Treating every action as a float axis produces incorrect movement vectors or ignored input. An Axis2D action returns FVector2D; a digital action returns a bool-like float.
Keep keys out of gameplay logic. Hard-coding FKey names in C++ defeats the purpose of the system. All physical bindings belong in mapping contexts.
Watch asset lifetime. Input Actions and Mapping Contexts are UObject assets. Circular references between a Pawn and its mapping context can interfere with garbage collection. Prefer loading contexts by soft reference or assigning them in the editor rather than creating hard reference cycles.
Modifiers and triggers cost evaluation time. Each active mapping applies modifiers and evaluates triggers per frame. A context with dozens of mappings is not free; profile if you see input-related frame spikes.
API drift across UE5 versions. Subsystem lookup macros, component class names, and Project Settings labels have changed between UE5 minor releases. Code copied from an older tutorial may compile with warnings yet bind nothing. Verify class names and settings against your exact engine build.
How to verify the setup
Add a temporary log or on-screen debug message inside the Move and JumpStarted functions. Press the mapped keys and confirm the callbacks fire. Then bind a debug key that calls AddMappingContext and RemoveMappingContext at runtime; toggle the context off and on to prove that priority and removal behave as expected. Finally, package a small test map with a rebinding widget, change the jump key, restart, and confirm the new key drives the same Input Action.
Because Enhanced Input settings and class names vary by engine version, treat the snippets above as a template and confirm each symbol against your UE5 build before shipping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.