In KOPI, we needed to implement couch co-op mode with multiple controllers all driving the game’s user interface using Unreal Engine’s CommonUI. All controllers should share the same focus, but a APlayerController
should only be spawned for a controller that joins. To implement this kind of functionality without strange glitches, we needed to get your hands on the innards of the FSlateApplication
: the FGenericApplicationMessageHandler
and ISlateInputManager
. The former allowed us to intercept and controllers connecting and disconnecting and creating the appropriate APlayerControllers
for them; the ISlateInputManager
provided a way to treat all already connected controllers as a single connected controller.
void UKopiGamepadSubsystem::Initialize(FSubsystemCollectionBase &Collection) {
Super::Initialize(Collection);
#if PLATFORM_DESKTOP
const TSharedPtr<GenericApplication> GenericApplication =
FSlateApplication::Get().GetPlatformApplication();
OriginalHandler = GenericApplication->GetMessageHandler();
DelegatingHandler = MakeShared<FKopiGamepadApplicationMessageHandler>(
OriginalHandler,
FKopiOtherGamepadKeyDelegate::CreateUObject(this, ...),
FKopiUnifiedGamepadKeyDelegate::CreateUObject(this, ...),
FSimpleDelegate::CreateUObject(this, ...));
GenericApplication->SetMessageHandler(DelegatingHandler.ToSharedRef());
InputManager = MakeShared<FKopiSlateInputManager>();
FSlateApplication::Get().SetInputManager(InputManager.ToSharedRef());
#endif
IPlatformInputDeviceMapper::Get().GetOnInputDeviceConnectionChange().AddUObject(this, &ThisClass::OnInputDeviceConnectionChange);
}
void UKopiGamepadSubsystem::Deinitialize() {
#if PLATFORM_DESKTOP
if (OriginalHandler) {
const TSharedPtr<GenericApplication> GenericApplication =
FSlateApplication::Get().GetPlatformApplication();
GenericApplication->SetMessageHandler(OriginalHandler.ToSharedRef());
}
#endif
Super::Deinitialize();
}
We already discussed the FGenericApplicationMessageHandler
in Multiple controllers and UI, and it’s a good first start. However, the FGenericApplicationMessageHandler
alone leads to strange “multiple focus” in standalone game builds. To ensure that once unified, all controllers are treated as one also for the purposes of Slate, we needed to add the appropriate implementation of ISlateInputManager
.
// Declaration
class FKopiSlateInputManager final : public FSlateDefaultInputMapping {
bool bUnifyGamepads = false;
public:
void SetUnifyGamepads(const bool bUnify);
virtual int32 GetUserIndexForController(int32 ControllerId) const override;
virtual TOptional<int32> GetUserIndexForInputDevice(FInputDeviceId InputDeviceId) const override;
virtual TOptional<int32> GetUserIndexForPlatformUser(FPlatformUserId PlatformUser) const override;
};
// Definition
void FKopiSlateInputManager::SetUnifyGamepads(const bool bUnify) {
bUnifyGamepads = bUnify;
}
int32 FKopiSlateInputManager::GetUserIndexForController(int32 ControllerId) const {
if (bUnifyGamepads)
return 0;
return FSlateDefaultInputMapping::GetUserIndexForController(ControllerId);
}
TOptional<int32> FKopiSlateInputManager::GetUserIndexForInputDevice(FInputDeviceId InputDeviceId) const {
if (bUnifyGamepads)
return 0;
return FSlateDefaultInputMapping::GetUserIndexForInputDevice(InputDeviceId);
}
TOptional<int32> FKopiSlateInputManager::GetUserIndexForPlatformUser(FPlatformUserId PlatformUser) const {
if (bUnifyGamepads)
return 0;
return FSlateDefaultInputMapping::GetUserIndexForPlatformUser(PlatformUser);
}
With this change in, all local controllers behave correctly: for UI, they in effect become a single local controller; but for gameplay–when bUnifyGamepads == false
–they are the same old local controllers again.
Leave a Reply