Skip to content

Instantly share code, notes, and snippets.

@haikusw
Forked from sinbad/InputModeDetector.cpp
Created August 29, 2020 15:45
Show Gist options
  • Save haikusw/27e2df0427ae48bf1fc1a77cc5d50e88 to your computer and use it in GitHub Desktop.
Save haikusw/27e2df0427ae48bf1fc1a77cc5d50e88 to your computer and use it in GitHub Desktop.
UE4 detecting which input method was last used by each player
#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));
}
}
#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);
};
// 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