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.
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
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.

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 decide | Where to configure it |
|---|---|
| Create an operation called jump | Input Action |
| Make Space perform the jump | The IMC's key assignment |
| Use this control table right now | Add Mapping Context to the Subsystem |
| Actually jump when input arrives | The 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.
| Part | Parent | Settings |
|---|---|---|
| Body (Static Mesh) | Capsule Component | Standard Cube, Relative Location 0, Scale X=0.5 / Y=0.5 / Z=1.76 |
| SpringArm | Capsule Component | Relative Location Z=60, Rotation Pitch=-25, Yaw/Roll=0, Target Arm Length=400 |
| Camera | SpringArm | Relative 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.

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.

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.

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.

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.
| Name | Value Type | What it receives |
|---|---|---|
| IA_PracticeMove | Axis2D (Vector2D) | Two numbers, left-right and forward-back |
| IA_PracticeJump | Digital (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 Action | Key | Modifiers (top to bottom) | Input when one key is pressed |
|---|---|---|---|
| IA_PracticeMove | D | None | X=1 / Y=0 |
| IA_PracticeMove | A | Negate | X=-1 / Y=0 |
| IA_PracticeMove | W | Swizzle Input Axis Values: Order=YXZ | X=0 / Y=1 |
| IA_PracticeMove | S | Swizzle Input Axis Values: Order=YXZ → Negate | X=0 / Y=-1 |
| IA_PracticeJump | Space Bar | None | true |
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 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.

Place two Add Movement Input nodes, leaving Target as self on both. Self is the BP_InputPractice that owns this graph.
| Node | World Direction | Scale Value |
|---|---|---|
| Add Movement Input for forward-back | X=1 / Y=0 / Z=0 | Break Vector2D's Y |
| Add Movement Input for left-right | X=0 / Y=1 / Z=0 | Break 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.

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.

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.
| Output | Behavior with this input | Logic connected |
|---|---|---|
| Started | Once at the start of the press | Display, start jump |
| Triggered | Every frame while pressed and emitting value | Movement input |
| Completed | When the input ends | End 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.
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.

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.
| Setting | What it changes | When you use it |
|---|---|---|
| Hold (Trigger) | Completes after holding a set time | Confirm with a hold |
| Pressed (Trigger) | Completes only at the moment of the press | Call one-shot logic from Triggered |
| Tap (Trigger) | Completes on a short press and release | Tap controls |
| Chorded Action (Trigger) | Conditions on another specified IA being active | Controls used while holding another button |
| Scalar (Modifier) | Multiplies the value by a specified factor | Look sensitivity |
| Dead Zone (Modifier) | Ignores small inputs | Countering 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.

1. Prepare the driving and shared control tables
Add the following Input Actions and IMCs to the InputPractice folder.
| Kind | Name | Settings |
|---|---|---|
| Input Action | IA_Accelerate | Axis1D (Float) |
| Input Action | IA_Steer | Axis1D (Float) |
| Input Action | IA_ToggleVehicle | Digital (Bool) |
| Input Mapping Context | IMC_Driving | W/S on IA_Accelerate, D/A on IA_Steer |
| Input Mapping Context | IMC_Common | F 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.

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.

| Input | Display |
|---|---|
| W | Accel: 1 |
| S | Accel: -1 |
| D | Steer: 1 |
| A | Steer: -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.

| Branch path | The order called on the white exec line |
|---|---|
| False: now walking | Remove Mapping Context (IMC_PlayerControls) → Add Mapping Context (IMC_Driving) → Set bIsDriving=true |
| True: now driving | Remove 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.

The True side is the reverse. Remove the driving table, add the walking one, and set bIsDriving back 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.
- At first you can walk with WASD. No Accel or Steer appears
- Press and release F once. Now pressing W shows Accel: 1 instead of walking
- Confirm negative values and Steer with S/A/D too
- 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.
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.
| Symptom | Where to check |
|---|---|
| Even the first E prints nothing | Did you click the game view? Are GameMode Override and Default Pawn Class the practice ones? Did you Add IMC_PlayerControls? |
| The template mannequin moves | Whether this level's GameMode is the practice one |
| Add Mapping Context errors | Whether Target is the fetched InputSubsystem rather than the Player Controller. Whether Get Player Controller's Index is 0 |
| W moves right | Whether W's Swizzle is YXZ. Whether input Y goes to the forward-back Scale Value |
| S moves forward | Whether Negate follows S's Swizzle and Y's inversion is on |
| It barely moves while held | Whether movement is connected to Started |
| E's hold fires immediately | Whether you moved Print String from Started to Triggered |
| It fires repeatedly after the hold | Whether Hold's Is One Shot is on |
| You cannot get back after switching to driving | Whether you Added IMC_Common at startup. Whether you removed it when switching |
| After F you neither walk nor see values | Check 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
BindActioninsideSetupPlayerInputComponent, 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.