Assigning multiple gamepads to PIE instances

Hi,

For dev testing purpose, we are trying to force-assign gamepads to multiple instances of PIE on one machine - not local split screen multiplayer but actual multiplayer in several windows - so that we can play test on one machine with several users at the same time. I was able to do that with the following approach in the Controller’s BeginPlay:

if (IsLocalController())
{
  TArray<FInputDeviceId> InputDevices;
  IPlatformInputDeviceMapper::Get().GetAllConnectedInputDevices(InputDevices);
  const int32 PIEIndex = GEngine->GetWorldContextFromWorld(GetWorld())->PIEInstance;
  const UInputDeviceSubsystem* InputDeviceSubsystem = UInputDeviceSubsystem::Get();
  int32 NextGamePadIndex = 0;
  for (const FInputDeviceId& InputDevice : InputDevices)
  {
    const FHardwareDeviceIdentifier DeviceIdentifier = InputDeviceSubsystem->GetInputDeviceHardwareIdentifier(InputDevice);
    if (DeviceIdentifier.IsValid() && DeviceIdentifier.PrimaryDeviceType == EHardwareDevicePrimaryType::Gamepad)
    {
      if (PIEIndex == NextGamePadIndex)
      {
        const FPlatformUserId PlatformUserId = IPlatformInputDeviceMapper::Get().GetUserForInputDevice(InputDevice);
        UGameplayStatics::SetPlayerPlatformUserId(this, PlatformUserId);
        break;
      }
      NextGamePadIndex++;
    }
  }
}

However this prevents the KBM to be used in the window that has focus, which is not convenient if only for console commands. Is there a way to force the assignation of gamepads without preventing the KBM to replace the gamepad in the focused window?

Thanks,

Ben

Hi [mention removed]​,

There is existing logic in UGameViewportClient::InputAxis and UGameViewportClient::InputKey that routes input events (particularly gamepad events) to another viewport client when multiple PIE windows are running. By default, this only handles one gamepad always routes it to the second player/viewport.

Building on that, you can override these methods in a custom UGameViewportClient class to explicitly route both keyboard/mouse and gamepad input to the correct PIE instance.

First, consider the following multiplayer options:

  • Net Mode is set to Client
  • Number of clients equals the number of gamepads
  • Run Under One Process set to True

Then create a subclass of UGameViewportClient (for example, UPIEGameViewportClient) and override InputAxis and InputKey.

Each override should:

  • Calculate the controller ID based on the PIE index (PIE 1 = Controller 0, PIE 2 = Controller 1, etc.).
  • For KBM input, update the InputDevice ID so that the correct PIE window receives it (since KBM uses device ID 0 by default).
  • Route gamepad input events to the proper PIE viewport, similar to how Unreal’s internal routing to the second viewport works.

Additionally, in the controller’s BeginPlay assign a unique Platform User ID to each PIE instance.

void AMultiPadCasePlayerController::BeginPlay()
{
	Super::BeginPlay();
 
    if (!IsLocalController()) return;
 
    const FWorldContext* WC = GEngine->GetWorldContextFromWorld(GetWorld());
    if (!WC) return;
 
    const int32 PIEIndex = WC->PIEInstance;
 
    if (PIEIndex > 1) {
        FPlatformUserId NewUser = FPlatformUserId::CreateFromInternalId(PIEIndex-1);
        UGameplayStatics::SetPlayerPlatformUserId(this, NewUser);
    }
    
    int32 ControllerId = GetLocalPlayer()->GetControllerId();
    FPlatformUserId PlatformUserId = GetLocalPlayer()->GetPlatformUserId();
    UE_LOG(LogTemp, Log, TEXT("PIE %d -> PlatformUser %d, ControllerId %d"),
        PIEIndex, PlatformUserId.GetInternalId(), ControllerId);
}

The following code contains the declaration/header of the UPIEGameViewportClient class:

#pragma once
 
#include "CoreMinimal.h"
#include "Engine/GameViewportClient.h"
#include "PIEGameViewportClient.generated.h"
 
/**
 * 
 */
UCLASS()
class MULTIPADCASE_API UPIEGameViewportClient : public UGameViewportClient
{
	GENERATED_BODY()
 
public:
 
	virtual bool InputAxis(const FInputKeyEventArgs& EventArgs) override;
 
	virtual bool InputKey(const FInputKeyEventArgs& EventArgs) override;
 
	UGameViewportClient* GetPIEViewportByIndex(int32 PIEIndex);
 
	void RemapInputToPIE(FInputKeyEventArgs& InOutKeyEvent);
};

The attached implementation of UPIEGameViewportClient handles the routing logic and device remapping. I tested it with the Third Person Template using three PIE clients and three gamepads, and each window received input from its own gamepad while KBM worked in the focused window.

Please let me know if this helps your case.

Best,

Francisco

Hi Francisco! Yeah that works with a little bit of adaptation to our case (assigning a gamepad to PIE 0, supporting PIE instances with higher counts than the number of gamepads, a bit of robustness in cases where gamepads are not device id 1+ in order, and a fix for a typo in InputKey which was redispatching to InputAxis instead).

That’s great, thanks a lot for the support!

Ben

Hello [mention removed]​,

Thanks for confirming and sorry about the typo! I’m glad the solution worked and you were able to adapt it to your projectr. I’ll go ahead and close this case now.

Feel free to open a new one if anything else comes up.

Best,

Francisco