Simply allow first gamepad to control Player 2

So in my game you can either select one player, or local 2 player.

If you select one player, the controller and the keyboard control the first person, and since there’s only one, that’s fine.

However, if you select two players, the controller and the still keyboard control the first person, and if you have another gamepad then the second one will control the second player.

I’d like to have it so that just the keyboard controls player 1 and the first gamepad controls player 2. Is this possible?

Bonus, detect the number of controllers and if there are two controllers and two gamepads then each gamepad controls a player, and the keyboard controls the first player.

Is there a fix to this?

There is no simple fix, but a workaround. You can create your own controller class and carry out the necessary operations in its event tick. However, your problems would not end there as you would need some c++ code to disable the split screen once a level is complete, otherwise UE4 splits the screen even when you open a new level or go back to the menu. It’s quite cumbersome but possible. It may be better to work for an official solution.

1 Like

By custom controller class you mean a custom player controller class?

Is there any way to duplicate my current player controller class (which isn’t currently accessible as a BP and work from there>?)

Also, what would I need to edit in the custom player controller class?

Would be very interested in this issue as well… :slight_smile:

You can do it by going into Project Settings > Maps & Modes > Skip Assigning Gamepad To Player 1. Set it to true and you are good to go.

1 Like

Hello there!
After searching a lot, I finally found a solution by talking to MordenTral (awesome dude and author of two amazing plugins). Basically, the clean solution to achieve this is to create a child class of UGameViewportClient which overrides InputKey(…) and InputAxis(…). In the overrides, you can reroute where certain inputs go and both functions have bool bGamepad as a parameter so you can basically use that to determine where you want to send the input. It is necessary to set the Game Viewport Client Class in Project Settings to be the newly created class. You can make this class blueprintable and expose an Enum UPROPERTY to Blueprint so that you can actually create a child blueprint class and can change behavior dynamically at runtime using blueprints (of course the Game Viewport Client Class in Project Settings would need to point to this blueprint class).

Here is my UCustomGameViewportClient class:

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameViewportClient.h"
#include "CustomGameViewportClient.generated.h"

/** This class introduces a new Input-PlayerController binding behaviour. Through the EGameInputMethod Enum it is possible to choose the default
* behavior (in which keyboard/mouse input and gamepad input are both binded to Player 1 [index 0]) or the Player1OnlyKeyboardAndMouse behavior 
* (in which keyboard/mouse input is binded to Player 1 [index 0] and gamepad input is shifted to the other players).
*/
UENUM(Blueprintable)
enum class EGameInputMethod : uint8
{
	GameInput_Default,
	GameInput_Player1OnlyKeyboardAndMouse,
};

UCLASS(Blueprintable)
class SIDESCROLLER_API UCustomGameViewportClient : public UGameViewportClient
{
	GENERATED_BODY()
	
public:

	// Input Method for the viewport
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Input")
		EGameInputMethod GameInputMethod;


	virtual bool InputKey(FViewport* tViewport, int32 ControllerId, FKey Key, EInputEvent EventType, float AmountDepressed = 1.f, bool bGamepad = false) override
	{
		if (GameInputMethod == EGameInputMethod::GameInput_Default)
			return Super::InputKey(tViewport, ControllerId, Key, EventType, AmountDepressed, bGamepad);

		if (GameInputMethod == EGameInputMethod::GameInput_Player1OnlyKeyboardAndMouse && bGamepad)
		{
			// shift gamepad input to controllers with +1 index
			++ControllerId;
			return Super::InputKey(tViewport, ControllerId, Key, EventType, AmountDepressed, bGamepad);
		}
		else 
		{
			return Super::InputKey(tViewport, ControllerId, Key, EventType, AmountDepressed, bGamepad);
		}
	}

	virtual bool InputAxis(FViewport* tViewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime, int32 NumSamples = 1, bool bGamepad = false) override
	{
		if (GameInputMethod == EGameInputMethod::GameInput_Default)
			return Super::InputAxis(tViewport, ControllerId, Key, Delta, DeltaTime, NumSamples, bGamepad);

		if (GameInputMethod == EGameInputMethod::GameInput_Player1OnlyKeyboardAndMouse && bGamepad)
		{
			// shift gamepad input to controllers with +1 index
			++ControllerId;
			return Super::InputAxis(tViewport, ControllerId, Key, Delta, DeltaTime, NumSamples, bGamepad);
		}
		else
		{
			return Super::InputAxis(tViewport, ControllerId, Key, Delta, DeltaTime, NumSamples, bGamepad);
		}
	}
	
	
};

Create Blueprint class from this, and you’ll see ou can change the Enum to whatever you like.

To change this at runtime you’ll need to access this class with UWorld::GetGameViewport(), so make a function in a c++ blueprint function library (if you want to have access via Blueprints):

class UGameViewportClient* FunctionLibrary::GetGameViewport(const UObject* WorldContextObject)
{
	UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
	return World ? World->GetGameViewport() : nullptr;
}

Once you have that exposed, all you need to do is cast it to your custom Game Viewport class and set the enum to the desired value.

I must say that this works perfectly for me… but I also have input on UI and in that case it isn’t working, I still need to find out what happens in that case.

1 Like

You can also modify the “Skip Assigning Gamepad To Player 1” setting at run time by changing “bOffsetPlayerGamepadIds” of the class “UGameMapsSettings” directly in your game instance.

Full answer in that post : https://forums.unrealengine.com/unreal-engine/feedback-for-epic/48403-keyboard-p1-gamepad-p2-please?p=1549367#post1549367

1 Like

This is an old thread now, but in case someone else is trying to custom map gamepads to controller players, things have changed in 5.1 and aren’t quite stable BUT, you can implement custom overrides for the GameViewPortClient (C++ only).

To do that, in your UGameViewportClient subclass, override Init() and in there, hook your overrides:

void UMyGameViewportClient::Init(struct FWorldContext& WorldContext, UGameInstance* OwningGameInstance, bool bCreateNewAudioDevice)
{
	Super::Init(WorldContext, OwningGameInstance, bCreateNewAudioDevice);
OnOverrideInputAxis().BindUObject(this, &ThisClass::OverrideInputAxisHandler);
OnOverrideInputKey().BindUObject(this, &ThisClass::OverrideInputKey);
}

In your overrides, route the input to whatever player controller you want:

bool UMyGameViewportClient::OverrideInputAxisHandler(FInputKeyEventArgs& EventArgs, float& Delta, float& DeltaTime, int32& NumSamples)
{
	if (EventArgs.IsGamepad()) {
		if (const auto GI = GetGameInstance()) {
			const int32 NumLocalPlayers = GI->GetNumLocalPlayers();
			if (NumLocalPlayers > 1) {
                               // TODO: implement custom logic in your GameInstance class to map gamepads.
				const auto TargetPlayer = GI->GetLocalPlayerByIndex(1);
				const auto ControllerId = TargetPlayer->GetControllerId();
				if (ControllerId < 0) { EventArgs.ControllerId++; }
				if (TargetPlayer && TargetPlayer->PlayerController)
				{
					EventArgs.ControllerId = ControllerId;
					TargetPlayer->PlayerController->InputKey(FInputKeyParams(EventArgs.Key, (double)Delta, DeltaTime, NumSamples, EventArgs.IsGamepad(), EventArgs.InputDevice));
					return true;
				}
			}
		}
	}
	return false;
}

bool UMyGameViewportClient::OverrideInputKey(FInputKeyEventArgs& EventArgs)
{
	if (EventArgs.IsGamepad()) {
		if (const auto GI = GetGameInstance()) {
			const int32 NumLocalPlayers = GI->GetNumLocalPlayers(); 
			if (NumLocalPlayers > 1) {
				const auto TargetPlayer = GI->GetLocalPlayerByIndex(1);
				const auto ControllerId = TargetPlayer->GetControllerId();
				if (ControllerId < 0) { EventArgs.ControllerId++; }
				if (TargetPlayer && TargetPlayer->PlayerController)
				{
					EventArgs.ControllerId = ControllerId;
					const auto bResult = TargetPlayer->PlayerController->InputKey(FInputKeyParams(EventArgs.Key, EventArgs.Event, static_cast<double>(EventArgs.AmountDepressed), EventArgs.IsGamepad(), EventArgs.InputDevice));
					// A gameviewport is always considered to have responded to a mouse buttons to avoid throttling
					if (!bResult && EventArgs.Key.IsMouseButton())
					{
						return true;
					}
					return bResult;
				}
			}
		}
	}
	return false;
}

In the above example, If we have 2 local players, player 2 gets the gamepad. You can of course implement your own custom logic in your custom GameInstance class.

Note that if your viewport client is a UCommonGameViewportClient you might also want to take a look at the OnRerouteInput() and OnRerouteAxis() delegates.

2 Likes

Thanks so much for this!

1 Like

We are now in 2024 and this has been taunting me for a week now. Does anyone please know how to fix this cursed issue? (Without c++)