UE5 Enhanced Input in Pure C++
Introduction
Most tutorials I’ve seen only cover Enhanced Input for blueprints, but in this post I’ll explain how to implement Enhanced Input in pure C++. We’ll create an input action and input mapping for WASD and Left Stick movement, then tie it all together in the player controller.
Enable Enhanced Input
In your Game.Build.cs file, add capability for Enhanced Input:
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput" });
Create classes
Launch the editor.
Go to Tools > New C++ Class
Click
All Classes
, selectInputAction
as the parent, and clickNext
.I named the class “IA_Move”.
- I also put this in an “Input” folder in my project. Note that you may have to remove “Input/” from the include path in the .cpp file for the project to build correctly.
Click
Create Class
.Create another class with
InputMappingContext
as the parent. I prefixed this one with “IMC_”.
Input action
All I did for the input action class was add a constructor to specify the ValueType
:
.h
public:
UIA_Move();
.cpp
#include "IA_Move.h"
UIA_Move::UIA_Move()
{
ValueType = EInputActionValueType::Axis2D;
}
We’ll use Axis2D
since it will work for all of our movement inputs.
Input mapping context
For the context, all we need are some private variables and another constructor.
.h
#pragma once
#include "CoreMinimal.h"
#include "InputMappingContext.h"
#include "IA_Move.h"
#include "IMC_Earth.generated.h"
UCLASS()
class EARTH_API UIMC_Earth : public UInputMappingContext
{
GENERATED_BODY()
private:
UIA_Move *Move;
UInputModifierNegate *Negate;
UInputModifierSwizzleAxis *Swizzle;
FEnhancedActionKeyMapping W;
FEnhancedActionKeyMapping A;
FEnhancedActionKeyMapping S;
FEnhancedActionKeyMapping D;
FEnhancedActionKeyMapping GamepadLeft2d;
public:
UIMC_Earth();
};
.cpp
#include "IMC_Earth.h"
#include "IA_Move.h"
UIMC_Earth::UIMC_Earth()
{
Move = NewObject<UIA_Move>();
Negate = NewObject<UInputModifierNegate>();
Swizzle = NewObject<UInputModifierSwizzleAxis>();
Swizzle->Order = EInputAxisSwizzle::YXZ;
W = FEnhancedActionKeyMapping(Move, EKeys::W);
W.Modifiers.Add(Swizzle);
Mappings.Add(W);
A = FEnhancedActionKeyMapping(Move, EKeys::A);
A.Modifiers.Add(Negate);
Mappings.Add(A);
S = FEnhancedActionKeyMapping(Move, EKeys::S);
S.Modifiers.Add(Negate);
S.Modifiers.Add(Swizzle);
Mappings.Add(S);
D = FEnhancedActionKeyMapping(Move, EKeys::D);
Mappings.Add(D);
GamepadLeft2d = FEnhancedActionKeyMapping(Move, EKeys::Gamepad_Left2D);
Mappings.Add(GamepadLeft2d);
}
To understand what Negate
and Swizzle
do, first take a look at the D
key, which has no modifiers. When D
is pressed, it will return a positive X-axis value of 1.0.
A
has only theNegate
modifier, which will flip positive 1.0 to negative 1.0.W
has only theSwizzle
modifier, which turns an X-axis value to a Y-axis value since we “swizzled” the axes fromXYZ
toYXZ
.S
will return a negative Y-axis value because it has both aNegate
and aSwizzle
.
Configure the player controller
You could also set this up in the Character or Pawn classes, but I like using the Controller for input.
.h
In your controller .h, add the following properties and methods:
public: UPROPERTY(EditAnywhere, Category = "Input") TSoftObjectPtr<UInputMappingContext> InputMapping; protected: virtual void SetupInputComponent() override; private: virtual void MoveCallback(const FInputActionInstance& Instance);
.cpp
It’s optional, but you can add this #define
at the top of your .cpp to conveniently print messages to the screen. We’ll use it to test if our callback works.
#define print(text) if (GEngine) GEngine->AddOnScreenDebugMessage(-1, 1.5, FColor::White, text)
Add the needed headers:
#include <EnhancedInputComponent.h> #include <EnhancedInputSubsystems.h>
In your controller’s constructor, initialize the InputMapping:
InputMapping = NewObject<UIMC_Earth>();
Add your input setup.
void AEarthPlayerController::SetupInputComponent() { print("setup input component called"); Super::SetupInputComponent(); if (ULocalPlayer* LocalPlayer = GetLocalPlayer()) { print("we have a local player"); if (UEnhancedInputLocalPlayerSubsystem* InputSystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>()) { print("we have an input system"); if (!InputMapping.IsNull()) { print("we have an input mapping"); InputSystem->AddMappingContext(InputMapping.LoadSynchronous(), 0); UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(InputComponent); FEnhancedActionKeyMapping mapping = InputMapping->GetMapping(0); Input->BindAction(mapping.Action, ETriggerEvent::Triggered, this, &AEarthPlayerController::MoveCallback); } } } }
Add the callback for your action.
void AEarthPlayerController::MoveCallback(const FInputActionInstance& Instance) { FVector2D Value = Instance.GetValue().Get<FVector2D>(); print(Value.ToString()); }
And that should do it. If you run the game, you should see the x/y values corresponding to your inputs printed to the screen.
A few more points
One important thing to note is I am getting the Action that is stored in the input mapping via InputMapping->GetMapping(0)
. If you simply create another Action
of the same type and try to feed that into BindAction
, it won’t work. It has to be the action stored in the mapping itself. We only had to get the first one because all our mappings reference the same action.
Also, in my case, I have a default game mode that specifies a default pawn and controller, and I have a Player Start actor sitting in my level. This means as soon as the game is launched, the controller will auto possess the pawn, and I will have a local player. Your case may be different, so just something to keep in mind.
We didn’t discuss triggers, but those are driven by UInputTrigger
and added the same way as modifiers.
I hope this post helps you add the rest of your inputs and actions, and good luck!
Support the blog! Buy a t-shirt or a mug!