По умолчанию в Unreal Engine к основному игроку привязываются клава, мышка и первый подключенный геймпад. Остальные устройства автоматически привязываются к другим игрокам. Это нормально если у вас есть поддержка мультиплеера на одном компьютере (сплитскрин или просто несколько персов бегают на одном экране). Но в single-player игре все может быть грустно.

Пример. Можно завалить этот тест и не получить заветную плашечку "Steam Deck Verified":

External Controller (Primary Player).
Test failed due to the title ignoring Steam Deck controls when an external controller is used. We found that once past the title screen, only the input device that engaged it will be recognized. This forces users to have to restart the game to regain controls of the Steam Deck built-in controls as the game does not allow you to return to the title screen and also doesn’t provide options for switching input devices.

Steam Deck Compatibility Review

Вполне возможны аналогичные проблемы и для Nintendo Switch с подключенным внешним контроллером.

К сожалению, я не нашел простого и универсального способа исправить ситуацию. Более того, без С++ ее тоже непонятно как решать. Единственный хороший момент: не нужно менять и пересобирать движок.

Решение для Unreal Engine 4

Немного теории. Отвечает за обработку ввода FSlateApplication и вызывает уже соответствующие обработчики у UI или PlayerController. На самом деле, можно копнуть еще глубже, но это уже будет платформо-зависимый код. Определяет же этот класс, какое устройство принадлежит какому игроку, при помощи интерфейса ISlateInputManager (и реализованного на его базе FSlateDefaultInputMapping).

Реализуем свой вариант ISlateInputManager, в котором все геймпады принадлежат одному игроку (с индексом 0). Выглядеть это будет примерно так:

class FSlateSinglePlayerInputMapping : public FSlateDefaultInputMapping
{
public:
    int32 GetUserIndexForController(int32 ControllerId) const override { return 0; }
};

Устанавливается новый менеджер так:

FSlateApplication::Get().SetInputManager(MakeShared<FSlateSinglePlayerInputMapping>());

Где и как это делать? Зависит от вашей игры. У себя в игре, мы устанавливаем новый менеджер в UGameInstance::Init. Можно сделать 2 функции: установка single-player менеджера и менеджера по умолчанию; и переключаться между ними по необходимости. Например в главном меню все перенаправлять на одного игрока, а в самой игре раздавать геймпады каждому участнику.

Стоит отметить, что нужно добавить в *.Build.cs вашего модуля зависимости от Slate и SlateCore:

PublicDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });

Решение для Unreal Engine 5

В этой версии движка обработку событий перелопатили и FSlateApplication добавляет в них не только информацию об идентификаторе игрока, но еще и идентификатор самого устройства. Причем разные обработчики, кушают разные идентификаторы. Поэтому, вариант от UE4 тут не прокатит.

Немного теории. Теперь за соответствие игрока и устройства отвечает интерфейс IPlatformInputDeviceMapper (и его реализация FGenericPlatformInputDeviceMapper). К сожалению, без пересборки движка, заменить этот класс невозможно, и придется пользоваться только тем, что он предоставляет. А именно: мы можем получить все устройства (GetAllConnectedInputDevices) и перепривязать их к первому игроку (Internal_ChangeInputDeviceUserMapping). И да, это очень страшный хак и, возможно, в будущем он перестанет работать.

IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
const FPlatformUserId PrimaryUserId = DeviceMapper.GetPrimaryPlatformUser();

TArray<FInputDeviceId> InputDevices;
DeviceMapper.GetAllConnectedInputDevices(InputDevices);

for (const FInputDeviceId& DeviceId : InputDevices)
{
	const FPlatformUserId UserId = DeviceMapper.GetUserForInputDevice(DeviceId);
	if (UserId != PrimaryUserId)
	{
		DeviceMapper.Internal_ChangeInputDeviceUserMapping(DeviceId, PrimaryUserId, UserId);
	}
}

Мы выполняем этот код при старте игры в UGameInstance::Init. Ваша реализация, может располагаться в каком-то другом месте или вообще отсутствовать, если обработчик из абзаца ниже подцепить к чему-то совсем глобальному.

Еще можно отслеживать событие на подключение нового устройства и сразу же привязывать его к первому игроку. У класса UGameInstance есть метод HandleInputDeviceConnectionChange. Если вы реализуете обработку в другом месте, то можно подцепиться к делегату самого IPlatformInputDeviceMapperGetOnInputDeviceConnectionChange. Код обработчика выглядит так:

void UMyGameInstance::HandleInputDeviceConnectionChange(EInputDeviceConnectionState NewConnectionState, 
	FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId)
{
	Super::HandleInputDeviceConnectionChange(NewConnectionState, PlatformUserId, InputDeviceId);

	if (NewConnectionState == EInputDeviceConnectionState::Connected)
	{
		IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get();
		const FPlatformUserId PrimaryUserId = DeviceMapper.GetPrimaryPlatformUser();

		if (PlatformUserId != PrimaryUserId)
		{
			DeviceMapper.Internal_ChangeInputDeviceUserMapping(InputDeviceId, PrimaryUserId, PlatformUserId);
		}
	}
}

К сожалению, красивого варианта «вернуть все взад» и переключаться между различными вариантами обработки событий от геймпадов уже не будет. Все это дело работает в версии 5.2, и у меня большие сомнения, что без модификации оно проживет сильно долго. Пользуйтесь на свой страх и риск.

Для компиляции в *.Build.cs вашего модуля нужно добавить зависимость от ApplicationCore:

PublicDependencyModuleNames.AddRange(new string[] { "ApplicationCore" });

Минутка саморекламы

Оценить, на сколько этот код будет работоспособным, вы сможете в нашей игре Бесконечная Мгла. Мы скоро выпускаем демо-версию, а пока, добавляйте нас вишлист????

Комментарии (4)


  1. DimoniXo
    03.08.2023 14:17

    Для пользователя есть решение из коробки, называется Copilot

    https://support.xbox.com/ru-RU/help/account-profile/accessibility/copilot

    Некоторые игры в таком режиме прямо играют новыми красками, особенно совместное прохождение платформеров.


    1. maltsevda Автор
      03.08.2023 14:17

      Спасибо, не знал о такой штуке. Но, я не думаю, что ее будут учитывать при прохождении игрой Steam Deck Compatibility Review.


  1. RandomVertex
    03.08.2023 14:17

    А можно такое блюпринтами сделать?)


    1. maltsevda Автор
      03.08.2023 14:17

      Можно написать плагин, и использовать в блюпринтах уже его. Я думал о таком варианте, пока не начал разбираться с UE5. А после этого решил не связываться, так как красивый и универсальный вариант сделать не получится.

      А вообще нет, к сожалению блюпринтами обойтись не получится. Для IPlatformInputDeviceMapper есть своя библиотека, но через нее можно только получать информацию. Я не нашел способа, как изменить мэппинг устройств.