So I found a workaround that is kinda ugly and invasive (in my opinion) but it works :
You should create a custom engine subsystem, who’s single purpose is to send an event on key press (you can add this feature on any subsystem really, in my case I didn’t have one so I created it). This event is fired when an input preprocessor receives a key down.
Relevant engine files :
Engine\Source\Runtime\Engine\Classes\GameFramework\InputDeviceSubsystem.h
Engine\Source\Runtime\Engine\Private\GameFramework\InputDeviceSubsystem.cpp
Engine\Source\Runtime\Slate\Public\Framework\Application\IInputProcessor.h
Example :
YourSubsystem.h
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/EngineSubsystem.h"
#include "YourSubsystem.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnyKeyPressSignature, FKeyEvent, KeyEvent);
class FYourSubsystemInputProcessor;
/**
*
*/
UCLASS()
class UYourSubsystem : public UEngineSubsystem
{
GENERATED_BODY()
public:
static UYourSubsystem* Get();
UFUNCTION(BlueprintCallable, BlueprintPure)
static FInputDeviceId GetInputDeviceIdFromKeyEvent(FKeyEvent KeyEvent);
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
UPROPERTY(BlueprintAssignable)
FOnAnyKeyPressSignature OnAnyKeyPressDelegate;
private:
friend class FYourSubsystemInputProcessor;
void PreprocessorReceivedInput(const FKeyEvent& KeyEvent);
TSharedPtr<FYourSubsystemInputProcessor> InputProcessor;
};
YourSubsystem.cpp
#include "YourSubsystem.h"
#include "Framework/Application/IInputProcessor.h"
class FYourSubsystemInputProcessor : public IInputProcessor
{
friend class FYourSubsystem;
public:
virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) override
{
}
void NotifySubsystemOfKeyPress(const FKeyEvent& KeyEvent)
{
if (UYourSubsystem* Subsystem = UYourSubsystem::Get())
{
Subsystem->PreprocessorReceivedInput(KeyEvent);
}
}
virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InEvent) override
{
NotifySubsystemOfKeyPress(InEvent);
return false;
}
};
UYourSubsystem* UYourSubsystem::Get()
{
return GEngine ? GEngine->GetEngineSubsystem<UYourSubsystem>() : nullptr;
}
FInputDeviceId UXpinEngineSubsystem::GetInputDeviceIdFromKeyEvent(FKeyEvent KeyEvent)
{
return KeyEvent.GetInputDeviceId();
}
void UYourSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// We have to have a valid slate app to run this subsystem
check(FSlateApplication::IsInitialized());
InputProcessor = MakeShared<FYourSubsystemInputProcessor>();
FSlateApplication::Get().RegisterInputPreProcessor(InputProcessor);
}
void UYourSubsystem::Deinitialize()
{
Super::Deinitialize();
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().UnregisterInputPreProcessor(InputProcessor);
}
InputProcessor.Reset();
}
void UYourSubsystem::PreprocessorReceivedInput(const FKeyEvent& KeyEvent)
{
OnAnyKeyPressDelegate.Broadcast(KeyEvent);
}
You can then use this event that is fired on EVERY KEY PRESS and check if the controller id stored in KeyEvent has a player. If not, create it.
I also added a little helper blueprint function that returns the Input device id since the function is not exposed in blueprints natively.