Record button input changes separately to axis changes, to narrow down user input activity

This commit is contained in:
Steve Streeting 2021-12-06 16:11:29 +00:00
parent 0cd30059f5
commit ecf1ea309b
2 changed files with 109 additions and 19 deletions

View File

@ -35,6 +35,7 @@ void UStevesGameSubsystem::CreateInputDetector()
FSlateApplication::Get().RegisterInputPreProcessor(InputDetector); FSlateApplication::Get().RegisterInputPreProcessor(InputDetector);
InputDetector->OnInputModeChanged.BindUObject(this, &UStevesGameSubsystem::OnInputDetectorModeChanged); InputDetector->OnInputModeChanged.BindUObject(this, &UStevesGameSubsystem::OnInputDetectorModeChanged);
InputDetector->OnButtonInputModeChanged.BindUObject(this, &UStevesGameSubsystem::OnButtonInputDetectorModeChanged);
} }
} }
@ -122,6 +123,13 @@ void UStevesGameSubsystem::OnInputDetectorModeChanged(int PlayerIndex, EInputMod
OnInputModeChanged.Broadcast(PlayerIndex, NewMode); OnInputModeChanged.Broadcast(PlayerIndex, NewMode);
} }
void UStevesGameSubsystem::OnButtonInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode)
{
// This is specifically for button changes; if this is a different main input mode it will also be registered in OnInputDetectorModeChanged
// Just relay this one
OnButtonInputModeChanged.Broadcast(PlayerIndex, NewMode);
}
FFocusSystem* UStevesGameSubsystem::GetFocusSystem() FFocusSystem* UStevesGameSubsystem::GetFocusSystem()
{ {
return &FocusSystem; return &FocusSystem;
@ -160,11 +168,13 @@ UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAction(const FName& N
GS_TempActionMap.Empty(); GS_TempActionMap.Empty();
Settings->GetActionMappingByName(Name, GS_TempActionMap); Settings->GetActionMappingByName(Name, GS_TempActionMap);
// For default, prefer keyboard for buttons // For default, prefer latest press keyboard/mouse for buttons
if (DevicePreference == EInputImageDevicePreference::Auto) if (DevicePreference == EInputImageDevicePreference::Auto)
DevicePreference = EInputImageDevicePreference::Gamepad_Keyboard_Mouse; DevicePreference = EInputImageDevicePreference::Gamepad_Keyboard_Mouse_Button;
const auto Preferred = GetPreferedActionOrAxisMapping<FInputActionKeyMapping>(GS_TempActionMap, Name, DevicePreference, LastInputWasGamePad(PlayerIdx)); const EInputMode LastInput = GetLastInputModeUsed(PlayerIdx);
const EInputMode LastButtonInput = GetLastInputButtonPressed(PlayerIdx);
const auto Preferred = GetPreferedActionOrAxisMapping<FInputActionKeyMapping>(GS_TempActionMap, Name, DevicePreference, LastInput, LastButtonInput);
if (Preferred) if (Preferred)
{ {
return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme); return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme);
@ -186,7 +196,9 @@ UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAxis(const FName& Nam
if (DevicePreference == EInputImageDevicePreference::Auto) if (DevicePreference == EInputImageDevicePreference::Auto)
DevicePreference = EInputImageDevicePreference::Gamepad_Mouse_Keyboard; DevicePreference = EInputImageDevicePreference::Gamepad_Mouse_Keyboard;
const auto Preferred = GetPreferedActionOrAxisMapping<FInputAxisKeyMapping>(GS_TempAxisMap, Name, DevicePreference, LastInputWasGamePad(PlayerIdx)); const EInputMode LastInput = GetLastInputModeUsed(PlayerIdx);
const EInputMode LastButtonInput = GetLastInputButtonPressed(PlayerIdx);
const auto Preferred = GetPreferedActionOrAxisMapping<FInputAxisKeyMapping>(GS_TempAxisMap, Name, DevicePreference, LastInput, LastButtonInput);
if (Preferred) if (Preferred)
{ {
return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme); return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme);
@ -281,7 +293,8 @@ bool UStevesGameSubsystem::FInputModeDetector::ShouldProcessInputEvents() const
UStevesGameSubsystem::FInputModeDetector::FInputModeDetector() UStevesGameSubsystem::FInputModeDetector::FInputModeDetector()
{ {
// 4 local players should be plenty usually (will expand if necessary) // 4 local players should be plenty usually (will expand if necessary)
LastInputModeByPlayer.Init(EInputMode::Mouse, 4); LastInputModeByPlayer.Init(DefaultInputMode, 4);
LastButtonPressByPlayer.Init(DefaultButtonInputMode, 4);
} }
bool UStevesGameSubsystem::FInputModeDetector::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) bool UStevesGameSubsystem::FInputModeDetector::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent)
@ -302,7 +315,7 @@ bool UStevesGameSubsystem::FInputModeDetector::HandleAnalogInputEvent(FSlateAppl
if (ShouldProcessInputEvents()) if (ShouldProcessInputEvents())
{ {
if (InAnalogInputEvent.GetAnalogValue() > GamepadAxisThreshold) if (InAnalogInputEvent.GetAnalogValue() > GamepadAxisThreshold)
SetMode(InAnalogInputEvent.GetUserIndex(), EInputMode::Gamepad); SetMode(InAnalogInputEvent.GetUserIndex(), EInputMode::Gamepad, false);
} }
// Don't consume // Don't consume
@ -316,7 +329,7 @@ bool UStevesGameSubsystem::FInputModeDetector::HandleMouseMoveEvent(FSlateApplic
FVector2D Dist = MouseEvent.GetScreenSpacePosition() - MouseEvent.GetLastScreenSpacePosition(); FVector2D Dist = MouseEvent.GetScreenSpacePosition() - MouseEvent.GetLastScreenSpacePosition();
if (FMath::Abs(Dist.X) > MouseMoveThreshold || FMath::Abs(Dist.Y) > MouseMoveThreshold) if (FMath::Abs(Dist.X) > MouseMoveThreshold || FMath::Abs(Dist.Y) > MouseMoveThreshold)
{ {
SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse); SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse, false);
} }
} }
// Don't consume // Don't consume
@ -328,7 +341,7 @@ bool UStevesGameSubsystem::FInputModeDetector::HandleMouseButtonDownEvent(FSlate
if (ShouldProcessInputEvents()) if (ShouldProcessInputEvents())
{ {
// We don't care which button // We don't care which button
SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse); SetMode(MouseEvent.GetUserIndex(), EInputMode::Mouse, true);
} }
// Don't consume // Don't consume
@ -340,7 +353,7 @@ bool UStevesGameSubsystem::FInputModeDetector::HandleMouseWheelOrGestureEvent(FS
{ {
if (ShouldProcessInputEvents()) if (ShouldProcessInputEvents())
{ {
SetMode(InWheelEvent.GetUserIndex(), EInputMode::Mouse); SetMode(InWheelEvent.GetUserIndex(), EInputMode::Mouse, false);
} }
// Don't consume // Don't consume
@ -356,38 +369,96 @@ EInputMode UStevesGameSubsystem::FInputModeDetector::GetLastInputMode(int Player
return DefaultInputMode; return DefaultInputMode;
} }
EInputMode UStevesGameSubsystem::FInputModeDetector::GetLastButtonInputMode(int PlayerIndex)
{
if (PlayerIndex >= 0 && PlayerIndex < LastButtonPressByPlayer.Num())
return LastButtonPressByPlayer[PlayerIndex];
// Assume default if never told
return DefaultButtonInputMode;
}
void UStevesGameSubsystem::FInputModeDetector::ProcessKeyOrButton(int PlayerIndex, FKey Key) void UStevesGameSubsystem::FInputModeDetector::ProcessKeyOrButton(int PlayerIndex, FKey Key)
{ {
if (Key.IsGamepadKey()) if (Key.IsGamepadKey())
{ {
SetMode(PlayerIndex, EInputMode::Gamepad); SetMode(PlayerIndex, EInputMode::Gamepad, IsAGamepadButton(Key));
} }
else if (Key.IsMouseButton()) else if (Key.IsMouseButton())
{ {
// Assuming mice don't have analog buttons! // Assuming mice don't have analog buttons!
SetMode(PlayerIndex, EInputMode::Mouse); SetMode(PlayerIndex, EInputMode::Mouse, true);
} }
else else
{ {
// We assume anything that's not mouse and not gamepad is a keyboard // We assume anything that's not mouse and not gamepad is a keyboard
// Assuming keyboards don't have analog buttons! // Assuming keyboards don't have analog buttons!
SetMode(PlayerIndex, EInputMode::Keyboard); SetMode(PlayerIndex, EInputMode::Keyboard, true);
} }
} }
void UStevesGameSubsystem::FInputModeDetector::SetMode(int PlayerIndex, EInputMode NewMode) bool UStevesGameSubsystem::FInputModeDetector::IsAGamepadButton(const FKey& Key)
{ {
// Key.IsButtonAxis() returns true for some thumbstick movement events, because the axis type is EInputAxisType::Button for
// some reason. That means you get button events for thumbstick movements, which is super dumb.
// See core engine InputCoreTypes.cpp for the stick axes which are defined FKeyDetails::GamepadKey | FKeyDetails::ButtonAxis
// This is for some kind of virtual input but it's a nasty hack, omit them
return Key.IsGamepadKey() &&
Key != EKeys::Gamepad_LeftStick_Up &&
Key != EKeys::Gamepad_LeftStick_Down &&
Key != EKeys::Gamepad_LeftStick_Left &&
Key != EKeys::Gamepad_LeftStick_Right &&
Key != EKeys::Gamepad_RightStick_Up &&
Key != EKeys::Gamepad_RightStick_Down &&
Key != EKeys::Gamepad_RightStick_Left &&
Key != EKeys::Gamepad_RightStick_Right;
}
void UStevesGameSubsystem::FInputModeDetector::SetMode(int PlayerIndex, EInputMode NewMode, bool bIsButton)
{
bool bButtonChanged = false;
bool bMainChanged = false;
if (bIsButton)
{
if (NewMode != EInputMode::Unknown && NewMode != GetLastButtonInputMode(PlayerIndex))
{
if (PlayerIndex >= LastButtonPressByPlayer.Num())
LastButtonPressByPlayer.SetNum(PlayerIndex + 1);
LastButtonPressByPlayer[PlayerIndex] = NewMode;
bButtonChanged = true;
}
}
// Whether it's a button or not it can affect the main input mode
if (NewMode != EInputMode::Unknown && NewMode != GetLastInputMode(PlayerIndex)) if (NewMode != EInputMode::Unknown && NewMode != GetLastInputMode(PlayerIndex))
{ {
if (PlayerIndex >= LastInputModeByPlayer.Num()) if (PlayerIndex >= LastInputModeByPlayer.Num())
LastInputModeByPlayer.SetNum(PlayerIndex + 1); LastInputModeByPlayer.SetNum(PlayerIndex + 1);
LastInputModeByPlayer[PlayerIndex] = NewMode; LastInputModeByPlayer[PlayerIndex] = NewMode;
bMainChanged = true;
}
// Raise events at the end once all state has changed
if (bButtonChanged)
{
// ReSharper disable once CppExpressionWithoutSideEffects
OnButtonInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode);
//UE_LOG(LogStevesUEHelpers, Display, TEXT("Button mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode));
}
if (bMainChanged)
{
// ReSharper disable once CppExpressionWithoutSideEffects
OnInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode); OnInputModeChanged.ExecuteIfBound(PlayerIndex, NewMode);
//UE_LOG(LogStevesUEHelpers, Display, TEXT("Input mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode)); //UE_LOG(LogStevesUEHelpers, Display, TEXT("Input mode for player %d changed: %s"), PlayerIndex, *UEnum::GetValueAsString(NewMode));
} }
} }
//PRAGMA_ENABLE_OPTIMIZATION //PRAGMA_ENABLE_OPTIMIZATION

View File

@ -64,8 +64,10 @@ protected:
{ {
protected: protected:
TArray<EInputMode> LastInputModeByPlayer; TArray<EInputMode> LastInputModeByPlayer;
TArray<EInputMode> LastButtonPressByPlayer;
const EInputMode DefaultInputMode = EInputMode::Mouse; const EInputMode DefaultInputMode = EInputMode::Mouse;
const EInputMode DefaultButtonInputMode = EInputMode::Keyboard;
const float MouseMoveThreshold = 1; const float MouseMoveThreshold = 1;
const float GamepadAxisThreshold = 0.2; const float GamepadAxisThreshold = 0.2;
@ -74,8 +76,10 @@ protected:
/// Whether this detector should ignore events (e.g. because the application is in the background) /// Whether this detector should ignore events (e.g. because the application is in the background)
bool bIgnoreEvents = false; bool bIgnoreEvents = false;
// Single delegate caller, owner should propagate if they want (this isn't a UObject) /// Event raised when main input mode changes for any reason
FInternalInputModeChanged OnInputModeChanged; FInternalInputModeChanged OnInputModeChanged;
/// Event raised when button input mode changes only
FInternalInputModeChanged OnButtonInputModeChanged;
FInputModeDetector(); FInputModeDetector();
@ -88,14 +92,18 @@ protected:
virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent, virtual bool HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& InWheelEvent,
const FPointerEvent* InGestureEvent) override; const FPointerEvent* InGestureEvent) override;
/// Get the last input mode from any kind of input
EInputMode GetLastInputMode(int PlayerIndex = 0); EInputMode GetLastInputMode(int PlayerIndex = 0);
/// Get the last input mode from button inputs (ignores axis changes, good for detecting if keyboard or mouse buttons are being used)
EInputMode GetLastButtonInputMode(int PlayerIndex = 0);
// Needed but unused // Needed but unused
virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) override {} virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) override {}
protected: protected:
static bool IsAGamepadButton(const FKey& Key);
void ProcessKeyOrButton(int PlayerIndex, FKey Key); void ProcessKeyOrButton(int PlayerIndex, FKey Key);
void SetMode(int PlayerIndex, EInputMode NewMode); void SetMode(int PlayerIndex, EInputMode NewMode, bool bIsButton);
}; };
protected: protected:
@ -122,6 +130,7 @@ protected:
// Called by detector // Called by detector
void OnInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode); void OnInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode);
void OnButtonInputDetectorModeChanged(int PlayerIndex, EInputMode NewMode);
TSoftObjectPtr<UDataTable> GetGamepadImages(int PlayerIndex, const UUiTheme* Theme); TSoftObjectPtr<UDataTable> GetGamepadImages(int PlayerIndex, const UUiTheme* Theme);
@ -129,17 +138,27 @@ protected:
public: public:
/// Event raised when input mode changed between gamepad and keyboard / mouse /// Event raised when main input mode changed between gamepad and keyboard / mouse (for any of axis / button events)
UPROPERTY(BlueprintAssignable) UPROPERTY(BlueprintAssignable)
FOnInputModeChanged OnInputModeChanged; FOnInputModeChanged OnInputModeChanged;
/// Event raised when the last button input changed between gamepad / keyboard / mouse
/// This can happen at a different time to OnInputModeChanged, e.g. if that was triggered by a mouse move, but the
/// last button pressed was still keyboard, you'd get this event later
UPROPERTY(BlueprintAssignable)
FOnInputModeChanged OnButtonInputModeChanged;
/// Event raised when the game window's foreground status changes /// Event raised when the game window's foreground status changes
UPROPERTY(BlueprintAssignable) UPROPERTY(BlueprintAssignable)
FOnWindowForegroundChanged OnWindowForegroundChanged; FOnWindowForegroundChanged OnWindowForegroundChanged;
/// Gets the device where the most recent input event of any kind happened
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
EInputMode GetLastInputModeUsed(int PlayerIndex = 0) const { return InputDetector->GetLastInputMode(PlayerIndex); } EInputMode GetLastInputModeUsed(int PlayerIndex = 0) const { return InputDetector->GetLastInputMode(PlayerIndex); }
/// Gets the device where the most recent button press happened
UFUNCTION(BlueprintCallable)
EInputMode GetLastInputButtonPressed(int PlayerIndex = 0) const { return InputDetector->GetLastButtonInputMode(PlayerIndex); }
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
bool LastInputWasGamePad(int PlayerIndex = 0) const { return GetLastInputModeUsed(PlayerIndex) == EInputMode::Gamepad; } bool LastInputWasGamePad(int PlayerIndex = 0) const { return GetLastInputModeUsed(PlayerIndex) == EInputMode::Gamepad; }