How to set up Enhanced Input for APlayerController in C++?

You are missing call for parent function in SetupInputComponent. Super::SetupInputComponent();

2 Likes

I wanted to do the same thing as you. This is the recommended way of handling input for some games. It’s a perfectly valid question and you are not wanting to do anything the wrong way or an odd way.

Also, in some cases, putting input handling or other functionality into the PlayerController is necessary. The PlayerController persists throughout the game, while the Pawn can be transient.

Even though it may no longer be of any interest to OP, it could help someone else who is interested in an answer to this question. One possible solution is to create a new C++ project with the third-person template. Then take a look at the generated Character class. Now move the relevant calls to your player controller class.

For a simple setup, this would be:

  • SetupPlayerInputComponent (see below)
  • DefaultMappingContext, MoveAction and LookAction properties
  • Move and Look methods
  • Copy/Paste the “Add Input Mapping Context” code from BeginPlay, modifying as needed to work inside player controller.
void AMyPlayerController::::Move(const FInputActionValue& Value)
{
	// input is a Vector2D
	FVector2D MovementVector = Value.Get<FVector2D>();

	// find out which way is forward
	const FRotator Rotation = GetControlRotation();
	const FRotator YawRotation(0, Rotation.Yaw, 0);

	// get forward vector
	const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);

	// get right vector 
	const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);

	// add movement 
	APawn* pawn = GetPawn();
	pawn->AddMovementInput(ForwardDirection, MovementVector.Y);
	pawn->AddMovementInput(RightDirection, MovementVector.X);
}

void AMyPlayerController::::Look(const FInputActionValue& Value)
{
	// input is a Vector2D
	FVector2D LookAxisVector = Value.Get<FVector2D>();

	// add yaw and pitch input to controller
	AddYawInput(LookAxisVector.X);
	AddPitchInput(LookAxisVector.Y);
}

void AMyPlayerController::::OnPossess(APawn* aPawn)
{
	Super::OnPossess(aPawn);
	// Call setup player input
	SetupPlayerInputComponent();
}

void AMyPlayerController::::SafeRetryClientRestart()
{
	Super::SafeRetryClientRestart();
	if (AcknowledgedPawn != GetPawn())
	{
		UWorld* World = GetWorld();
		check(World);

		SetupPlayerInputComponent();		
	}
}

void AMyPlayerController::StartFire(uint8 FireModeNum)
{
	Super::StartFire(FireModeNum);
	if (((IsInState(NAME_Spectating) && bPlayerIsWaiting) || IsInState(NAME_Inactive)) && !IsFrozen())
	{
		// Call setup player input
		SetupPlayerInputComponent();
	}
}

Regarding SetupPlayerInputComponent, this is not coming from AActor, but APawn. That’s why you can’t override it from the player controller. If you look at DefaultPawn.cpp you can see that this method configures bindings of input events to their event handlers.

Implement SetupPlayerInputComponent in your player controller and override StartFire, SafeRetryClientRestart and OnPossess, calling base class (super) implementation and then calling your new SetupPlayerInputComponent under the same conditions that would have been called from the respective APlayerController methods. Your PC SetupPlayerInputComponent method takes no arguments and instead uses the AActor::InputComponent property.

void AMyPlayerController::SetupPlayerInputComponent()
{
	// Set up action bindings
	if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(InputComponent)) {

		// Moving
		EnhancedInputComponent->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyPlayerController::Move);

		// Looking
		EnhancedInputComponent->BindAction(LookAction, ETriggerEvent::Triggered, this, &AMyPlayerController::Look);
	}
	else
	{
		MYGAME_LOG(nullptr, MyDebugLog, Error, false, TEXT("'%s' Failed to find an Enhanced Input component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this));
	}
}

Remember to setup DefaultMappingConntext and actions in your player controller blueprint.
Capture

I know, this is not a whole solution written out with full example code. However, it provides a thread to pull on to get this working. This is not the only way to implement this, but this approach will guarantee that it behaves similar to the ACharacter implementation. In case your wondering, I’ve implemented this in my own game, as described above, and it is working.

7 Likes

I too am trying to add the Enhanced Input via the Controller instead the Character (to allow that segregation).

Thanks @Rogue_Programmer for the help in solving the issue instead of a workaround!
Following your solution I couldn’t find in the base classes where ‘StartFire’, 'SafeRetryClientRestart ', and ‘OnPossess’, should be using ‘SetupPlayerInputComponent’, so I couldn’t follow the example.
Would love to know where I missed them :slight_smile:

BUT I did manage to make my inputs work just the way you are trying @Thalamus01.
Any chance (just like @Rogue_Programmer suggested), that you accidently didn’t set up the Mapping and Input in the Controller’s Blueprint?

This is my code in the controller:

void ATantrumPlayerController::BeginPlay()
{
	Super::BeginPlay();

	if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(GetLocalPlayer()))
	{
		Subsystem->AddMappingContext(MappingContext, 0);
	}
}

void ATantrumPlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();

	UEnhancedInputComponent* Input = Cast<UEnhancedInputComponent>(InputComponent);
	Input->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ATantrumPlayerController::RequestJumpAction);
	Input->BindAction(LookAction, ETriggerEvent::Triggered, this, &ATantrumPlayerController::RequestLookAction);
	Input->BindAction(MoveAction, ETriggerEvent::Triggered, this, &ATantrumPlayerController::RequestMoveAction);

}

And this is the Controller’s Blueprint inputs:
image

If it still doesn’t work please let us know, I would love to try and help further!

Edit: wanted to add that I’m using UE5.4, don’t know if that’s related in any way, but thought I’d mention it