-
-
Save haikusw/27e2df0427ae48bf1fc1a77cc5d50e88 to your computer and use it in GitHub Desktop.
UE4 detecting which input method was last used by each player
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "InputModeDetector.h" | |
#include "Input/Events.h" | |
FInputModeDetector::FInputModeDetector() | |
{ | |
// 4 local players should be plenty usually (will expand if necessary) | |
LastInputModeByPlayer.Init(EInputMode::Mouse, 4); | |
} | |
bool FInputModeDetector::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) | |
{ | |
// Key down also registers for gamepad buttons | |
ProcessKeyOrButton(InKeyEvent.GetUserIndex(), InKeyEvent.GetKey()); | |
// Don't consume | |
return false; | |
} | |
bool FInputModeDetector::HandleAnalogInputEvent(FSlateApplication& SlateApp, | |
const FAnalogInputEvent& InAnalogInputEvent) | |
{ | |
if (InAnalogInputEvent.GetAnalogValue() > GamepadAxisThreshold) | |
SetMode(InAnalogInputEvent.GetUserIndex(), EInputMode::Gamepad); | |
// Don't consume | |
return false; | |
} | |
bool FInputModeDetector::HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) | |
{ | |
FVector2D Dist = MouseEvent.GetScreenSpacePosition() - MouseEvent.GetLastScreenSpacePosition(); | |
if (FMath::Abs(Dist.X) > MouseMoveThreshold || FMath::Abs(Dist.Y) > MouseMoveThreshold) | |
{ | |
SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse); | |
} | |
// Don't consume | |
return false; | |
} | |
bool FInputModeDetector::HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) | |
{ | |
// We don't care which button | |
SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse); | |
// Don't consume | |
return false; | |
} | |
bool FInputModeDetector::HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, | |
const FPointerEvent* InGestureEvent) | |
{ | |
SetMode(InWheelEvent.GetUserIndex(), EInputMode::Mouse); | |
// Don't consume | |
return false; | |
} | |
void FInputModeDetector::Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) | |
{ | |
// Required, but do nothing | |
} | |
EInputMode FInputModeDetector::GetLastInputMode(int PlayerIndex) | |
{ | |
if (PlayerIndex >= 0 && PlayerIndex < LastInputModeByPlayer.Num()) | |
return LastInputModeByPlayer[PlayerIndex]; | |
// Assume default if never told | |
return DefaultInputMode; | |
} | |
void FInputModeDetector::ProcessKeyOrButton(int PlayerIndex, FKey Key) | |
{ | |
if (Key.IsGamepadKey()) | |
{ | |
SetMode(PlayerIndex, EInputMode::Gamepad); | |
} | |
else if (Key.IsMouseButton()) | |
{ | |
// Assuming mice don't have analog buttons! | |
SetMode(PlayerIndex, EInputMode::Mouse); | |
} | |
else | |
{ | |
// We assume anything that's not mouse and not gamepad is a keyboard | |
// Assuming keyboards don't have analog buttons! | |
SetMode(PlayerIndex, EInputMode::Keyboard); | |
} | |
} | |
void FInputModeDetector::SetMode(int PlayerIndex, EInputMode NewMode) | |
{ | |
if (NewMode != EInputMode::Unknown && NewMode != GetLastInputMode(PlayerIndex)) | |
{ | |
if (PlayerIndex >= LastInputModeByPlayer.Num()) | |
LastInputModeByPlayer.SetNum(PlayerIndex + 1); | |
LastInputModeByPlayer[PlayerIndex] = NewMode; | |
OnInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode); | |
UE_LOG(LogTemp, Warning, TEXT("Input mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode)); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "CoreMinimal.h" | |
#include "InputCoreTypes.h" | |
#include "Framework/Application/IInputProcessor.h" | |
#include "UObject/ObjectMacros.h" // for UENUM | |
UENUM(BlueprintType) | |
enum class EInputMode : uint8 | |
{ | |
Mouse, | |
Keyboard, | |
Gamepad, | |
Unknown | |
}; | |
DECLARE_DELEGATE_TwoParams(FOnInputModeForPlayerChanged, int /* PlayerIndex */, EInputMode) | |
/** | |
* This class should be registered as an input processor in order to capture all input events & detect | |
* what kind of devices are being used. We can't use PlayerController to do this reliably because in UMG | |
* mode, all the mouse move events are consumed by Slate and you never see them, so it's not possible to | |
* detect when the user moved a mouse. | |
* | |
* This class should be instantiated and used from some UObject of your choice, e.g. your GameInstance class, | |
* something like this: | |
* | |
* InputDetector = MakeShareable(new FInputModeDetector()); | |
* FSlateApplication::Get().RegisterInputPreProcessor(InputDetector); | |
* InputDetector->OnInputModeChanged.BindUObject(this, &UMyGameInstance::OnInputDetectorModeChanged); | |
* | |
* Note how the OnInputModeChanged on this object is a simple delegate, not a dynamic multicast etc, because | |
* this is not a UObject. You should relay the input mode event changed through the owner if you want to distribute | |
* the information further. | |
*/ | |
class PROJECT_API FInputModeDetector : public IInputProcessor, public TSharedFromThis<FInputModeDetector> | |
{ | |
protected: | |
TArray<EInputMode> LastInputModeByPlayer; | |
public: | |
EInputMode DefaultInputMode = EInputMode::Mouse; | |
float MouseMoveThreshold = 1; | |
float GamepadAxisThreshold = 0.2; | |
// Single delegate caller, owner should propagate if they want (this isn't a UObject) | |
FOnInputModeForPlayerChanged OnInputModeChanged; | |
FInputModeDetector(); | |
virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override; | |
virtual bool | |
HandleAnalogInputEvent(FSlateApplication& SlateApp, const FAnalogInputEvent& InAnalogInputEvent) override; | |
virtual bool HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override; | |
virtual bool HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent) override; | |
virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, | |
const FPointerEvent* InGestureEvent) override; | |
virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) override; | |
EInputMode GetLastInputMode(int PlayerIndex = 0); | |
protected: | |
void ProcessKeyOrButton(int PlayerIndex, FKey Key); | |
void SetMode(int PlayerIndex, EInputMode NewMode); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// You probably want to do this from your GameInstance subclass | |
... | |
// Namespace level in header | |
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnInputModeChanged, int, PlayerIndex, EInputMode, InputMode); | |
... | |
// In class declaration: | |
protected: | |
TSharedPtr<FInputModeDetector> InputDetector; | |
public: | |
/// Event raised when input mode changed between gamepad / keyboard / mouse | |
UPROPERTY(BlueprintAssignable) | |
FOnInputModeChanged OnInputModeChanged; | |
UFUNCTION(BlueprintCallable) | |
EInputMode GetLastInputModeUsed(int PlayerIndex = 0) const { return InputDetector->GetLastInputMode(PlayerIndex); } | |
UFUNCTION(BlueprintCallable) | |
bool LastInputWasGamePad(int PlayerIndex = 0) const { return GetLastInputModeUsed(PlayerIndex) == EInputMode::Gamepad; } | |
... | |
// In source | |
// Do this at startup somewhere | |
void MyExampleGameInstance::CreateInputDetector() | |
{ | |
if (!InputDetector.IsValid()) | |
{ | |
InputDetector = MakeShareable(new FInputModeDetector()); | |
FSlateApplication::Get().RegisterInputPreProcessor(InputDetector); | |
InputDetector->OnInputModeChanged.BindUObject(this, &USnukaGameInstance::OnInputDetectorModeChanged); | |
} | |
} | |
// Do this at shutdown | |
void MyExampleGameInstance::DestroyInputDetector() | |
{ | |
if (InputDetector.IsValid()) | |
{ | |
FSlateApplication::Get().UnregisterInputPreProcessor(InputDetector); | |
InputDetector.Reset(); | |
} | |
} | |
void MyExampleGameInstance::OnInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode) | |
{ | |
// Propagate dynamic multicast event, everyone else should listen on this | |
OnInputModeChanged.Broadcast(PlayerIndex, NewMode); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment