Using the EnhancedInput system in C++

A rundown of how to use the EnhancedInput system in C++.

Updated 9 months ago Edit Page Revisions

This tutorial is aimed at users of Unreal Engine 5 and its subsequent releases. In versions of UE4 that feature the plugin, it is marked as experimental and should thus not be used in shipped games!

Motivation

With its replacement of the old Input system in Unreal Engine 5, the EnhancedInput system has become the standard for new projects being created in UE5. Therefore, it is important for all developers to familiarize themselves with the new system, as player inputs are at the core of what distinguishes a game from other media formats (without inputs, you'd just be watching a movie!).
While the system is relatively easy to pick up and use thanks to the excellent documentation and plenty of tutorials on the Blueprint side, things are slightly more difficult when using the system in conjunction with C++.
This article aims to navigate the reader through some of the most common troubles when first using this system in C++.

Table of Contents

  • Setup
  • Adding a mapping context
  • Binding an action to a function
  • Binding many actions more elegantly

Setup

First and foremost, make sure that the EnhancedInput plugin is actually enabled by going to Edit > Plugins and then searching for "EnhancedInput" in the window that pops up. If you're using the latest version of UE5, the plugin will most definitely already be enabled by default.
Below is an example of what the enabled plugin looks like:

EnhancedInputPluginEnabled.png

Next up, you need to make sure that your player actor uses the EnhancedInputComponent for its input handling. To do so, go to Edit > Project Settings > Input, scroll down and make sure that the "Default Input Component Class" is set to EnhancedInputComponent like shown here:

EnhancedInputComponentSetting.png

Before you can use any of the EnhancedInput functionality on the C++ side, you need to add the EnhancedInput module to your project's [ProjectName].Build.cs file like so (with [ProjectName] obviously being your project's name):

PublicDependencyModuleNames.AddRange(new string[] {"EnhancedInput", "Core", "CoreUObject", "Engine", "InputCore"});

If you're getting weird linker errors when trying to use the classes the system provides, you probably forgot about this step!

And with that, you're ready to use the EnhancedInput system in C++!

Adding a mapping context

Just like in Blueprints, you need to add at least one mapping context to be able to use any Input Actions. To do that, you get a reference to the UEnhancedInputLocalPlayerSubsystem (what a mouthful!) and then use it to add a UInputMappingContext. It's a good practice to add a property to the player actor for the UInputMappingContext in the header (.h) file like so:

YourCharacter.h

/** MappingContext for player input. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "EnhancedInput")
UInputMappingContext* InputMapping;

Make sure to forward declare UInputMappingContext somewhere in your header file like so:

class UInputMappingContext;

This property can be properly filled out in the Blueprint version of your character. This way avoids hard-coding any references to asset names, which is very error-prone.

Here's how the Blueprint property could look like: MappingContextPropertyBP.png

Now head over to your source (.cpp) file. It's a good practice to add the mapping context inside void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent);:

YourCharacter.cpp

#include "EnhancedInputSubsystems.h"

void AYourCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    APlayerController* PlayerController = Cast<APlayerController>(GetController());

    UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer());
    
    Subsystem->ClearAllMappings();
    Subsystem->AddMappingContext(InputMapping, 0);
}

If you're adding multiple mapping contexts, be sure to set the priority (the second argument of AddMappingContext()) properly.

Binding an action to a function

With a mapping context now added, it is time to actually bind functions to our Input Actions. Unlike with Blueprints, there is more work involved than just typing the asset name and getting a handy node to use in C++.

Firstly, we need to get a reference to the EnhancedInputComponent. We will then call BindAction() on it to bind a function to a UInputAction of our choice. Just like with the UInputMappingContext before, we can also add this UInputAction to our header file and select an appropriate input action in the Blueprint of this character.

YourCharacter.h

class UInputAction;

UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
UInputAction* ShootAction;

// Adding the UFUNCTION() macro before the function you want to bind seems to be important, even if it's empty.
UFUNCTION()
void Shoot(const FInputActionValue& Value);

YourCharacter.cpp

#include "EnhancedInputComponent.h"

void AYourCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    // ...
    
    UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(PlayerInputComponent);
    Input->BindAction(ShootAction, ETriggerEvent::Triggered, this, &AYourCharacter::Shoot);
}

void AYourCharacter::Shoot(const FInputActionValue& Value)
{
    // You can get the action value like this:
    
    bool BoolValue = Value.Get<bool>(); // for digital input actions
    float FloatValue = Value.Get<float>(); // for Axis1D input actions
    FVector2D Axis2DValue = Value.Get<FVector2D>(); // for Axis2D input actions
    FVector VectorValue = Value.Get<FVector>(); // for Axis3D input actions
    
    // Cool stuff here!
}

For comparison, here's the same code in Blueprint: InputActionShootBP.png

Binding many actions more elegantly

While the way above works just fine, you might find that the number of input action properties on your character quickly balloons as you add more and more inputs. There are of course multiple ways to fix this, but I'll just provide one solution here: to use a data asset.
This asset will simply contain references to all input actions that you want to bind to functions. The player actor will only reference the data asset instead of all the input actions individually.

Here's one way to set a system like this up:

InputDataConfig.h

UCLASS()
class ARCTICRESEARCH_API UInputDataConfig : public UDataAsset
{
	GENERATED_BODY()
	
public:
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	UInputAction* Move;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	UInputAction* Look;

	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	UInputAction* Shoot;

	// You can add as many input actions as you want here!
};

YourCharacter.h

class UInputDataConfig;

/** Config for avaiable input actions - create custom InputDataConfig object to insert here. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "EnhancedInput")
UInputDataConfig* InputActions;

YourCharacter.cpp

#include "InputDataConfig.h" // Or whatever the path of your data asset is

void AYourCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    // ...

    UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(PlayerInputComponent);
    
    if(!InputActions) return; // Don't wanna bind to invalid UInputActions!
    
    Input->BindAction(InputActions->Move, ETriggerEvent::Triggered, this, &AYourCharacter::Move);
    Input->BindAction(InputActions->Look, ETriggerEvent::Triggered, this, &AYourCharacter::Look);
    Input->BindAction(InputActions->Shoot, ETriggerEvent::Triggered, this, &AYourCharacter::Shoot);
    // etc...
}

Afterward, don't forget to set the input action properties in a Blueprint version of your data asset: InputDataAssetBP.png

And make sure to add a valid data asset reference to your character Blueprint: DataAssetReferenceBP.png

Conclusion

And this is how you set up EnhancedInput in C++! As you can see, it is a relatively straightforward process, albeit a bit more complex than doing it in Blueprint! I hope this article has helped you out. If you have any further questions/feedback/suggestions etc., you can message me on the official Wiki Discord! My name there is "TheCreator".

Further Reading