Epic deprecated:
ENGINE_API virtual bool InputAxis(FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples=1, bool bGamepad=false) override;
In favor of:
ENGINE_API virtual bool InputAxis(FViewport* Viewport, FInputDeviceId InputDevice, FKey Key, float Delta, float DeltaTime, int32 NumSamples = 1, bool bGamepad = false) override;
Notice the lack of a ControllerId? Yep…
However, they have provided a RemapControllerInput(EventArgs);
you can override to increment the ControllerId
as needed.
And they have implemented the option to do it for you via bOffsetPlayerGamepadIds
which appears in project settings as Skip Assigning Gamepad to Player 1
.
However, later in the code it calls:
ULocalPlayer* const TargetPlayer = GEngine->GetLocalPlayerFromInputDevice(this, EventArgs.InputDevice);
if (TargetPlayer && TargetPlayer->PlayerController)
{
bResult = TargetPlayer->PlayerController->InputKey(FInputKeyParams(EventArgs.Key, (double)Delta, DeltaTime, NumSamples, EventArgs.IsGamepad(), EventArgs.InputDevice));
}
Its using the wrong function, the ControllerId
is never used, it needs to use GetLocalPlayerFromControllerId
for this to work as intended (or work at all!).
Sloppy work. I looked at the 5.4 source code and it still isn’t fixed in the next release either.
But of course you could override InputAxis
, copy/paste it, and fix it – but nope, those input events are private so you cannot do that.
HOWEVER, UGameViewportClient::OnOverrideInputAxisEvent
will allow you to implement your own behaviour.
Talk about misusing virtual functions, Epic!
This was my first attempt, but its incorrect:
void UMyGameViewportClient::Init(FWorldContext& WorldContext, UGameInstance* OwningGameInstance,
bool bCreateNewAudioDevice)
{
Super::Init(WorldContext, OwningGameInstance, bCreateNewAudioDevice);
OnOverrideInputAxis().BindUObject(this, &ThisClass::OnOverrideInputAxisHandler);
}
bool UMyGameViewportClient::OnOverrideInputAxisHandler(FInputKeyEventArgs& EventArgs, float& Delta, float& DeltaTime,
int32& NumSamples)
{
const ULocalPlayer* const TargetPlayer = GEngine->GetLocalPlayerFromControllerId(this, EventArgs.ControllerId);
if (TargetPlayer && TargetPlayer->PlayerController)
{
TargetPlayer->PlayerController->InputKey(FInputKeyParams(EventArgs.Key, (double)Delta, DeltaTime, NumSamples, EventArgs.IsGamepad(), EventArgs.InputDevice));
}
// If we failed here, don't let it route back to player 0
return true;
}
The Player 2 Id is not always local player + 1, if you remove/add players that number keeps going up, even if you only have a total of 2, the first might be 0 and after a few remove/add the second might be 13!
We need to factor that in:
#include "System/MyGameViewportClient.h"
#include "GameFramework/PlayerInput.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MyGameViewportClient)
void UMyGameViewportClient::Init(FWorldContext& WorldContext, UGameInstance* OwningGameInstance,
bool bCreateNewAudioDevice)
{
Super::Init(WorldContext, OwningGameInstance, bCreateNewAudioDevice);
OnOverrideInputAxis().BindUObject(this, &ThisClass::OnOverrideInputAxisHandler);
OnOverrideInputKey().BindUObject(this, &ThisClass::OnOverrideInputKeyHandler);
}
APlayerController* UMyGameViewportClient::GetPlayerTwoController(const FInputDeviceId& InputDevice) const
{
const ULocalPlayer* const PlayerOne = GEngine->GetLocalPlayerFromInputDevice(this, InputDevice);
const APlayerController* PlayerOneController = PlayerOne->PlayerController;
TArray<APlayerController*> PlayerControllers;
GEngine->GetAllLocalPlayerControllers(PlayerControllers);
if (PlayerControllers.Num() != 2)
{
return nullptr;
}
return (PlayerControllers[0] == PlayerOneController) ? PlayerControllers[1] : PlayerControllers[0];
}
bool UMyGameViewportClient::OnOverrideInputAxisHandler(FInputKeyEventArgs& EventArgs, float& Delta, float& DeltaTime,
int32& NumSamples)
{
if (!EventArgs.IsGamepad()) { return false; }
if (APlayerController* PlayerController = GetPlayerTwoController(EventArgs.InputDevice))
{
PlayerController->InputKey(FInputKeyParams(EventArgs.Key, (double)Delta, DeltaTime, NumSamples, EventArgs.IsGamepad(), EventArgs.InputDevice));
}
// If we failed here, don't let it route back to player 0
return true;
}
bool UMyGameViewportClient::OnOverrideInputKeyHandler(FInputKeyEventArgs& EventArgs)
{
if (!EventArgs.IsGamepad()) { return false; }
if (APlayerController* PlayerController = GetPlayerTwoController(EventArgs.InputDevice))
{
PlayerController->InputKey(FInputKeyParams(EventArgs.Key, EventArgs.Event, static_cast<double>(EventArgs.AmountDepressed), EventArgs.IsGamepad(), EventArgs.InputDevice));
}
// If we failed here, don't let it route back to player 0
return true;
}
However, it still didn’t work, and that was because I was missing the setting bFilterInputByPlatformUser
(you can find this in project settings, DISABLE it! It blocks any input that comes from a device that isn’t registered to the player. And of course, the engine doesn’t draw any link between these settings.
Now it works.