Skip to content

Instantly share code, notes, and snippets.

@sinbad
Last active March 18, 2024 19:02
Show Gist options
  • Select an option

  • Save sinbad/9b8f8007fb1e55f1a952cce2d12aaac1 to your computer and use it in GitHub Desktop.

Select an option

Save sinbad/9b8f8007fb1e55f1a952cce2d12aaac1 to your computer and use it in GitHub Desktop.

Revisions

  1. sinbad renamed this gist Aug 26, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. sinbad revised this gist Aug 26, 2020. 2 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
  3. sinbad revised this gist Aug 26, 2020. 1 changed file with 0 additions and 3 deletions.
    3 changes: 0 additions & 3 deletions InputDetector.cpp
    Original file line number Diff line number Diff line change
    @@ -1,7 +1,4 @@
    #include "InputModeDetector.h"


    #include "InputHelper.h"
    #include "Input/Events.h"

    FInputModeDetector::FInputModeDetector()
  4. sinbad revised this gist Aug 26, 2020. 1 changed file with 2 additions and 5 deletions.
    7 changes: 2 additions & 5 deletions InputDetector.cpp
    Original file line number Diff line number Diff line change
    @@ -72,13 +72,11 @@ EInputMode FInputModeDetector::GetLastInputMode(int PlayerIndex)

    void FInputModeDetector::ProcessKeyOrButton(int PlayerIndex, FKey Key)
    {
    bool bGamepad = Key.IsGamepadKey();

    if (bGamepad)
    if (Key.IsGamepadKey())
    {
    SetMode(PlayerIndex, EInputMode::Gamepad);
    }
    else if (UInputHelper::IsMouse(Key))
    else if (Key.IsMouseButton())
    {
    // Assuming mice don't have analog buttons!
    SetMode(PlayerIndex, EInputMode::Mouse);
    @@ -89,7 +87,6 @@ void FInputModeDetector::ProcessKeyOrButton(int PlayerIndex, FKey Key)
    // Assuming keyboards don't have analog buttons!
    SetMode(PlayerIndex, EInputMode::Keyboard);
    }

    }

    void FInputModeDetector::SetMode(int PlayerIndex, EInputMode NewMode)
  5. sinbad renamed this gist Aug 26, 2020. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  6. sinbad created this gist Aug 26, 2020.
    50 changes: 50 additions & 0 deletions ExampleUsage.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    // 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);
    }
    106 changes: 106 additions & 0 deletions InputDetector.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,106 @@
    #include "InputModeDetector.h"


    #include "InputHelper.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)
    {
    bool bGamepad = Key.IsGamepadKey();

    if (bGamepad)
    {
    SetMode(PlayerIndex, EInputMode::Gamepad);
    }
    else if (UInputHelper::IsMouse(Key))
    {
    // 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));
    }
    }
    70 changes: 70 additions & 0 deletions InputDetector.h
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,70 @@
    #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);
    };