Enhanced Input Basics in UE5: From WASD Movement to Switching Control Schemes

Created: 2025-12-12Last updated: 2026-09-05

Input Actions are what you want to do; Mapping Contexts are the key assignment table. After confirming input arrives, build WASD movement and jumping, a hold trigger, and switching between walking and driving modes.

Press W to walk forward. While in a car, use that same W as the accelerator. The key is the same, but what you want to do changes with the game's situation.

Enhanced Input is the system that handles "what you want to do" and "which key" separately. There look like a lot of assets to configure at first, but once the roles are clear, so is where to change key layouts and control modes.

Treating what you want to do and which key you use as separate cards

In this article we first wire it up so a key press prints text, then move on to WASD movement and jumping. At the end we switch control tables with the F key and confirm that the same W changes from "walk input" to "accelerator input."

What You'll Learn

  • The roles of Input Action, Mapping Context, and Subsystem
  • How to align WASD values into left-right and forward-back input
  • The difference between Started and Triggered, and how to build a hold
  • Hands-on keeping shared controls while swapping only the movement table

Sponsored

Separate what you want to do from key assignments

The first things to learn are the Input Action (IA) and the Input Mapping Context (IMC).

An IA is a name tag for an operation such as "move" or "jump." It expresses what you want to do in game rather than a key name. An IMC is the control table binding those name tags to keys. The assignment "jump is Space" is written here.

In other engines: this corresponds to Unity's Input System (Action-based) and Godot's InputMap actions. Mapping Contexts, which swap the whole table, are a UE-specific idea.

Input Action is what you want to do, Mapping Context is the key assignment, and the Subsystem manages which table is in use

The window that applies that table to the current player is the Enhanced Input Local Player Subsystem. It is a long name, but think of it here as "the thing managing the control table my player uses." Hand it an IMC with Add Mapping Context and that table becomes active.

But enabling the table alone does not make the character jump. You receive the IA's event in Blueprint and connect it to logic such as Jump.

What you want to decideWhere to configure it
Create an operation called jumpInput Action
Make Space perform the jumpThe IMC's key assignment
Use this control table right nowAdd Mapping Context to the Subsystem
Actually jump when input arrivesThe IA event in Blueprint

With that split, moving jump to a different key requires no rebuilding of the jump logic. You can also swap the whole table, as with walking and driving.

Modifiers that transform values and Triggers that decide completion conditions like a hold are settings added to that input. First get one plain input working end to end, then try the differences later.

Prepare a practice character

We use a Third Person Blueprint project. In versions with a "Variant" at creation, choose None. This is for people who know how to add components, create variables, and connect pins.

The template comes with input built in. So that you can confirm the results of the input you build yourself, we prepare a practice Character and GameMode. Any map with a Third Person floor works as the level.

1. Build the body and camera

Create an InputPractice folder in the content browser and save subsequent assets there. Choose Character as the Blueprint Class parent and name it BP_InputPractice. A Character comes with Character Movement, which handles walking and gravity.

Set the Capsule Component's Capsule Radius to 34 and Capsule Half Height to 88, and add components as follows.

PartParentSettings
Body (Static Mesh)Capsule ComponentStandard Cube, Relative Location 0, Scale X=0.5 / Y=0.5 / Z=1.76
SpringArmCapsule ComponentRelative Location Z=60, Rotation Pitch=-25, Yaw/Roll=0, Target Arm Length=400
CameraSpringArmRelative Location and Rotation all 0

Set Body's Collision Presets to NoCollision and Simulate Physics off. Collision is left to the Capsule Component surrounding the body. You do not need to assign anything to the existing Mesh.

Turn off Use Pawn Control Rotation on SpringArm and Camera. Turn off Use Controller Rotation Pitch/Yaw/Roll in Class Defaults, and Character Movement's Orient Rotation to Movement and Use Controller Desired Rotation as well.

Here we walk a boxy character while keeping the camera and body facing fixed. That makes it easier to compare input direction against actual movement. We do not add mouse-look camera rotation here.

2. Make this Character the player

Create BP_InputPracticeGameMode with GameModeBase as parent. In Class Defaults, set Default Pawn Class to BP_InputPractice and Player Controller Class to the standard PlayerController.

Change GameMode Override in the level's World Settings to this GameMode. Place the PlayerStart on the floor with rotation set to 0. The Character is spawned by the GameMode, so you do not need to place BP_InputPractice in the level by hand.

The GameMode decides things like which player this level uses. The division of responsibility is also explained in the gameplay framework article.

Compile, save, and Play. If you get a camera looking at the boxy body from behind, you are ready. Nothing moves at this stage.

Body and SpringArm as children of the capsule, with the Camera at the end of the SpringArm

First, print text with the E key

The first goal is displaying Interact when you press E once. Stop Play and, before movement, build the path input travels.

1. Bind an operation to a key

Right-click in the content browser, create "Input" → "Input Action," and name it IA_Interact. Open it and set Value Type to Digital (Bool). Value Type is the kind of value that operation receives. Since this is pressed or not pressed, we use Bool.

Next create IMC_PlayerControls with "Input" → "Input Mapping Context." Add a row for IA_Interact with the + in Mappings and set the key to E. Leave Modifiers and Triggers empty and save.

2. Enable the control table

Return to BP_InputPractice's event graph. Place Get Player Controller and set Player Index to 0. From its output, create Get Enhanced Input Local Player Subsystem.

These two are nodes that fetch values. No white exec line runs through them. Connect the Player Controller's Return Value to the Subsystem node's Player Controller input.

Getting the Enhanced Input Local Player Subsystem from the Player Controller

Right-click the Subsystem's Return Value, "Promote to Variable," and name it InputSubsystem. That remembers what you fetched so you can designate the same window later.

Connect a white line from Event BeginPlay to Set InputSubsystem, then call Add Mapping Context.

  • Target: the blue output of Get InputSubsystem
  • Mapping Context: IMC_PlayerControls
  • Priority: 0

Priority is used when several tables conflict on an input. With one table, 0 is fine. Leave Options at their defaults.

At BeginPlay, remembering the Subsystem and enabling IMC_PlayerControls

3. Receive the input and display it

Search IA_Interact in the event graph and add the Enhanced Input Action event. Connect Started on its right to a Print String's white exec input, with In String Interact and Duration 2.

Calling Print String from IA_Interact's Started to display once at the start of the press

Compile, save, Play, and click the game view. Text appearing once per E press means success. The IA, key assignment, table activation, and receiving event are now connected.

Holding the same key does not keep adding displays from Started. With this input, we receive the first press only.

Add WASD movement and jumping

With the input path in place, stop Play and add movement and jumping to the same control table.

1. Decide the value types

Create the following new Input Actions in the InputPractice folder. We use practice names to distinguish them from the template's IA_Move and IA_Jump.

NameValue TypeWhat it receives
IA_PracticeMoveAxis2D (Vector2D)Two numbers, left-right and forward-back
IA_PracticeJumpDigital (Bool)Pressed or not pressed

Here we use movement input's X for left-right and Y for forward-back. That is a convention for how we use the input value, separate from UE's world coordinate axes. Later we connect "which direction's amount this input feeds."

2. Align WASD values into left-right and forward-back

Add the following key assignments to IMC_PlayerControls. Leave the IA-side Modifiers and Triggers empty; configure the following on each key row's Modifiers in the IMC.

Input ActionKeyModifiers (top to bottom)Input when one key is pressed
IA_PracticeMoveDNoneX=1 / Y=0
IA_PracticeMoveANegateX=-1 / Y=0
IA_PracticeMoveWSwizzle Input Axis Values: Order=YXZX=0 / Y=1
IA_PracticeMoveSSwizzle Input Axis Values: Order=YXZ → NegateX=0 / Y=-1
IA_PracticeJumpSpace BarNonetrue

Leave Negate with X/Y/Z all on. A has its value on X and the swizzled S on Y, so each one's sign flips.

Why do only W and S need a Swizzle? Because a single keyboard key emits 1 on X before any processing. Press W and the initial value is (1, 0). As is, that would be input to the right under our convention.

Swizzle moves the X value W emits onto Y for use as forward input, and Negate flips the sign

Swizzle Input Axis Values' YXZ swaps X and Y. (1, 0) becomes (0, 1), letting W act as forward input. S then gets Negate to become (0, -1), which is backward. A stays on the left-right axis and only needs its sign changed.

3. Connect input values to movement

Add the IA_PracticeMove event in BP_InputPractice. Create Break Vector2D from Action Value to split the input into X and Y.

Passing Action Value into Break Vector2D and splitting it into left-right X and forward-back Y

Place two Add Movement Input nodes, leaving Target as self on both. Self is the BP_InputPractice that owns this graph.

NodeWorld DirectionScale Value
Add Movement Input for forward-backX=1 / Y=0 / Z=0Break Vector2D's Y
Add Movement Input for left-rightX=0 / Y=1 / Z=0Break Vector2D's X

World Direction is the direction in world space you want to move. Scale Value is the amount of input applied in that direction, with negatives reversing it. Character Movement uses this input to handle walking.

Connect the white lines in the order IA_PracticeMove's Triggered → forward-back → left-right. Action Value to Break, and Break's X/Y to Scale Value, are value connections. No white line runs through Break.

Passing input Y to the forward-back Scale Value and X to the left-right one, and calling the two movement inputs in order on the white line

Compile and Play, and confirm W/S move forward and back and A/D move left and right. Since we fixed the camera direction, the forward and lateral reference does not change either.

Input value and movement speed are separate. Speed is tuned by Character Movement's Max Walk Speed and the like. To move on to sprinting and air control, the Character Movement article is the next entry point.

4. Connect jumping

Place IA_PracticeJump's event and connect Started to Jump and Completed to Stop Jumping. Target is self on both. We do not use Triggered here.

Connecting Started to Jump's exec input and Completed to Stop Jumping's

In Class Defaults, set Jump Max Count to 1 and Jump Max Hold Time to 0. Success is jumping once per Space press and returning to the floor. Stop Jumping conveys the end of jump input; it is not an instruction to land immediately in mid-air.

Choosing between Started and Triggered

With the basic input we just built, with no Trigger attached, they are used like this.

OutputBehavior with this inputLogic connected
StartedOnce at the start of the pressDisplay, start jump
TriggeredEvery frame while pressed and emitting valueMovement input
CompletedWhen the input endsEnd of jump input

Temporarily switch the movement lines to Started and holding the key adds movement input only at the first instant. Once confirmed, switch back to Triggered and re-confirm you walk while holding.

That said, whether Triggered fires every frame depends on the Trigger you attached. And Started signals "the condition began being evaluated." We try the difference with a hold attached next.

Sponsored

Change it to an input that fires on hold

Let's change the E key display we made first so it appears after a one-second hold. It is the foundation for inputs like opening a treasure chest or confirming without misclicks.

Stop Play and open the E row assigned to IA_Interact in IMC_PlayerControls. Add one Hold to Triggers.

  • Hold Time Threshold: 1.0
  • Is One Shot: on

Hold Time Threshold is the seconds you must keep holding for it to complete. Turning Is One Shot on emits Triggered exactly once when the hold completes. Off, it can keep emitting while held after completion.

Back in BP_InputPractice, move the line from IA_Interact to Print String from Started to Triggered. Change the display text to Hold OK.

Switching from the initial display on Started to a display on Triggered when the Hold completes

Play and press and release E quickly. This time nothing appears. Hold E for over a second and Hold OK appearing once means success. Release and press again and it appears a second later once more.

Left on Started, it displays the moment the hold begins being evaluated. "Started pressing" and "the hold completed" are different moments, so change the receiving pin to match.

There are also Ongoing and Canceled outputs. With Hold, Ongoing signals the wait toward completion and Canceled signals releasing before reaching the time. You can use them to show a waiting display and clear it when you abort. Combinations of finer states can wait until you need them.

Trigger is the condition, Modifier is the value transform

The Swizzle and Negate used for WASD are Modifiers that transform the input value. Hold is a Trigger deciding when the input completes.

SettingWhat it changesWhen you use it
Hold (Trigger)Completes after holding a set timeConfirm with a hold
Pressed (Trigger)Completes only at the moment of the pressCall one-shot logic from Triggered
Tap (Trigger)Completes on a short press and releaseTap controls
Chorded Action (Trigger)Conditions on another specified IA being activeControls used while holding another button
Scalar (Modifier)Multiplies the value by a specified factorLook sensitivity
Dead Zone (Modifier)Ignores small inputsCountering slight stick drift

Multiply look input by 0.5 with a Scalar, for instance, and the same physical motion turns you half as far. Stick tuning is covered in the gamepad support article.

Hands-On: swapping control tables between walking and driving

Next we switch what the same W key means. We build up to pressing F stopping walk input and instead displaying the accelerator value. We add no vehicle model or driving logic and confirm the result of the control table being swapped.

Removing the walking control table and adding the driving one changes what the same W key means

1. Prepare the driving and shared control tables

Add the following Input Actions and IMCs to the InputPractice folder.

KindNameSettings
Input ActionIA_AccelerateAxis1D (Float)
Input ActionIA_SteerAxis1D (Float)
Input ActionIA_ToggleVehicleDigital (Bool)
Input Mapping ContextIMC_DrivingW/S on IA_Accelerate, D/A on IA_Steer
Input Mapping ContextIMC_CommonF on IA_ToggleVehicle

Axis1D is the type receiving one number. For driving, leave W and D unprocessed and add Negate to S and A. No Swizzle is needed. Leave Triggers empty on all these IAs.

Put F in IMC_Common, which survives switching. That way you can still receive the key that returns you even after removing the walking or driving table.

Keeping the shared F key while swapping only the walking and driving control tables

On BP_InputPractice's BeginPlay side, connect another Add Mapping Context after the one adding IMC_PlayerControls. Target is the same Get InputSubsystem, Mapping Context is IMC_Common, and Priority is 0. At startup, two tables — walking plus shared — are active.

2. Make the swapped controls confirmable through display

Add IA_Accelerate and IA_Steer events to BP_InputPractice. Call a separate Print String from each one's Triggered.

Connecting Action Value directly to In String inserts a number-to-text conversion automatically. To tell them apart, place an Append along the value line with A set to Accel: or Steer: and B taking Action Value. Append joins two strings. The number goes through the text conversion into B, and Append's Return Value connects to Print String's In String.

Set the display Duration to 0. That checks the value only while Triggered fires each frame.

Converting the accelerator value to text and joining it with an Accel label for display
InputDisplay
WAccel: 1
SAccel: -1
DSteer: 1
ASteer: -1

Since IMC_Driving is not enabled yet, no driving display appears at this stage.

3. Swap only the movement table when F is pressed

Create a Boolean variable bIsDriving in BP_InputPractice with default false. It remembers which control mode you are in.

Place IA_ToggleVehicle's event and connect Started to a Branch. Pass Get bIsDriving to the Condition.

Checking the current bIsDriving and switching to driving when False and to walking when True
Branch pathThe order called on the white exec line
False: now walkingRemove Mapping Context (IMC_PlayerControls) → Add Mapping Context (IMC_Driving) → Set bIsDriving=true
True: now drivingRemove Mapping Context (IMC_Driving) → Add Mapping Context (IMC_PlayerControls) → Set bIsDriving=false

Target on all four Remove and Add nodes is Get InputSubsystem. Add's Priority is 0 and Options stay at their defaults. Do not remove IMC_Common. Only the walking and driving tables are swapped.

The False side removes the walking table and adds the driving one. Connect the blue Subsystem line to both nodes' Targets.

From Branch's False, removing the walking table, adding the driving one, then setting bIsDriving to true

The True side is the reverse. Remove the driving table, add the walking one, and set bIsDriving back to false.

From Branch's True, removing the driving table, adding the walking one, then setting bIsDriving to false

4. Walk, watch the values, walk again

Compile, save, Play, and click the game view. When switching, release WASD first, and press and release F before trying the next input.

  1. At first you can walk with WASD. No Accel or Steer appears
  2. Press and release F once. Now pressing W shows Accel: 1 instead of walking
  3. Confirm negative values and Steer with S/A/D too
  4. Press and release F once more. WASD walks again

You switched which controls are received without adding a "do not walk while driving" Branch inside the movement event. The Branch deciding what to switch to is gathered on F's event side.

Sponsored

Common Pitfalls

First, separate "the input event was never called" from "it was called but the movement is wrong." Calling a Print String from an IA event confirms where input arrived.

SymptomWhere to check
Even the first E prints nothingDid you click the game view? Are GameMode Override and Default Pawn Class the practice ones? Did you Add IMC_PlayerControls?
The template mannequin movesWhether this level's GameMode is the practice one
Add Mapping Context errorsWhether Target is the fetched InputSubsystem rather than the Player Controller. Whether Get Player Controller's Index is 0
W moves rightWhether W's Swizzle is YXZ. Whether input Y goes to the forward-back Scale Value
S moves forwardWhether Negate follows S's Swizzle and Y's inversion is on
It barely moves while heldWhether movement is connected to Started
E's hold fires immediatelyWhether you moved Print String from Started to Triggered
It fires repeatedly after the holdWhether Hold's Is One Shot is on
You cannot get back after switching to drivingWhether you Added IMC_Common at startup. Whether you removed it when switching
After F you neither walk nor see valuesCheck IMC_Driving's key assignments, the Add running, and the driving IAs' Prints

If you cannot find the input nodes, check that Enhanced Input is enabled under "Edit" → "Plugins." On an older existing project, also check that Default Player Input Class under Project Settings' Input is EnhancedPlayerInput and Default Input Component Class is EnhancedInputComponent.

Ways to display values are collected in the Print String article. If you rerouted movement to a Print for checking, restore the lines to Add Movement Input when done.

Bonus: Good to Know Up Front

  • Event names and physical key actions are not always one-to-one: Started is evaluation beginning; Triggered is condition completion. Completed does not necessarily mean the instant a key was released for every Trigger either. When you add Hold or Tap, also check the event you receive.
  • Priority does not switch off a whole table: even where a higher-priority table wins, non-conflicting keys can survive. Whether the same key passes to a lower operation also involves input consumption settings. To disable walking, make the intent explicit by removing the target IMC as we did.
  • When switching while a key is held: Add and Remove's Options include a setting to ignore held keys until they are released. Tune it to whether you want the new control to fire at the moment of switching or want the key re-pressed.
  • Extending to key config: changing assignments in game and saving and restoring them continues in the rebinding article. Keep rewriting an IMC in the editor separate from saving the player's settings.
  • Binding IAs to logic in C++ too: bind to the Enhanced Input Component with BindAction inside SetupPlayerInputComponent, registering the IA, the event you want, and the function to call. It corresponds to connecting a line from the Blueprint event. The entry point from Blueprint into C++ is also a useful reference.
  • When extending to multiple players: we practiced with one local player at Player Index 0. For split screen and networking, extend the structure to use the Controller and Subsystem belonging to the player being configured.

Summary

The IA is what you want to do, the IMC is the key assignment, and the Subsystem manages which table is in use. Blueprint drives the game using the arriving IA's event and value.

Print something with one key first, then connect movement. Trying holds and table swaps afterwards makes it easy to follow "what did I change to make the behavior change." In your own game too, start by separating controls you keep shared from controls you swap per situation.

References: Epic's Enhanced Input overview, Hold settings, Add Movement Input, Options when changing control tables.

Unreal Engine Notes in this section98