Binding Input in C++ with Unreal Engine 4

March 6, 2020

Player input is what separates video games from television - it allows the viewer to interact with their experience. In most games (though not all), player input is essential to progression. In this article, we will discuss the various places that you can react to input in the Unreal Engine 4 application lifecycle, using only C++.

For more information on the input handling framework in Unreal Engine 4, check out this article.

Input Component

Regardless of where you choose to react to input (i.e. in an Actor, Pawn, or PlayerController), all input processing goes through an UInputComponent. Every Actor possesses an Input Component (though it is not always initialized), and the way to bind to inputs once you have the Component is nearly identical for each class. Once you know how to do this, it is just a matter of finding the right Input Component for your desired reactor.

So the first thing we'll do is work through how to bind to events with an Input Component. There are two types of possible bindings: Actions and Axes.

Input Action

Bind to an Action Input using the BindAction() function on UInputComponent. It takes an input identifier name, an event type, a reacting object, and a callback function.

The callback function should return void and take 0 parameters.

void JumpCallback();

The binding looks like this:

// If we have a valid InputComponent...
if (InputComponent)
{
    // Bind an action to it
    InputComponent->BindAction
    (
        "Jump", // The input identifier (specified in DefaultInput.ini)
        IE_Pressed, // React when button pressed (or on release, etc., if desired)
        this, // The object instance that is going to react to the input
        &ACustomActor::JumpCallback // The function that will fire when input is received
    ); 
}

Input Axis

Bind to an Axis Input using the BindAxis function on UInputComponent. It takes an input identifier name, a reacting object, and a callback function.

The callback function should return void and take a float parameter.

void MoveForwardCallback(float AxisValue);

The binding looks like this:

// If we have a valid InputComponent...
if (InputComponent)
{
    // Bind an action to it
    InputComponent->BindAxis
    (
        "MoveForward", // The input binding (specified in DefaultInput.ini)
        this, // The object that is going to react to the input
        &ACustomActor::MoveForwardCallback // The function that will fire when input is received
    );
}

Binding Specifics

For each of the following examples, we will implement the callback functions in the reacting class (although you could implement it locally in source, globally, or statically, in more advanced use cases). So if we are binding in a PlayerController, we define the callbacks in the custom Player Controller class. We leave implementation of the callbacks up to you.

Challenge: How would you make your character actually jump once it receives a "Jump" input?

Player Controller Implementation

The most common place to handle input is in a Player Controller. The Player Controller is intended to be the interface between the player's intent and the game world. It also happens to be the easiest place to bind to input, which makes sense in light of this design choice.

Override the SetupInputComponent() function in your custom Player Controller class. This function gets called automatically when the level starts. At this stage, the Player Controller already has a valid Input Component, so you can jump right into binding.

UCLASS()
class AUnrealisticPlayerController : public APlayerController 
{

	GENERATED_BODY()

public:
    // ~Overrides: APlayerController
	virtual void SetupInputComponent() override;

    // Declare your callbacks here
    // MoveForward(); MoveLateral(); Jump();
};
void AUnrealisticPlayerController::SetupInputComponent() 
{
    // Always call this.
    Super::SetupInputComponent();

    // This is initialized on startup, you can go straight to binding
    InputComponent->BindAction("Jump", IE_Pressed, this, &AUnrealisticPlayerController::Jump);
    InputComponent->BindAxis("MoveForward", this, &AUnrealisticPlayerController::MoveForward);
    InputComponent->BindAxis("MoveLateral", this, &AUnrealisticPlayerController::MoveLateral); 
}

Pawn Implementation

The next best place to bind to input is in the Pawn class. It can be convenient to keep common inputs in a central location like the Player Controller (think non-gameplay menus), but have character-specific inputs in a Pawn (think MOBA-style input diversity).

Override the SetupPlayerInputComponent(UInputComponent* InputComponent) function in your custom APawn class. Just like in the Player Controller, this function gets called automatically when the Pawn is possessed. Use the passed in Input Component, which belongs to the possessing Player Controller, to bind to input.

UCLASS()
class AUnrealisticPawn : public APawn 
{

	GENERATED_BODY()

public:
    // ~Overrides: APawn
	virtual void SetupPlayerInputComponent(UInputComponent* InputComponent) override;

    // Declare your callbacks here
    // MoveForward(); MoveLateral(); Jump();
};
void AUnrealisticPawn::SetupPlayerInputComponent(UInputComponent* InputComponent)
{
    // Always call this.
    Super::SetupPlayerInputComponent(InputComponent);

    // This component belongs to the possessing Player Controller
    InputComponent->BindAction("Jump", IE_Pressed, this, &AUnrealisticPawn::Jump);
    InputComponent->BindAxis("MoveForward", this, &AUnrealisticPawn::MoveForward);
    InputComponent->BindAxis("MoveLateral", this, &AUnrealisticPawn::MoveLateral);
}

Actor Implementation

Actor input binding is the most complicated of the bunch. If you are handling input in an Actor, consider upgrading it to a Pawn instead.

There is no dedicated function for input binding in the Actor class, so we will make our own called BindToInput() and call it in BeginPlay() - though, you could choose to bind to input sometime after an Actor spawns, if that makes more sense for your game. For example: Maybe you want a lockbox to start accepting input only once you hover your cursor over it.

In addition, we are going to initialize the Actor's internal Input Component to bind to. Finally, we have to register our Actor with a Player Controller to start receiving input from it.

UCLASS()
class AUnrealisticActor: public AActor 
{

	GENERATED_BODY()

public:

  virtual void BeginPlay() override;
  void BindToInput();

  // Declare your callbacks here
  // MoveForward(); MoveLateral(); Jump();
};
void AUnrealisticActor::BeginPlay() 
{
    Super::BeginPlay();

    // This is not automatically called. Call it yourself here.
    BindToInput();
}

void AUnrealisticActor::BindToInput() 
{
    // Initialize our component
    InputComponent = NewObject<UInputComponent>(this);
    InputComponent->RegisterComponent();
    if (InputComponent)
    {
        // Bind inputs here
        // InputComponent->BindAction("Jump", IE_Pressed, this, &AUnrealisticPawn::Jump);
        // etc...

        // Now hook up our InputComponent to one in a Player
        // Controller, so that input flows down to us
        EnableInput(GetWorld()->GetFirstPlayerController());
    }    
}

Input Hierarchy

Unreal Engine 4 uses an input stack to manage input. This means that some entities will get the chance to process, and optionally consume, input before others. The hierarchy looks like this, from highest priority to lowest:

  • Actors (in order of latest enabled)
  • PlayerControllers
  • LevelScripts
  • Pawns

If you run into input handling issues while debugging, think about where your handlers are in the hierarchy, and consider whether your inputs are getting processed further up the chain.

Input consumption in the natural world

Consuming Input

One of the core features of an input stack is the ability to consume input. When input is consumed by an entity higher up the chain, the input never reaches entities further down the chain. For example, if a Player Controller handles and then consumes a Jump input, a Pawn in the same input stack will never hear about the input.

By default, input is set to be consumed. It's easy to change that behavior though. We just have to catch the return value of the input binding function that we used above, a struct, and change one of its properties.

{
    // Inside an input staging function, i.e. SetupInputComponent()

    // Make sure this is a reference - '&'. We have to change the original binding, not a copy.
    FInputActionBinding& NewBinding = InputComponent->BindAction("Jump", IE_Pressed, this, AUnrealisticPlayerController::JumpCallback);
    NewBinding.bConsumeInput = false;
}

The BindAction() function returns a reference to a FInputActionBinding object. We hang on to that reference, and change the value of its member bConsumeInput to false. Now our input can pass to the next entity that cares about it without being consumed.

For more information, check out the official documentation on input components and input binding.

Now you know how to bind to input identifiers that are statically defined (i.e. by you, when the game is developed). But what if you want your users to be able to change their keybindings in-game? Find out how to dynamically rebind input mapping here.



© 2021 Mustafa Moiz.