Coding Babble  Code it once more, with feeling.

UE5 Enhanced Input in Pure C++

By Luke Simpson

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

  1. Launch the editor.

  2. Go to Tools > New C++ Class

  3. Click All Classes, select InputAction as the parent, and click Next.

  4. 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.
  5. Click Create Class.

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

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

  1. 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)
  1. Add the needed headers:

    #include <EnhancedInputComponent.h>
    #include <EnhancedInputSubsystems.h>
    
  2. In your controller’s constructor, initialize the InputMapping:

    InputMapping = NewObject<UIMC_Earth>();
    
  3. 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);
                }
            }
        }
    }
    
  4. 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!

Tags: