-
-
Save sinbad/9b8f8007fb1e55f1a952cce2d12aaac1 to your computer and use it in GitHub Desktop.
| #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); | |
| } |
Ok I debug more. What I understand is that the application store all the number of local player. However every player create his own player controller, hud, game instance, etc...
I create and register the InputProcessor in the HUD which is created per player. This means that in my case I don't need to distinguish the player index. I hope my logic is correct, I'll be happy if someone can confirm my theory.
..............................................................................................................................................................................................................................................................................................
I explored more the source code and result that InputProcessor is created in the SubSystem which has a unique instance (Singleton),
Hi sinbad, I think that your theory is valid for legacy ue4 versions because I look at the source code and when we register an input processor it will add it to a list.
When a local player is created, a new HUD will be created as well, which means a new Input processor will be registered. I think I miss something but right now I do not understand why we need user index.