// Copyright Epic Games, Inc. All Rights Reserved. #include "GameInputDeviceProcessor.h" #include "GameInputUtils.h" #include "GameInputLogging.h" #include "HAL/PlatformTime.h" #include "GameInputKeyTypes.h" #include "GenericPlatform/GenericPlatformInputDeviceMapper.h" #include "GenericPlatform/CursorUtils.h" #include "Framework/Application/SlateApplication.h" #include "HAL/IConsoleManager.h" #include "Misc/ConfigCacheIni.h" #include "GameInputDeveloperSettings.h" #if GAME_INPUT_SUPPORT namespace UE::GameInput { static FName InputClassName = FName("GameInput"); /** * Returns a map of GameInputRacingWheelButtons to their associated Unreal Engine FKey names. */ static const TMap& GetRacingWheelButtonMap() { static const TMap RacingWheelButtonMap { { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelNone), FGameInputKeys::RacingWheel_None.GetFName() }, { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelMenu), FGameInputKeys::RacingWheel_Menu.GetFName() }, { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelView), FGameInputKeys::RacingWheel_View.GetFName() }, { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelPreviousGear), FGameInputKeys::RacingWheel_PreviousGear.GetFName() }, { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelNextGear), FGameInputKeys::RacingWheel_NextGear.GetFName() }, { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelDpadUp), FGamepadKeyNames::DPadUp }, { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelDpadDown), FGamepadKeyNames::DPadDown }, { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelDpadLeft), FGamepadKeyNames::DPadLeft }, { static_cast(GameInputRacingWheelButtons::GameInputRacingWheelDpadRight), FGamepadKeyNames::DPadRight }, }; return RacingWheelButtonMap; } }; //////////////////////////////////////////////////////// // IGameInputDeviceProcessor IGameInputDeviceProcessor::IGameInputDeviceProcessor() { InitialButtonRepeatDelay = UE::GameInput::InitialRepeatDelay; ButtonRepeatDelay = UE::GameInput::SubsequentRepeatDelay; GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); } const GameInputDeviceInfo* IGameInputDeviceProcessor::FGameInputEventParams::GetDeviceInfo() const { if (Device) { return Device->GetDeviceInfo(); } return nullptr; } bool IGameInputDeviceProcessor::PostProcessInput(const FGameInputEventParams& Params) { // Nothing needs to be done by default here in PostProcessInput. return false; } const FString& IGameInputDeviceProcessor::GetHardwareDeviceIdentifierName(const IGameInputDeviceProcessor::FGameInputEventParams& Params) const { static FString ID_Virtual = TEXT("Virtual"); static FString ID_Aggregate = TEXT("Aggregate"); static FString ID_XboxOne = TEXT("XboxOne"); static FString ID_Xbox360 = TEXT("Xbox360"); static FString ID_Hid = TEXT("Hid"); static FString ID_I8042 = TEXT("I8042"); if (Params.Device) { if (const GameInputDeviceInfo* Info = Params.GetDeviceInfo()) { // Check for any device specific overrides that may be there for custom devices if (const UGameInputDeveloperSettings* Settings = GetDefault()) { if (const FGameInputDeviceConfiguration* Config = Settings->FindDeviceConfiguration(Info)) { if (Config->bOverrideHardwareDeviceIdString) { return Config->OverriddenHardwareDeviceId; } } } switch (Info->deviceFamily) { case GameInputFamilyVirtual: return ID_Virtual; case GameInputFamilyAggregate: return ID_Aggregate; case GameInputFamilyXbox360: return ID_Xbox360; case GameInputFamilyHid: return ID_Hid; case GameInputFamilyI8042: return ID_I8042; case GameInputFamilyXboxOne: default: return ID_XboxOne; } } } // If for some reason we are given a null device, default to just "Xbox One" return ID_XboxOne; } void IGameInputDeviceProcessor::OnControllerAnalog(const FGameInputEventParams& Params, const FName& GamePadKey, float NewAxisValueNormalized, float OldAxisValueNormalized, float DeadZone, const bool bSetDeviceScope /*= true*/) { if (OldAxisValueNormalized != NewAxisValueNormalized || FMath::Abs(NewAxisValueNormalized) > DeadZone) { UE_LOG(LogGameInput, VeryVerbose, TEXT("Device %s (PlatformUserId = %d, InputDeviceId = %d) - Analog %s : %.3f"), *UE::GameInput::LexToString(Params.Device), Params.PlatformUserId.GetInternalId(), Params.InputDeviceId.GetId(), *GamePadKey.ToString(), NewAxisValueNormalized); // We should only tell slate about this message if the platform user and input device are valid because it will attempt // to create a new slate user based on the index if it doesn't already exist if (Params.PlatformUserId.IsValid() && Params.InputDeviceId.IsValid()) { if (bSetDeviceScope) { FInputDeviceScope InputScope(nullptr, UE::GameInput::InputClassName, IPlatformInputDeviceMapper::Get().GetUserIndexForPlatformUser(Params.PlatformUserId), GetHardwareDeviceIdentifierName(Params)); Params.MessageHandler->OnControllerAnalog(GamePadKey, Params.PlatformUserId, Params.InputDeviceId, NewAxisValueNormalized); } else { Params.MessageHandler->OnControllerAnalog(GamePadKey, Params.PlatformUserId, Params.InputDeviceId, NewAxisValueNormalized); } } } } void IGameInputDeviceProcessor::EvaluateButtonStates( const FGameInputEventParams& Params, const uint32 CurrentButtonHeldMask, uint32& PreviousButtonMask, double* RepeatTime, const TMap& UnrealButtonNameMap, const uint32 SupportedButtonCount /*= MaxSupportedButtons*/) { // handle button change events const uint32 ActionMask = (PreviousButtonMask ^ CurrentButtonHeldMask); const uint32 RepeatMask = (PreviousButtonMask & CurrentButtonHeldMask); uint32 BitMask = 1; if (!RepeatTime) { UE_LOG(LogGameInput, Error, TEXT("[IGameInputDeviceProcessor::EvaluateButtonStates] Invalid RepeatTime array given to evaluate button states!")); return; } // If the given button mask and repeat mask are both zero, then no buttons have been pressed or had a state change. No need to iterate the bitmask if (ActionMask == 0 && RepeatMask == 0) { return; } // We can't tell slate about input messages from an invalid platform user if (!Params.PlatformUserId.IsValid()) { UE_LOG(LogGameInput, Verbose, TEXT("[IGameInputDeviceProcessor::EvaluateButtonStates] Attempting to evaluate button states with an invalid platform user id of '%d'. The button messages will not be sent."), Params.PlatformUserId.GetInternalId()); return; } const double CurrentTime = FPlatformTime::Seconds(); IPlatformInputDeviceMapper& DeviceMapper = IPlatformInputDeviceMapper::Get(); for (uint32 n = 0; n < SupportedButtonCount; ++n) { FGamepadKeyNames::Type ButtonKey; if (UE::GameInput::GameInputButtonToUnrealName(UnrealButtonNameMap, BitMask, ButtonKey)) { // Check for button state change if (0 != (ActionMask & BitMask)) { FInputDeviceScope InputScope(nullptr, UE::GameInput::InputClassName, DeviceMapper.GetUserIndexForPlatformUser(Params.PlatformUserId), GetHardwareDeviceIdentifierName(Params)); if (0 != (CurrentButtonHeldMask & BitMask)) { UE_LOG(LogGameInput, Verbose, TEXT("[FGameInputDevice::EvaluateButtonStates] (PlatformUserId = %d, InputDeviceId = %d) - Button '%s' Pressed"), Params.PlatformUserId.GetInternalId(), Params.InputDeviceId.GetId(), *ButtonKey.ToString()); Params.MessageHandler->OnControllerButtonPressed(ButtonKey, Params.PlatformUserId, Params.InputDeviceId, false); RepeatTime[n] = CurrentTime + InitialButtonRepeatDelay; } else { UE_LOG(LogGameInput, Verbose, TEXT("[FGameInputDevice::EvaluateButtonStates] (PlatformUserId = %d, InputDeviceId = %d) - Button '%s' Released"), Params.PlatformUserId.GetInternalId(), Params.InputDeviceId.GetId(), *ButtonKey.ToString()); Params.MessageHandler->OnControllerButtonReleased(ButtonKey, Params.PlatformUserId, Params.InputDeviceId, false); RepeatTime[n] = 0.0; } } // Check for repeat key if (0 != (RepeatMask & BitMask) && CurrentTime > RepeatTime[n]) { FInputDeviceScope InputScope(nullptr, UE::GameInput::InputClassName, DeviceMapper.GetUserIndexForPlatformUser(Params.PlatformUserId), GetHardwareDeviceIdentifierName(Params)); RepeatTime[n] = CurrentTime + ButtonRepeatDelay; UE_LOG(LogGameInput, Verbose, TEXT("[FGameInputDevice::EvaluateButtonStates] (PlatformUserId = %d, InputDeviceId = %d) - Button '%s' Repeat Pressed"), Params.PlatformUserId.GetInternalId(), Params.InputDeviceId.GetId(), *ButtonKey.ToString()); Params.MessageHandler->OnControllerButtonPressed(ButtonKey, Params.PlatformUserId, Params.InputDeviceId, true); } } // Move on to the next bit! BitMask <<= 1; } PreviousButtonMask = CurrentButtonHeldMask; } void IGameInputDeviceProcessor::EvaluateSwitchState( const FGameInputEventParams& Params, GameInputSwitchPosition CurrentPosition, GameInputSwitchPosition& PreviousPosition, TArray& RepeatTimes) { if (!ensureMsgf(RepeatTimes.IsValidIndex(static_cast(GameInputSwitchLeft)), TEXT("RepeatTimes array needs to be the same size as the number of switch positions!"))) { return; } GameInputSwitchPosition PrevCopy = PreviousPosition; // If the current and previous switch states are both the center, then nothing has happened. if (CurrentPosition == GameInputSwitchCenter && PreviousPosition == GameInputSwitchCenter) { return; } // We can't send any slate input events to slate if the platform user is invalid if (!Params.PlatformUserId.IsValid()) { UE_LOG(LogGameInput, Verbose, TEXT("[IGameInputDeviceProcessor::EvaluateSwitchState] Attempting to evaluate button states with an invalid platform user id of '%d'. The input messages will not be sent."), Params.PlatformUserId.GetInternalId()); return; } const double CurrentTime = FPlatformTime::Seconds(); // If the current and previous are not the same, then release the previous // and send the pressed event for the current, as this is the first press if (CurrentPosition != PreviousPosition) { // Release the previous keys if (const TArray* PrevKeyArray = UE::GameInput::SwitchPositionToUnrealName(PreviousPosition)) { for (const FGamepadKeyNames::Type KeyName : *PrevKeyArray) { UE_LOG(LogGameInput, Verbose, TEXT("[EvaluateSwitchState] (PlatformUserId = %d, InputDeviceId = %d) - Switch '%s' Released"), Params.PlatformUserId.GetInternalId(), Params.InputDeviceId.GetId(), *KeyName.ToString()); Params.MessageHandler->OnControllerButtonReleased(KeyName, Params.PlatformUserId, Params.InputDeviceId, false); } RepeatTimes[static_cast(PreviousPosition)] = 0.0; } // "Press" the current key if (const TArray* CurrentKeyArray = UE::GameInput::SwitchPositionToUnrealName(CurrentPosition)) { for (const FGamepadKeyNames::Type KeyName : *CurrentKeyArray) { UE_LOG(LogGameInput, Verbose, TEXT("[EvaluateSwitchState] (PlatformUserId = %d, InputDeviceId = %d) - Switch '%s' Pressed"), Params.PlatformUserId.GetInternalId(), Params.InputDeviceId.GetId(), *KeyName.ToString()); Params.MessageHandler->OnControllerButtonPressed(KeyName, Params.PlatformUserId, Params.InputDeviceId, true); } RepeatTimes[static_cast(CurrentPosition)] = CurrentTime + InitialButtonRepeatDelay; } } // Otherwise, the states are the same. Check if we can repeat them else if (CurrentTime > RepeatTimes[static_cast(CurrentPosition)]) { if (const TArray* CurrentKeyArray = UE::GameInput::SwitchPositionToUnrealName(CurrentPosition)) { for (const FGamepadKeyNames::Type KeyName : *CurrentKeyArray) { UE_LOG(LogGameInput, Verbose, TEXT("[EvaluateSwitchState] (PlatformUserId = %d, InputDeviceId = %d) - Switch '%s' Repeat Pressed"), Params.PlatformUserId.GetInternalId(), Params.InputDeviceId.GetId(), *KeyName.ToString()); Params.MessageHandler->OnControllerButtonPressed(KeyName, Params.PlatformUserId, Params.InputDeviceId, true); } } // Check for repeat key RepeatTimes[static_cast(CurrentPosition)] = CurrentTime + ButtonRepeatDelay; } // Keep track of the previous position PreviousPosition = CurrentPosition; } //////////////////////////////////////////////////////// // FGameInputGamepadDeviceProcessor namespace UE::GameInput { inline bool IsEmptyGamepadReading(const GameInputGamepadState& GamepadState) { return GamepadState.buttons == 0 && GamepadState.leftTrigger <= GamepadTriggerDeadzone && GamepadState.rightTrigger <= GamepadTriggerDeadzone && FMath::Abs(GamepadState.leftThumbstickX) <= GamepadLeftStickDeadzone && FMath::Abs(GamepadState.leftThumbstickY) <= GamepadLeftStickDeadzone && FMath::Abs(GamepadState.rightThumbstickX) <= GamepadRightStickDeadzone && FMath::Abs(GamepadState.rightThumbstickY) <= GamepadRightStickDeadzone; }; inline bool HasDifferentAnalogInput(const GameInputGamepadState& CurrentGamepadState, const GameInputGamepadState& PreviousGamepadState) { return CurrentGamepadState.leftTrigger != PreviousGamepadState.leftTrigger || CurrentGamepadState.rightTrigger != PreviousGamepadState.rightTrigger || CurrentGamepadState.leftThumbstickX != PreviousGamepadState.leftThumbstickX || CurrentGamepadState.leftThumbstickY != PreviousGamepadState.leftThumbstickY || CurrentGamepadState.rightThumbstickX != PreviousGamepadState.rightThumbstickX || CurrentGamepadState.rightThumbstickY != PreviousGamepadState.rightThumbstickY; }; /** * Returns a map of GameInputGamepadButtons to their associated Unreal Engine FKey names. */ static const TMap& GetGamepadButtonMap() { static const TMap GamepadButtonMap { { static_cast(GameInputGamepadButtons::GameInputGamepadA), FGamepadKeyNames::FaceButtonBottom }, { static_cast(GameInputGamepadButtons::GameInputGamepadB), FGamepadKeyNames::FaceButtonRight }, { static_cast(GameInputGamepadButtons::GameInputGamepadX), FGamepadKeyNames::FaceButtonLeft }, { static_cast(GameInputGamepadButtons::GameInputGamepadY), FGamepadKeyNames::FaceButtonTop }, { static_cast(GameInputGamepadButtons::GameInputGamepadLeftShoulder), FGamepadKeyNames::LeftShoulder }, { static_cast(GameInputGamepadButtons::GameInputGamepadRightShoulder), FGamepadKeyNames::RightShoulder }, { static_cast(GameInputGamepadButtons::GameInputGamepadMenu), FGamepadKeyNames::SpecialRight }, { static_cast(GameInputGamepadButtons::GameInputGamepadView), FGamepadKeyNames::SpecialLeft }, { static_cast(GameInputGamepadButtons::GameInputGamepadDPadUp), FGamepadKeyNames::DPadUp }, { static_cast(GameInputGamepadButtons::GameInputGamepadDPadDown), FGamepadKeyNames::DPadDown }, { static_cast(GameInputGamepadButtons::GameInputGamepadDPadLeft), FGamepadKeyNames::DPadLeft }, { static_cast(GameInputGamepadButtons::GameInputGamepadDPadRight), FGamepadKeyNames::DPadRight }, { static_cast(GameInputGamepadButtons::GameInputGamepadLeftThumbstick), FGamepadKeyNames::LeftThumb }, { static_cast(GameInputGamepadButtons::GameInputGamepadRightThumbstick), FGamepadKeyNames::RightThumb }, { static_cast(EGameInputAuxButton::GamepadButtonAux_LeftTrigger), FGamepadKeyNames::LeftTriggerThreshold }, { static_cast(EGameInputAuxButton::GamepadButtonAux_RightTrigger), FGamepadKeyNames::RightTriggerThreshold }, { static_cast(EGameInputAuxButton::GamepadButtonAux_LeftStickUp), FGamepadKeyNames::LeftStickUp }, { static_cast(EGameInputAuxButton::GamepadButtonAux_LeftStickDown), FGamepadKeyNames::LeftStickDown }, { static_cast(EGameInputAuxButton::GamepadButtonAux_LeftStickLeft), FGamepadKeyNames::LeftStickLeft }, { static_cast(EGameInputAuxButton::GamepadButtonAux_LeftStickRight), FGamepadKeyNames::LeftStickRight }, { static_cast(EGameInputAuxButton::GamepadButtonAux_RightStickUp), FGamepadKeyNames::RightStickUp }, { static_cast(EGameInputAuxButton::GamepadButtonAux_RightStickDown), FGamepadKeyNames::RightStickDown }, { static_cast(EGameInputAuxButton::GamepadButtonAux_RightStickLeft), FGamepadKeyNames::RightStickLeft }, { static_cast(EGameInputAuxButton::GamepadButtonAux_RightStickRight), FGamepadKeyNames::RightStickRight } }; return GamepadButtonMap; } } bool FGameInputGamepadDeviceProcessor::ProcessInput(const FGameInputEventParams& Params) { // check if the reading had gamepad info GameInputGamepadState GamepadState; if (!Params.Reading->GetGamepadState(&GamepadState)) { return false; } bool bRes = false; // We want to process gamepad BUTTON states for every game input reading. bRes |= ProcessGamepadButtonState(Params, GamepadState); ++NumReadingsProcessedThisFrame; return bRes; } bool FGameInputGamepadDeviceProcessor::PostProcessInput(const FGameInputEventParams& Params) { // On the last input reading for the frame, the "Current Reading" should always be null. we only care for the LastReading here ensure (Params.Reading == nullptr); const bool bProcessedAnyGamepadButtonsThisFrame = (NumReadingsProcessedThisFrame > 0); // This is the last reading of this frame, reset the counter to 0 NumReadingsProcessedThisFrame = 0; if (!Params.PreviousReading) { return false; } // Get the gamepad state of the *Last Reading* of the frame here GameInputGamepadState GamepadState; if (!Params.PreviousReading->GetGamepadState(&GamepadState)) { return false; } bool bRes = false; // We only want to process gamepad ANALOG inputs on the last reading because we only care about // the most recent analog stick input value. If there are multiple readings and we processed analog // for every one, then the values would accumulate and stack, giving us incorrect data in the message handler. bRes |= ProcessGamepadAnalogState(Params, GamepadState); // If there were no gamepad events this frame, send button events using the previous frame's reading for button repeats. // This is necessary because GetNextReading won't return any reading if the state is unchanged if (!bProcessedAnyGamepadButtonsThisFrame) { bRes |= ProcessGamepadButtonState(Params, GamepadState); } return bRes; } bool FGameInputGamepadDeviceProcessor::ProcessGamepadAnalogState(const FGameInputEventParams& Params, GameInputGamepadState& GamepadState) { if (!Params.PlatformUserId.IsValid()) { return false; } // ignore this input if the reading has remained empty from last time const bool bIsEmptyReading = UE::GameInput::IsEmptyGamepadReading(GamepadState); const bool bWasEmptyReading = UE::GameInput::IsEmptyGamepadReading(PreviousState); const bool bHasDifferentAnalogInput = UE::GameInput::HasDifferentAnalogInput(GamepadState, PreviousState); if (bIsEmptyReading && bWasEmptyReading && !bHasDifferentAnalogInput) { return false; } OnControllerAnalog(Params, FGamepadKeyNames::LeftAnalogX, GamepadState.leftThumbstickX, PreviousState.leftThumbstickX, UE::GameInput::GamepadLeftStickDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::LeftAnalogY, GamepadState.leftThumbstickY, PreviousState.leftThumbstickY, UE::GameInput::GamepadLeftStickDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::RightAnalogX, GamepadState.rightThumbstickX, PreviousState.rightThumbstickX, UE::GameInput::GamepadRightStickDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::RightAnalogY, GamepadState.rightThumbstickY, PreviousState.rightThumbstickY, UE::GameInput::GamepadRightStickDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::LeftTriggerAnalog, GamepadState.leftTrigger, PreviousState.leftTrigger, UE::GameInput::GamepadTriggerDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::RightTriggerAnalog, GamepadState.rightTrigger, PreviousState.rightTrigger, UE::GameInput::GamepadTriggerDeadzone); // map analog triggers to digital input. uint32 AnalogButtonMask = 0u; if (GamepadState.leftTrigger > UE::GameInput::GamepadTriggerDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_LeftTrigger; } if (GamepadState.rightTrigger > UE::GameInput::GamepadTriggerDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_RightTrigger; } // map left/right stick digital inputs to (top 8 bits) if (GamepadState.leftThumbstickY > UE::GameInput::GamepadLeftStickDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_LeftStickUp; } else if (GamepadState.leftThumbstickY < -UE::GameInput::GamepadLeftStickDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_LeftStickDown; } if (GamepadState.leftThumbstickX > UE::GameInput::GamepadLeftStickDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_LeftStickRight; } else if (GamepadState.leftThumbstickX < -UE::GameInput::GamepadLeftStickDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_LeftStickLeft; } if (GamepadState.rightThumbstickY > UE::GameInput::GamepadRightStickDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_RightStickUp; } else if (GamepadState.rightThumbstickY < -UE::GameInput::GamepadRightStickDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_RightStickDown; } if (GamepadState.rightThumbstickX > UE::GameInput::GamepadRightStickDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_RightStickRight; } else if (GamepadState.rightThumbstickX < -UE::GameInput::GamepadRightStickDeadzone) { AnalogButtonMask |= UE::GameInput::EGameInputAuxButton::GamepadButtonAux_RightStickLeft; } // Evaluate the analog button mask EvaluateButtonStates( Params, AnalogButtonMask, OUT LastAnalogButtonHeldMask, RepeatTime, UE::GameInput::GetGamepadButtonMap(), MaxSupportedButtons); // update saved analog state PreviousState.leftTrigger = GamepadState.leftTrigger; PreviousState.rightTrigger = GamepadState.rightTrigger; PreviousState.leftThumbstickX = GamepadState.leftThumbstickX; PreviousState.leftThumbstickY = GamepadState.leftThumbstickY; PreviousState.rightThumbstickX = GamepadState.rightThumbstickX; PreviousState.rightThumbstickY = GamepadState.rightThumbstickY; return true; } bool FGameInputGamepadDeviceProcessor::ProcessGamepadButtonState(const FGameInputEventParams& Params, GameInputGamepadState& GamepadState) { if (!Params.PlatformUserId.IsValid()) { return false; } // ignore this input if the reading has remained empty from last time, or we still have outstanding held buttons // (these held buttons are likely to occur with mapped analog triggers when there have been multiple input events this frame) const bool bIsEmptyReading = UE::GameInput::IsEmptyGamepadReading(GamepadState); const bool bWasEmptyReading = UE::GameInput::IsEmptyGamepadReading(PreviousState); const bool bHasDifferentAnalogInput = UE::GameInput::HasDifferentAnalogInput(GamepadState, PreviousState); if (bIsEmptyReading && bWasEmptyReading && !bHasDifferentAnalogInput && LastButtonHeldMask == 0) { return false; } // map buttons (low 15 bits) uint32 CurrentButtonHeldMask = (static_cast(GamepadState.buttons) & UE::GameInput::GamingInputButtonMask); EvaluateButtonStates( Params, CurrentButtonHeldMask, OUT LastButtonHeldMask, RepeatTime, UE::GameInput::GetGamepadButtonMap(), MaxSupportedButtons); // Keep track of the current BUTTON state. We don't want to update the entire PreviousState struct here // because buttons may be evaluated more then analog inputs per-frame PreviousState.buttons = GamepadState.buttons; return true; } void FGameInputGamepadDeviceProcessor::ClearState(const FGameInputEventParams& Params) { // We need a valid input device id when sending messages, otherwise slate will hit a check // and attempt to create some new slate user with an invalid index. if (!Params.PlatformUserId.IsValid() || !Params.InputDeviceId.IsValid()) { return; } // Reset Axis values OnControllerAnalog(Params, FGamepadKeyNames::LeftAnalogX, 0.0f, PreviousState.leftThumbstickX, UE::GameInput::GamepadLeftStickDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::LeftAnalogY, 0.0f, PreviousState.leftThumbstickY, UE::GameInput::GamepadLeftStickDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::RightAnalogX, 0.0f, PreviousState.rightThumbstickX, UE::GameInput::GamepadRightStickDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::RightAnalogY, 0.0f, PreviousState.rightThumbstickY, UE::GameInput::GamepadRightStickDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::LeftTriggerAnalog, 0.0f, PreviousState.leftTrigger, UE::GameInput::GamepadTriggerDeadzone); OnControllerAnalog(Params, FGamepadKeyNames::RightTriggerAnalog, 0.0f, PreviousState.rightTrigger, UE::GameInput::GamepadTriggerDeadzone); // Reset button values // Just use 0 as our button mask because we want them all to be set to 0 uint32 CurrentButtonHeldMask = 0; EvaluateButtonStates( Params, CurrentButtonHeldMask, LastButtonHeldMask, RepeatTime, UE::GameInput::GetGamepadButtonMap(), MaxSupportedButtons); // Clear the analog button mask LastAnalogButtonHeldMask = 0u; for (uint32 i = 0; i < MaxSupportedButtons; i++) { RepeatTime[i] = 0.0; } // clear previous gamepad state FMemory::Memset(PreviousState, 0); } GameInputKind FGameInputGamepadDeviceProcessor::GetSupportedReadingKind() const { return GameInputKindGamepad; } //////////////////////////////////////////////////////// // FGameInputControllerDeviceProcessor /** * Returns a map of GameInputLabel's to their associated Unreal Engine FKey names. */ const TMap& FGameInputControllerDeviceProcessor::GetGameInputButtonLabelToUnrealName() { static const TMap LabelMap { { GameInputLabelUnknown, FGamepadKeyNames::Invalid }, { GameInputLabelNone, FGamepadKeyNames::Invalid }, { GameInputLabelXboxGuide, FGamepadKeyNames::SpecialRight }, // TODO: Check if this is correct { GameInputLabelXboxBack, FGamepadKeyNames::SpecialLeft }, { GameInputLabelXboxStart, FGamepadKeyNames::SpecialRight }, { GameInputLabelXboxMenu, FGamepadKeyNames::SpecialRight }, { GameInputLabelXboxView, FGamepadKeyNames::SpecialLeft }, { GameInputLabelXboxA, FGamepadKeyNames::FaceButtonBottom }, { GameInputLabelXboxB, FGamepadKeyNames::FaceButtonRight }, { GameInputLabelXboxX, FGamepadKeyNames::FaceButtonLeft }, { GameInputLabelXboxY, FGamepadKeyNames::FaceButtonTop }, { GameInputLabelXboxDPadUp, FGamepadKeyNames::DPadUp }, { GameInputLabelXboxDPadDown, FGamepadKeyNames::DPadDown }, { GameInputLabelXboxDPadLeft, FGamepadKeyNames::DPadLeft }, { GameInputLabelXboxDPadRight, FGamepadKeyNames::DPadRight }, { GameInputLabelXboxLeftShoulder, FGamepadKeyNames::LeftShoulder }, { GameInputLabelXboxLeftTrigger, FGamepadKeyNames::LeftTriggerAnalog }, { GameInputLabelXboxLeftStickButton, FGamepadKeyNames::LeftThumb }, { GameInputLabelXboxRightShoulder, FGamepadKeyNames::RightShoulder }, { GameInputLabelXboxRightTrigger, FGamepadKeyNames::RightTriggerAnalog }, { GameInputLabelXboxRightStickButton, FGamepadKeyNames::RightThumb }, { GameInputLabelXboxPaddle1, FGamepadKeyNames::Invalid }, // TODO: Do we need special additional FKey's for these paddle types? { GameInputLabelXboxPaddle2, FGamepadKeyNames::Invalid }, // Return invalid for now, but I thought the Xbox One pro controller would { GameInputLabelXboxPaddle3, FGamepadKeyNames::Invalid }, // be handled by the OS itself via virtual remapping { GameInputLabelXboxPaddle4, FGamepadKeyNames::Invalid }, { GameInputLabelLetterA, EKeys::A.GetFName() }, { GameInputLabelLetterB, EKeys::B.GetFName() }, { GameInputLabelLetterC, EKeys::C.GetFName() }, { GameInputLabelLetterD, EKeys::D.GetFName() }, { GameInputLabelLetterE, EKeys::E.GetFName() }, { GameInputLabelLetterF, EKeys::F.GetFName() }, { GameInputLabelLetterG, EKeys::G.GetFName() }, { GameInputLabelLetterH, EKeys::H.GetFName() }, { GameInputLabelLetterI, EKeys::I.GetFName() }, { GameInputLabelLetterJ, EKeys::J.GetFName() }, { GameInputLabelLetterK, EKeys::K.GetFName() }, { GameInputLabelLetterL, EKeys::L.GetFName() }, { GameInputLabelLetterM, EKeys::M.GetFName() }, { GameInputLabelLetterN, EKeys::N.GetFName() }, { GameInputLabelLetterO, EKeys::O.GetFName() }, { GameInputLabelLetterP, EKeys::P.GetFName() }, { GameInputLabelLetterQ, EKeys::Q.GetFName() }, { GameInputLabelLetterR, EKeys::R.GetFName() }, { GameInputLabelLetterS, EKeys::S.GetFName() }, { GameInputLabelLetterT, EKeys::T.GetFName() }, { GameInputLabelLetterU, EKeys::U.GetFName() }, { GameInputLabelLetterV, EKeys::V.GetFName() }, { GameInputLabelLetterW, EKeys::W.GetFName() }, { GameInputLabelLetterX, EKeys::X.GetFName() }, { GameInputLabelLetterY, EKeys::Y.GetFName() }, { GameInputLabelLetterZ, EKeys::Z.GetFName() }, { GameInputLabelNumber0, EKeys::Zero.GetFName() }, { GameInputLabelNumber1, EKeys::One.GetFName() }, { GameInputLabelNumber2, EKeys::Two.GetFName() }, { GameInputLabelNumber3, EKeys::Three.GetFName() }, { GameInputLabelNumber4, EKeys::Four.GetFName() }, { GameInputLabelNumber5, EKeys::Five.GetFName() }, { GameInputLabelNumber6, EKeys::Six.GetFName() }, { GameInputLabelNumber7, EKeys::Seven.GetFName() }, { GameInputLabelNumber8, EKeys::Eight.GetFName() }, { GameInputLabelNumber9, EKeys::Nine.GetFName() }, { GameInputLabelArrowUp, EKeys::Up.GetFName() }, { GameInputLabelArrowUpRight, EKeys::Up.GetFName() }, { GameInputLabelArrowRight, EKeys::Right.GetFName() }, { GameInputLabelArrowDownRight, EKeys::Down.GetFName() }, // TODO: We should support multiple FKey's here, like we do for switches { GameInputLabelArrowDown, EKeys::Down.GetFName() }, { GameInputLabelArrowDownLLeft, EKeys::Down.GetFName() }, { GameInputLabelArrowLeft, EKeys::Left.GetFName() }, { GameInputLabelArrowUpLeft, FGamepadKeyNames::DPadUp }, { GameInputLabelArrowUpDown, FGamepadKeyNames::DPadUp }, { GameInputLabelArrowLeftRight, FGamepadKeyNames::DPadUp }, { GameInputLabelArrowUpDownLeftRight, FGamepadKeyNames::DPadUp }, { GameInputLabelArrowClockwise, FGamepadKeyNames::DPadUp }, // TODO: new key for this { GameInputLabelArrowCounterClockwise, FGamepadKeyNames::DPadUp }, // TODO: new key for this { GameInputLabelArrowReturn, EKeys::Enter.GetFName() }, { GameInputLabelIconBranding, EKeys::Home.GetFName() }, // TODO: I dont think we have a UE key for this, maybe we use home? { GameInputLabelIconHome, FGamepadKeyNames::SpecialRight }, { GameInputLabelIconMenu, FGamepadKeyNames::SpecialLeft }, { GameInputLabelIconCross, FGamepadKeyNames::FaceButtonBottom }, { GameInputLabelIconCircle, FGamepadKeyNames::FaceButtonRight }, { GameInputLabelIconSquare, FGamepadKeyNames::FaceButtonLeft }, { GameInputLabelIconTriangle, FGamepadKeyNames::FaceButtonTop }, { GameInputLabelIconStar, EKeys::Asterix.GetFName() }, // TODO: Star? Is this the asterix? { GameInputLabelIconDPadUp, FGamepadKeyNames::DPadUp }, { GameInputLabelIconDPadDown, FGamepadKeyNames::DPadDown }, { GameInputLabelIconDPadLeft, FGamepadKeyNames::DPadLeft }, { GameInputLabelIconDPadRight, FGamepadKeyNames::DPadRight }, { GameInputLabelIconDialClockwise, FGamepadKeyNames::DPadUp }, { GameInputLabelIconDialCounterClockwise, FGamepadKeyNames::DPadUp }, { GameInputLabelIconSliderLeftRight, FGamepadKeyNames::DPadUp }, { GameInputLabelIconSliderUpDown, FGamepadKeyNames::DPadUp }, { GameInputLabelIconWheelUpDown, FGamepadKeyNames::DPadUp }, { GameInputLabelIconPlus, EKeys::Add.GetFName() }, { GameInputLabelIconMinus, EKeys::Subtract.GetFName() }, { GameInputLabelIconSuspension, FGamepadKeyNames::DPadUp }, { GameInputLabelHome, EKeys::Home.GetFName() }, // TODO: Do we have a gamepad key for guide? { GameInputLabelGuide, FGamepadKeyNames::SpecialLeft }, { GameInputLabelMode, FGamepadKeyNames::SpecialLeft }, { GameInputLabelSelect, FGamepadKeyNames::SpecialRight }, { GameInputLabelMenu, FGamepadKeyNames::SpecialRight }, { GameInputLabelView, FGamepadKeyNames::SpecialLeft }, { GameInputLabelBack, FGamepadKeyNames::SpecialLeft }, { GameInputLabelStart, FGamepadKeyNames::SpecialRight }, { GameInputLabelOptions, FGamepadKeyNames::SpecialRight }, { GameInputLabelShare, FGamepadKeyNames::SpecialLeft }, { GameInputLabelUp, FGamepadKeyNames::DPadUp }, { GameInputLabelDown, FGamepadKeyNames::DPadDown }, { GameInputLabelLeft, FGamepadKeyNames::DPadLeft }, { GameInputLabelRight, FGamepadKeyNames::DPadRight }, { GameInputLabelLB, FGamepadKeyNames::LeftShoulder }, { GameInputLabelLT, FGamepadKeyNames::LeftTriggerAnalog }, { GameInputLabelLSB, FGamepadKeyNames::LeftShoulder }, { GameInputLabelL1, FGamepadKeyNames::LeftShoulder }, { GameInputLabelL2, FGamepadKeyNames::LeftTriggerAnalog }, { GameInputLabelL3, FGamepadKeyNames::DPadUp }, { GameInputLabelRB, FGamepadKeyNames::RightShoulder }, { GameInputLabelRT, FGamepadKeyNames::RightTriggerAnalog }, { GameInputLabelRSB, FGamepadKeyNames::RightShoulder }, { GameInputLabelR1, FGamepadKeyNames::RightShoulder }, { GameInputLabelR2, FGamepadKeyNames::RightTriggerAnalog }, { GameInputLabelR3, FGamepadKeyNames::DPadUp }, { GameInputLabelP1, FGamepadKeyNames::DPadUp }, { GameInputLabelP2, FGamepadKeyNames::DPadUp }, // what are these? More paddle types? { GameInputLabelP3, FGamepadKeyNames::DPadUp }, { GameInputLabelP4, FGamepadKeyNames::DPadUp } }; return LabelMap; } FGameInputControllerDeviceProcessor::FGameInputControllerDeviceProcessor() : IGameInputDeviceProcessor() { // Ensure that our switch repeat times array has some default values set on it by default // because we access it with the [] operator when processing it. SwitchRepeatTimes.AddDefaulted(static_cast(GameInputSwitchUpLeft) + 1); } bool FGameInputControllerDeviceProcessor::ProcessInput(const FGameInputEventParams& Params) { bool bRes = false; const FGameInputDeviceConfiguration* ControllerConfig = GetDefault()->FindDeviceConfiguration(Params.GetDeviceInfo()); // Note that we use the current reading here. "ProcessInput" can be called multiple times per frame // if there is more then one input reading in the stack. We want to process all button and switch states // to ensure that we don't miss one bRes |= ProcessControllerSwitchState(Params, ControllerConfig, Params.Reading); bRes |= ProcessControllerButtonState(Params, ControllerConfig, Params.Reading); ++NumReadingsProcessedThisFrame; return bRes; } bool FGameInputControllerDeviceProcessor::PostProcessInput(const FGameInputEventParams& Params) { // On the last input reading for the frame, the "Current Reading" should always be null. we only care for the LastReading here ensure(Params.Reading == nullptr); bool bRes = false; const FGameInputDeviceConfiguration* ControllerConfig = GetDefault()->FindDeviceConfiguration(Params.GetDeviceInfo()); const bool bProcessedAnyButtonsThisFrame = (NumReadingsProcessedThisFrame > 0); // This is the last reading of this frame, reset the counter to 0 NumReadingsProcessedThisFrame = 0; // Note that we use "Params.PreviousReading", because the current reading will be null. if (!bProcessedAnyButtonsThisFrame) { bRes |= ProcessControllerSwitchState(Params, ControllerConfig, Params.PreviousReading); bRes |= ProcessControllerButtonState(Params, ControllerConfig, Params.PreviousReading); } bRes |= ProcessControllerAxisState(Params, ControllerConfig, Params.PreviousReading); return bRes; } void FGameInputControllerDeviceProcessor::ClearState(const FGameInputEventParams& Params) { // Reset the switches to all be "center" positions { const uint32 SwitchCount = static_cast(PreviousSwitchPositions.Num()); TArray SwitchPositions; SwitchPositions.AddUninitialized(SwitchCount); // Evaluate all switch positions for (uint32 i = 0; i < SwitchCount; ++i) { // The PreviousSwitchPositions will be updated to the current switch position by the evaluate function EvaluateSwitchState( Params, SwitchPositions[i], OUT PreviousSwitchPositions[i], SwitchRepeatTimes); } } if (const FGameInputDeviceConfiguration* Config = GetDefault()->FindDeviceConfiguration(Params.GetDeviceInfo())) { // Reset the button state to not being pressed // Just use 0 as our button mask because we want them all to be set to 0 uint32 CurrentButtonHeldMask = 0; EvaluateButtonStates( Params, CurrentButtonHeldMask, LastButtonHeldMask, RepeatTime, Config->ControllerButtonMappingData, MaxSupportedButtons); // Reset the axis state to be zero on all axis that we had in the previous state for (int32 i = 0; i < PreviousControllerAxisValues.Num(); ++i) { if (const FGameInputControllerAxisData* AxisData = Config->ControllerAxisMappingData.Find(i)) { OnControllerAnalog(Params, AxisData->KeyName, 0.0f, PreviousControllerAxisValues[i], AxisData->DeadZone); } } FMemory::Memset(PreviousControllerAxisValues, 0); } } GameInputKind FGameInputControllerDeviceProcessor::GetSupportedReadingKind() const { return GameInputKindController | GameInputKindControllerAxis | GameInputKindControllerButton; } bool FGameInputControllerDeviceProcessor::ProcessControllerButtonState(const FGameInputEventParams& Params, const FGameInputDeviceConfiguration* ControllerConfig, IGameInputReading* InputReading) { // We can only process generic controllers that have their config set up // but allow you to run without a config to log out button indexes to make it // easier to configure your device if (ControllerConfig && !ControllerConfig->bProcessControllerButtons) { return false; } const uint32 ButtonCount = InputReading->GetControllerButtonCount(); TArray ButtonStates; ButtonStates.AddUninitialized(ButtonCount); const uint32 Res = InputReading->GetControllerButtonState(ButtonCount, ButtonStates.GetData()); if (!Res) { return false; } uint32 CurrentButtonHeldMask = 0x00; for (uint32 i = 0; i < ButtonCount; ++i) { const FName* KeyName = ControllerConfig ? ControllerConfig->ControllerButtonMappingData.Find(1 << i) : nullptr; CurrentButtonHeldMask |= (ButtonStates[i] << i); if (ButtonStates[i]) { UE_LOG(LogGameInput, Verbose, TEXT("[ProcessControllerButtonState] Device ID: %d Button count: %d index: %d State: %d %s"), Params.InputDeviceId.GetId(), ButtonCount, i, ButtonStates[i], KeyName ? *(KeyName->ToString()) : TEXT("NONE")); } } if (ControllerConfig) { EvaluateButtonStates( Params, CurrentButtonHeldMask, OUT LastButtonHeldMask, RepeatTime, ControllerConfig->ControllerButtonMappingData, ButtonCount ); } // Note: Ideally we would try and use the GameInput label system, // but it is currently unfinished within the GameInput API itself so we can't. // We have provided this device specific config driven option instead. return true; } bool FGameInputControllerDeviceProcessor::ProcessControllerAxisState(const FGameInputEventParams& Params, const FGameInputDeviceConfiguration* Config, IGameInputReading* InputReading) { if (Config && !Config->bProcessControllerAxis) { return false; } const uint32 AxisCount = InputReading->GetControllerAxisCount(); TArray AxisValues; AxisValues.AddZeroed(AxisCount); if (!InputReading->GetControllerAxisState(AxisCount, AxisValues.GetData())) { return false; } // Make sure that there is previous controller data initialized if necessary while (PreviousControllerAxisValues.Num() < static_cast(AxisCount)) { PreviousControllerAxisValues.AddZeroed(); } for (uint32 i = 0; i < AxisCount; ++i) { float CurrentValue = AxisValues[i]; const float PreviousValue = PreviousControllerAxisValues[i]; if (Config) { if (const FGameInputControllerAxisData* AxisData = Config->ControllerAxisMappingData.Find(i)) { if (AxisData->KeyName.IsValid() && AxisData->KeyName != NAME_None) { if (AxisData->bIsPackedPositveAndNegative) { // Maps the value to be -1.0 to +1.0 CurrentValue = (CurrentValue * 2.f) - 1.f; } CurrentValue *= AxisData->Scalar; OnControllerAnalog(Params, AxisData->KeyName, CurrentValue, PreviousValue, AxisData->DeadZone); // Store this value for the next frame to compare to PreviousControllerAxisValues[i] = CurrentValue; } else { UE_LOG(LogGameInput, VeryVerbose, TEXT("[ProcessControllerAxisState] (Device %s) Invalid key name configured for controller axis '%d': %.3f"), *UE::GameInput::LexToString(Params.Device), i, CurrentValue); } } else { // TODO: Here is where could send a "Generic USB Axis X" key here which could allow for us to support many more devices via a key rebind screen UE_LOG(LogGameInput, VeryVerbose, TEXT("[ProcessControllerAxisState] (Device %s) Controller axis '%d' has value: %.3f"), *UE::GameInput::LexToString(Params.Device), i, CurrentValue); } } // You are receiving analog values from an axis that you might not know about, log it here // in case you are trying to set something up else { UE_LOG(LogGameInput, VeryVerbose, TEXT("[ProcessControllerAxisState] (Device %s) Receiving input from an unconfigured controller axis '%d': %.3f"), *UE::GameInput::LexToString(Params.Device), i, CurrentValue); } } return false; } bool FGameInputControllerDeviceProcessor::ProcessControllerSwitchState(const FGameInputEventParams& Params, const FGameInputDeviceConfiguration* Config, IGameInputReading* InputReading) { if (Config && !Config->bProcessControllerSwitchState) { return false; } const uint32 SwitchCount = InputReading->GetControllerSwitchCount(); TArray SwitchPositions; SwitchPositions.AddUninitialized(SwitchCount); // Make sure that we have some previous state initialized if we can if (static_cast(PreviousSwitchPositions.Num()) < SwitchCount) { PreviousSwitchPositions.AddDefaulted(SwitchCount); } uint32 Res = InputReading->GetControllerSwitchState(SwitchCount, SwitchPositions.GetData()); if (!Res) { return false; } // Evaluate all switch positions for (uint32 i = 0; i < SwitchCount; ++i) { // The PreviousSwitchPositions will be updated to the current switch position by the evaluate function EvaluateSwitchState( Params, SwitchPositions[i], OUT PreviousSwitchPositions[i], SwitchRepeatTimes); } return true; } //////////////////////////////////////////////////////// // FGameInputKeyboardDeviceProcessor static float GKeyboardRepeatInitialDelay = 0.25f; static FAutoConsoleVariableRef CVarKeyboardRepeatInitialDelay( TEXT("Input.KeyboardRepeatInitialDelay"), GKeyboardRepeatInitialDelay, TEXT("Time in seconds before a key repeat starts")); static float GKeyboardRepeatDelay = 0.05f; static FAutoConsoleVariableRef CVarKeyboardRepeatDelay( TEXT("Input.KeyboardRepeatDelay"), GKeyboardRepeatDelay, TEXT("Time in seconds between each subsequent key repeat")); bool FGameInputKeyboardDeviceProcessor::ProcessInput(const FGameInputEventParams& Params) { TSet CurrentPressedKeys; int32 KeyCount = Params.Reading->GetKeyCount(); if (KeyCount > 0) { // read the key state TArray KeyStates; KeyStates.AddUninitialized(KeyCount); int32 ReadCount = Params.Reading->GetKeyState(KeyCount, KeyStates.GetData()); // build a set of the pressed keycodes for (GameInputKeyState KeyState : KeyStates) { CurrentPressedKeys.Add(KeyState.virtualKey); } } UpdateUnifiedKeyboardState(Params, CurrentPressedKeys); return true; } void FGameInputKeyboardDeviceProcessor::UpdateUnifiedKeyboardState(const FGameInputEventParams& Params, TSet& CurrentPressedKeys) { // process unified pressed keys double CurrentTime = FPlatformTime::Seconds(); for (uint8 KeyCode : CurrentPressedKeys) { bool bIsRepeat = LastPressedKeys.Contains(KeyCode); if (!bIsRepeat) { KeyRepeatTime.Add(KeyCode, CurrentTime + GKeyboardRepeatInitialDelay); Params.MessageHandler->OnKeyDown(KeyCode, 0, false); UE_LOG(LogGameInput, Verbose, TEXT("Key Press 0x%X"), KeyCode); if (KeyCode == VK_CAPITAL) { SetSimulatedCapsLock(!bSimulatedCapsLock); UE_LOG(LogGameInput, Verbose, TEXT("Simulated caps lock is %s"), bSimulatedCapsLock ? TEXT("ON") : TEXT("OFF")); } } else if (CurrentTime > KeyRepeatTime[KeyCode]) { KeyRepeatTime.Add(KeyCode, CurrentTime + GKeyboardRepeatDelay); Params.MessageHandler->OnKeyDown(KeyCode, 0, true); UE_LOG(LogGameInput, Verbose, TEXT("Key Press 0x%X (repeat)"), KeyCode); } } // process any released keys for (uint8 KeyCode : LastPressedKeys) { if (!CurrentPressedKeys.Contains(KeyCode)) { KeyRepeatTime.Remove(KeyCode); Params.MessageHandler->OnKeyUp(KeyCode, 0, false); UE_LOG(LogGameInput, Verbose, TEXT("Key Release 0x%X"), KeyCode); } } // update saved state LastPressedKeys = CurrentPressedKeys; } void FGameInputKeyboardDeviceProcessor::SetSimulatedCapsLock(bool bVal) { bSimulatedCapsLock = bVal; } void FGameInputKeyboardDeviceProcessor::ClearState(const FGameInputEventParams& Params) { TSet NoPressedKeys; UpdateUnifiedKeyboardState(Params, NoPressedKeys); } GameInputKind FGameInputKeyboardDeviceProcessor::GetSupportedReadingKind() const { return GameInputKindKeyboard; } //////////////////////////////////////////////////////// // FGameInputMouseDeviceProcessor static int GAllowVirtualMouseInput = 1; static FAutoConsoleVariableRef CVarAllowVirtualMouseInput( TEXT("GameInput.AllowVirtualMouseInput"), GAllowVirtualMouseInput, TEXT("Whether to accept input from virtual mice, such as those from a remote viewer. Note that this doesn't change whether the mouse is 'connected'")); static float GMouseSensitivity = 1.0f; static FAutoConsoleVariableRef CVarMouseSensitivity( TEXT("Input.MouseSensitivity"), GMouseSensitivity, TEXT("The sensitivity multiplier of the mouse\n") TEXT(" 1 (default)"), ECVF_Default); static float GMouseDoubleClickArea = 10.0f; static FAutoConsoleVariableRef CVarMouseDoubleClickArea( TEXT("Input.DoubleClickArea"), GMouseDoubleClickArea, TEXT("How far the mouse can move between double clicks to still count as a double click")); static float GMouseDoubleClickDelay = 0.5f; static FAutoConsoleVariableRef CVarMouseDoubleClickDelay( TEXT("Input.DoubleClickDelay"), GMouseDoubleClickDelay, TEXT("Time in seconds between mouse down events to trigger a double click event. Set to 0 to disable double clicking")); namespace UE::GameInput { struct FMouseButtonMapping { uint32 GameInputButton; EMouseButtons::Type MouseButton; }; static const FMouseButtonMapping MouseButtonMappings[] = { { static_cast(GameInputMouseButtons::GameInputMouseLeftButton), EMouseButtons::Left }, { static_cast(GameInputMouseButtons::GameInputMouseRightButton), EMouseButtons::Right }, { static_cast(GameInputMouseButtons::GameInputMouseMiddleButton), EMouseButtons::Middle }, { static_cast(GameInputMouseButtons::GameInputMouseButton4), EMouseButtons::Thumb01 }, { static_cast(GameInputMouseButtons::GameInputMouseButton5), EMouseButtons::Thumb02 }, }; } FGameInputMouseDeviceProcessor::FGameInputMouseDeviceProcessor(const TSharedPtr& InCursor) : Cursor(InCursor) , LastMouseOffset(ForceInitToZero) { FMemory::Memset(PreviousMouseState, 0); for (uint32 i = 0; i < MaxSupportedButtons; i++) { RepeatTime[i] = 0.0; } } bool FGameInputMouseDeviceProcessor::CanProcessVirtualMouse() const { // ignore the input if requested if (GAllowVirtualMouseInput == 0) { return false; } return true; } bool FGameInputMouseDeviceProcessor::ProcessInput(const FGameInputEventParams& Params) { // read mouse state GameInputMouseState MouseState; if (!Params.Reading->GetMouseState(&MouseState)) { return false; } int32 LEGACY_VirtualMaxX = MAX_int32; int32 LEGACY_VirtualMaxY = MAX_int32; const GameInputDeviceInfo* DeviceInfo = Params.GetDeviceInfo(); const bool bIsVirtualMouse = DeviceInfo ? DeviceInfo->deviceFamily == GameInputFamilyVirtual : false; const bool bHighPrecisionMouseMode = FSlateApplication::Get().IsUsingHighPrecisionMouseMovment(); if (bIsVirtualMouse) { if (!CanProcessVirtualMouse()) { return false; } } // update mouse position float MouseDX, MouseDY = 0.0f; int32 RawMouseDX = static_cast(MouseState.positionX) - static_cast(PreviousMouseState.positionX); int32 RawMouseDY = static_cast(MouseState.positionY) - static_cast(PreviousMouseState.positionY); if (bHighPrecisionMouseMode) { MouseDX = static_cast(RawMouseDX); MouseDY = static_cast(RawMouseDY); } else { MouseDX = UE::Cursor::CalculateDeltaWithAcceleration(RawMouseDX, GMouseSensitivity); MouseDY = UE::Cursor::CalculateDeltaWithAcceleration(RawMouseDY, GMouseSensitivity); } if (MouseDX != 0 || MouseDY != 0) { if (Cursor.IsValid()) { FVector2D CursorPos = Cursor->GetPosition(); CursorPos.X += MouseDX; CursorPos.Y += MouseDY; Cursor->SetPosition((int32)CursorPos.X, (int32)CursorPos.Y); } // LastMouseOffset is used for double-click detection, so we should use the same coordinates as the cursor i.e. the processed ones. LastMouseOffset.X += MouseDX; LastMouseOffset.Y += MouseDY; UE_LOG(LogGameInput, VeryVerbose, TEXT("Device %s (InputDeviceId = %d) - Mouse RawDX %d, RawDY %d, DX %0.2f, DY %.2f %s"), *UE::GameInput::LexToString(Params.Device), Params.InputDeviceId.GetId(), RawMouseDX, RawMouseDY, MouseDX, MouseDY, bHighPrecisionMouseMode ? TEXT("(high precision)") : TEXT("")); // OnRawMouseMove may be used to process player character controls so, as the name suggests, these should be raw data without any smoothing. Params.MessageHandler->OnRawMouseMove(RawMouseDX, RawMouseDY); } // update mouse wheel const float MouseWheelSpinFactor = 1 / 120.0f; float MouseWheelDX = (float)(MouseState.wheelX - PreviousMouseState.wheelX); float MouseWheelDY = (float)(MouseState.wheelY - PreviousMouseState.wheelY); if (MouseWheelDY != 0) { UE_LOG(LogGameInput, Verbose, TEXT("Device %s (InputDeviceId = %d) - Mouse Wheel DY %.2f"), *UE::GameInput::LexToString(Params.Device), Params.InputDeviceId.GetId(), MouseWheelDY); Params.MessageHandler->OnMouseWheel(MouseWheelDY * MouseWheelSpinFactor); } // handle button change events uint32 CurrentButtonHeldMask = (uint32)MouseState.buttons; uint32 ActionMask = (LastButtonHeldMask ^ CurrentButtonHeldMask); uint32 RepeatMask = (LastButtonHeldMask & CurrentButtonHeldMask); double CurrentTime = FPlatformTime::Seconds(); for (int ButtonIndex = 0; ButtonIndex < UE_ARRAY_COUNT(UE::GameInput::MouseButtonMappings); ButtonIndex++) { const uint32 GameInputButton = UE::GameInput::MouseButtonMappings[ButtonIndex].GameInputButton; if ((ActionMask & GameInputButton) == 0) { continue; } const EMouseButtons::Type MouseButton = UE::GameInput::MouseButtonMappings[ButtonIndex].MouseButton; if ((CurrentButtonHeldMask & GameInputButton) != 0) { const bool bHasDoubleClick = (GMouseDoubleClickDelay > 0) && (CurrentTime <= RepeatTime[ButtonIndex]) && (LastMouseOffset.SizeSquared() <= FMath::Square(GMouseDoubleClickArea)); if (bHasDoubleClick) { UE_LOG(LogGameInput, Verbose, TEXT("Device %s (DeviceIndex = %d) - %s Double Click"), *UE::GameInput::LexToString(Params.Device), Params.InputDeviceId.GetId(), UE::GameInput::GetMouseButtonName(MouseButton)); Params.MessageHandler->OnMouseDoubleClick(nullptr, MouseButton); } else { UE_LOG(LogGameInput, Verbose, TEXT("Device %s (DeviceIndex = %d) - %s Pressed"), *UE::GameInput::LexToString(Params.Device), Params.InputDeviceId.GetId(), UE::GameInput::GetMouseButtonName(MouseButton)); Params.MessageHandler->OnMouseDown(nullptr, MouseButton); } RepeatTime[ButtonIndex] = CurrentTime + GMouseDoubleClickDelay; LastMouseOffset = FVector2D::ZeroVector; } else { UE_LOG(LogGameInput, Verbose, TEXT("Device %s (DeviceIndex = %d) - %s Released"), *UE::GameInput::LexToString(Params.Device), Params.InputDeviceId.GetId(), UE::GameInput::GetMouseButtonName(MouseButton)); Params.MessageHandler->OnMouseUp(MouseButton); } } PreviousMouseState = MouseState; LastButtonHeldMask = CurrentButtonHeldMask; return true; } void FGameInputMouseDeviceProcessor::ClearState(const FGameInputEventParams& Params) { // clear buttons for (int32 ButtonIndex = 0; ButtonIndex < UE_ARRAY_COUNT(UE::GameInput::MouseButtonMappings); ButtonIndex++) { uint32 GameInputButton = UE::GameInput::MouseButtonMappings[ButtonIndex].GameInputButton; if ((GameInputButton & LastButtonHeldMask) != 0) { EMouseButtons::Type MouseButton = UE::GameInput::MouseButtonMappings[ButtonIndex].MouseButton; UE_LOG(LogGameInput, Verbose, TEXT("Device %s (InputDeviceId = %d) - %s Released (via ClearState)"), *UE::GameInput::LexToString(Params.Device), Params.InputDeviceId.GetId(), UE::GameInput::GetMouseButtonName(MouseButton)); Params.MessageHandler->OnMouseUp(MouseButton); } } // Clear repeat times for (uint32 i = 0; i < MaxSupportedButtons; i++) { RepeatTime[i] = 0.0; } // clear previous mouse state LastMouseOffset = FVector2D::ZeroVector; FMemory::Memset(PreviousMouseState, 0); } GameInputKind FGameInputMouseDeviceProcessor::GetSupportedReadingKind() const { return GameInputKindMouse; } //////////////////////////////////////////////////////// // FGameInputTouchDeviceProcessor bool FGameInputTouchDeviceProcessor::ProcessInput(const FGameInputEventParams& Params) { // read the new touch events int32 TouchCount = Params.Reading->GetTouchCount(); TArray InputTouchData; InputTouchData.SetNum(TouchCount, EAllowShrinking::No); Params.Reading->GetTouchState(TouchCount, InputTouchData.GetData()); // no new touch events and we dont have any previous inputs if (TouchCount == 0 && ActiveTouchPoints == 0) { return false; } // We can't tell slate about input messages from an invalid platform user if (!Params.PlatformUserId.IsValid()) { UE_LOG(LogGameInput, Verbose, TEXT("[FGameInputTouchDeviceProcessor::EvaluateButtonStates] Attempting to evaluate button states with an invalid platform user id of '%d'. The button messages will not be sent."), Params.PlatformUserId.GetInternalId()); return false; } auto FindOrAddTouch = [](TArray& TouchStates, const GameInputTouchState& NewTouchData) -> int32 { int32 Index = TouchStates.IndexOfByPredicate([&](const FTouchData& A) {return A.TouchId == NewTouchData.touchId; }); if (Index == INDEX_NONE) { // check if we have a free slot Index = TouchStates.IndexOfByPredicate([&](const FTouchData& A) {return A.TouchId == INDEX_NONE; }); // no free slot - create a new one. (NB. We will never receive more than GameInputDeviceInfo::touchPointCount separate touches) if (Index == INDEX_NONE) { Index = TouchStates.Add(FTouchData()); } } return Index; }; // Reset the active touch state from the previous frame for (FTouchData& TouchData : PreviousTouchData) { TouchData.bIsActive = false; } // process all previous frame events and check if there are in list of new events for this frame for (const GameInputTouchState& InputTouch : InputTouchData) { // search for the same touchId in the new events list int32 Index = FindOrAddTouch(PreviousTouchData, InputTouch); if (Index == INDEX_NONE) { continue; } FTouchData& TouchState = PreviousTouchData[Index]; // need to scale by resolution FVector2D NewPosition = FVector2D(InputTouch.positionX * (float)MaxTouchX, InputTouch.positionY * (float)MaxTouchY).RoundToVector(); if (TouchState.TouchId == INDEX_NONE) { Params.MessageHandler->OnTouchStarted(nullptr, NewPosition, InputTouch.pressure, Index, Params.PlatformUserId, Params.InputDeviceId); TouchState.TouchId = InputTouch.touchId; ActiveTouchPoints++; } else if (NewPosition != TouchState.Position) { if (!TouchState.bHasMoved) { Params.MessageHandler->OnTouchFirstMove(NewPosition, InputTouch.pressure, Index, Params.PlatformUserId, Params.InputDeviceId); TouchState.bHasMoved = true; } else { Params.MessageHandler->OnTouchMoved(NewPosition, InputTouch.pressure, Index, Params.PlatformUserId, Params.InputDeviceId); } } if (TouchState.Pressure != InputTouch.pressure) { Params.MessageHandler->OnTouchForceChanged(NewPosition, InputTouch.pressure, Index, Params.PlatformUserId, Params.InputDeviceId); } TouchState.Pressure = InputTouch.pressure; TouchState.Position = NewPosition; TouchState.bIsActive = true; } // process all new event that where not in the stack for (int32 Index = 0; Index < PreviousTouchData.Num(); Index++) { FTouchData& TouchData = PreviousTouchData[Index]; if (!TouchData.bIsActive && TouchData.TouchId != INDEX_NONE) { Params.MessageHandler->OnTouchEnded(TouchData.Position, Index, Params.PlatformUserId, Params.InputDeviceId); TouchData = FTouchData(); ActiveTouchPoints--; } } check(ActiveTouchPoints >= 0); return true; } void FGameInputTouchDeviceProcessor::ClearState(const FGameInputEventParams& Params) { // There is nothing to be done for touch data clearing... } GameInputKind FGameInputTouchDeviceProcessor::GetSupportedReadingKind() const { return GameInputKindTouch; } constexpr float FGameInputRawDeviceProcessor::RawValueToFloatTrigger(const uint8 RawValue) const { // Maps the uint8 value of 0-255 to a float between 0.0 and +1.0, like a gamepad trigger. return (static_cast(RawValue) / 255.f); } const float FGameInputRawDeviceProcessor::RawValueToFloatAnalog(uint8 RawValue, const uint8 DeadZone /* = 2 */) const { // Apply a simple square deadzone... { // Remember, we are mapping a uint8 (0-255) to a float of -1.0 and +1.0. So, any value // between 0 and 127 is negative and 129-255 is positive. 128 is the center. // Calculate the offset of how far this raw value is from center const int32 MaxOffset = FMath::Abs(RawValue - 128); // TODO: Implement a better deadzone then this, this is a square one // If we are within the deadzone, set this value to 128 which will translate to 0.0f if (MaxOffset <= DeadZone) { RawValue = 128; } } // Maps the uint8 value to a float between -1.0 and +1.0 return ((static_cast(RawValue) * (2.f / 255.f)) - 1.f); } const GameInputRawDeviceReportInfo* FGameInputRawDeviceProcessor::ReadCurrentRawInputState(const FGameInputEventParams& Params, IGameInputReading* ReadingToUse) { if (!ReadingToUse) { UE_LOG(LogGameInput, Error, TEXT("[ReadCurrentRawInputState] Cannot read raw input state, ReadingToUse was null (Device %s) "), *UE::GameInput::LexToString(Params.Device)); return nullptr; } TComPtr RawReport; const bool bSuccessfulReading = ReadingToUse->GetRawReport(&RawReport); if (!bSuccessfulReading) { UE_LOG(LogGameInput, Error, TEXT("[ReadCurrentRawInputState] Unsuccessful reading of raw input report! (GetRawReport failed) (Device %s) "), *UE::GameInput::LexToString(Params.Device)); return nullptr; } const int32 RawRepDataSize = RawReport->GetRawDataSize(); // Ensure that the current data array is populated to the size needed. CurrentRawData.Reset(); CurrentRawData.AddZeroed(RawRepDataSize); // Ensure that the previous data array is populated to the size needed so that we can compare its values per-index if (PreviousRawData.Num() < RawRepDataSize) { PreviousRawData.AddZeroed(RawRepDataSize - PreviousRawData.Num()); } // This will populate the values in the CurrentRawData so that we can process them const int32 NumReadBytes = RawReport->GetRawData(RawReport->GetRawDataSize(), CurrentRawData.GetData()); const GameInputRawDeviceReportInfo* RawReportInfo = RawReport->GetReportInfo(); if (!RawReportInfo) { UE_LOG(LogGameInput, Warning, TEXT("[ProcessRawReport] Unsuccessful reading of raw input report! (GameInputRawDeviceReportInfo is null) (Device %s) "), *UE::GameInput::LexToString(Params.Device)); } return RawReportInfo; } bool FGameInputRawDeviceProcessor::ProcessInput(const FGameInputEventParams& Params) { bool bRes = false; // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid()) { return bRes; } const FGameInputDeviceConfiguration* DeviceConfig = GetDefault()->FindDeviceConfiguration(Params.GetDeviceInfo()); // Check that we have a valid config before bothering to read the raw report if (!DeviceConfig) { UE_LOG(LogGameInput, Verbose, TEXT("[ProcessRawReport] (Device %s) Does not have a valid FGameInputDeviceConfiguration in the UGameInputDeveloperSettings. We can't process Raw Input without it. Exiting."), *UE::GameInput::LexToString(Params.Device)); return bRes; } // Skip if this device config isn't even supposed to be processing raw input values if (!DeviceConfig->bProcessRawReportData) { return bRes; } // Read from the current reading the CURRENT frame const GameInputRawDeviceReportInfo* RawReportInfo = ReadCurrentRawInputState(Params, Params.Reading); // Actually process the current raw input values if this reading ID matches the one we want if (RawReportInfo && RawReportInfo->id == DeviceConfig->RawReportReadingId) { // Process the BUTTON types here, we only want to process analog events once per frame to avoid over-accumulation constexpr bool bShouldProcessButtons = true; constexpr bool bShouldProcessAnalog = false; bRes |= ProcessAllRawValues(Params, DeviceConfig, bShouldProcessButtons, bShouldProcessAnalog); ++NumReadingsProcessedThisFrame; } return bRes; } bool FGameInputRawDeviceProcessor::PostProcessInput(const FGameInputEventParams& Params) { bool bRes = false; const bool bProcessedAnyGamepadButtonsThisFrame = (NumReadingsProcessedThisFrame > 0); // This is the last reading of this frame, reset the counter to 0 NumReadingsProcessedThisFrame = 0; // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid()) { return bRes; } const FGameInputDeviceConfiguration* DeviceConfig = GetDefault()->FindDeviceConfiguration(Params.GetDeviceInfo()); // Check that we have a valid config before bothering to read the raw report if (!DeviceConfig) { UE_LOG(LogGameInput, Verbose, TEXT("[ProcessRawReport] (Device %s) Does not have a valid FGameInputDeviceConfiguration in the UGameInputDeveloperSettings. We can't process Raw Input without it. Exiting."), *UE::GameInput::LexToString(Params.Device)); return bRes; } // On the last input reading for the frame, the "Current Reading" should always be null. we only care for the LastReading here ensure(Params.Reading == nullptr); if (!Params.PreviousReading) { return bRes; } // Read from the current reading the PREVIOUS frame const GameInputRawDeviceReportInfo* RawReportInfo = ReadCurrentRawInputState(Params, Params.PreviousReading); // Actually process the current raw input values if this reading ID matches the one we want if (RawReportInfo && RawReportInfo->id == DeviceConfig->RawReportReadingId) { // We always want to process analog events on the last frame constexpr bool bShouldPorcessAnalog = true; // We only need to process buttons this frame too if there have been no other readings yet. const bool bNeedToProcessButtons = !bProcessedAnyGamepadButtonsThisFrame; bRes |= ProcessAllRawValues(Params, DeviceConfig, bNeedToProcessButtons, bShouldPorcessAnalog); } // Track our previous input only on the last input frame. We don't want any duplicate readings PreviousRawData = CurrentRawData; return bRes; } bool FGameInputRawDeviceProcessor::ProcessAllRawValues(const FGameInputEventParams& Params, const FGameInputDeviceConfiguration* DeviceConfig, const bool bShouldProcessButtons, const bool bShouldProcessAnalog) { ensure(CurrentRawData.Num() == PreviousRawData.Num()); ensure(Params.PlatformUserId.IsValid()); bool bRes = false; for (int32 i = 0; i < CurrentRawData.Num(); ++i) { const uint8 Val = CurrentRawData[i]; if (const FGameInputRawDeviceReportData* AxisData = DeviceConfig->RawReportMappingData.Find(i)) { if (AxisData->TranslationBehavior == ERawDeviceReportTranslationBehavior::TreatAsButtonBitmask) { if (bShouldProcessButtons) { bRes |= ProcessRawInputValueAsBitmask(Params, i, AxisData); } } else if (AxisData->TranslationBehavior == ERawDeviceReportTranslationBehavior::TreatAsPackedAxisPair) { // Only call this function on the higher index if (bShouldProcessAnalog && i == AxisData->HigherBitAxisIndex) { ProcessRawInputValueAsAanalogPaired(Params, AxisData); } } // All other methods require a valid key name on the config else if (AxisData->KeyName.IsValid()) { // Treat this value as a button. If it is non-zero then consider it pressed. If it is zero, then it is not pressed. if (AxisData->TranslationBehavior == ERawDeviceReportTranslationBehavior::TreatAsButton) { if (bShouldProcessButtons) { bRes |= ProcessRawInputValueAsButton(Params, i, AxisData); } } // Otherwise we can do analog values, which can be either "trigger" or "analog" types else if (bShouldProcessAnalog) { bRes |= ProcessRawInputValueAsAanalog(Params, i, AxisData); } } else { // You want a valid key name here, throw a warning if your config is wrong UE_LOG(LogGameInput, Warning, TEXT("[ProcessRawReport] Invalid key name for raw report axis at index %d with value of %u (Device %s)"), i, Val, *UE::GameInput::LexToString(Params.Device)); } } else if (Val > 0) { UE_LOG(LogGameInput, VeryVerbose, TEXT("[ProcessRawReport] (Device %s) No raw device report config for axis '%d' with value of %u"), *UE::GameInput::LexToString(Params.Device), i, Val); } } return bRes; } bool FGameInputRawDeviceProcessor::ProcessRawInputValueAsBitmask(const FGameInputEventParams& Params, const int32 RawValueIndex, const FGameInputRawDeviceReportData* AxisData) { check(AxisData && AxisData->TranslationBehavior == ERawDeviceReportTranslationBehavior::TreatAsButtonBitmask); const uint8 Val = CurrentRawData[RawValueIndex]; const uint8 PrevVal = PreviousRawData[RawValueIndex]; // Ensure we have a compatible key map setup FPerRawInputIndexData& IndexData = RawInputIndexDataMap.FindOrAdd(RawValueIndex); if (IndexData.KeyNameMap.IsEmpty()) { // The settings use their button map as a bit number, so the key map we actually need to use // is 1 << that bit to have EvaluateButtonStates work correctly for (const TPair& MappingPair : AxisData->ButtonBitMaskMappings) { IndexData.KeyNameMap.Add({ 1 << MappingPair.Key, MappingPair.Value }); } } const uint32 CurrentValue32 = static_cast(Val); uint32 PreviousValue32 = static_cast(PrevVal); // Note: Set the max supported buttons here to 1 because we only care about the first bit EvaluateButtonStates( Params, CurrentValue32, OUT PreviousValue32, IndexData.RepeatTime, IndexData.KeyNameMap, FPerRawInputIndexData::MaxSupportedButtons); // If this value is non-zero then we had a reading return Val != 0; } bool FGameInputRawDeviceProcessor::ProcessRawInputValueAsButton(const FGameInputEventParams& Params, const int32 RawValueIndex, const FGameInputRawDeviceReportData* AxisData) { check(AxisData && AxisData->TranslationBehavior == ERawDeviceReportTranslationBehavior::TreatAsButton); const uint8 Val = CurrentRawData[RawValueIndex]; const uint8 PrevVal = PreviousRawData[RawValueIndex]; // Just treat this button as pressed when non-zero, and not pressed when 0. const uint32 CurrentValue32 = FMath::Clamp(static_cast(Val), 0, 1); uint32 PreviousValue32 = FMath::Clamp(static_cast(PrevVal), 0, 1); // Map the value of the key name to this 1 if we haven't already FPerRawInputIndexData& IndexData = RawInputIndexDataMap.FindOrAdd(RawValueIndex); if (IndexData.KeyNameMap.IsEmpty()) { IndexData.KeyNameMap.Add(1, AxisData->KeyName); } // Note: Set the max supported buttons here to 1 because we only care about the first bit EvaluateButtonStates( Params, CurrentValue32, OUT PreviousValue32, IndexData.RepeatTime, IndexData.KeyNameMap, /* maxSupportedButtons */ 1); // If this value is non-zero then we had a reading return Val != 0; } bool FGameInputRawDeviceProcessor::ProcessRawInputValueAsAanalog(const FGameInputEventParams& Params, const int32 RawValueIndex, const FGameInputRawDeviceReportData* AxisData) { check(AxisData); const uint8 Val = CurrentRawData[RawValueIndex]; const uint8 PrevVal = PreviousRawData[RawValueIndex]; const float CurrentValueFloat = AxisData->Scalar * (AxisData->TranslationBehavior == ERawDeviceReportTranslationBehavior::TreatAsTrigger ? RawValueToFloatTrigger(Val) : RawValueToFloatAnalog(Val, AxisData->AnalogDeadzone)); const float PreviousValueFloat = AxisData->Scalar * (AxisData->TranslationBehavior == ERawDeviceReportTranslationBehavior::TreatAsTrigger ? RawValueToFloatTrigger(PrevVal) : RawValueToFloatAnalog(PrevVal, AxisData->AnalogDeadzone)); if (!AxisData->KeyName.IsValid()) { return false; } OnControllerAnalog( Params, AxisData->KeyName, CurrentValueFloat, PreviousValueFloat, UE::GameInput::GamepadLeftStickDeadzone, /* bShouldSetDeviceScope = */!AxisData->bIgnoreAnalogInputDeviceScopeForThisRawReport); // We had a reading as long as it is non-zero return CurrentValueFloat != 0.0f; } bool FGameInputRawDeviceProcessor::ProcessRawInputValueAsAanalogPaired(const FGameInputEventParams& Params, const FGameInputRawDeviceReportData* AxisData) { check(AxisData); if (!CurrentRawData.IsValidIndex(AxisData->LowerBitAxisIndex) || !CurrentRawData.IsValidIndex(AxisData->HigherBitAxisIndex)) { return false; } // Get the current value const uint8 CurrentLowerVal = CurrentRawData[AxisData->LowerBitAxisIndex]; const uint8 CurrentHigherVal = CurrentRawData[AxisData->HigherBitAxisIndex]; // Combine the two values into a single int16. Do this by // shifting the higher value up by 8 bits, and then just use the lower value in our int16 const int16 CurrentPackedVal = (int16)((CurrentHigherVal << 8) | CurrentLowerVal); const float CurrentValueFloat = (static_cast(CurrentPackedVal) / 32767.f); // Get the previous value const uint8 PreviousLowerVal = PreviousRawData[AxisData->LowerBitAxisIndex]; const uint8 PreviousHigherVal = PreviousRawData[AxisData->HigherBitAxisIndex]; const int16 PreviousPackedVal = (int16)((PreviousHigherVal << 8) | PreviousLowerVal); const float PreviousValueFloat = (static_cast(PreviousPackedVal) / 32767.f); if (!AxisData->KeyName.IsValid()) { return false; } OnControllerAnalog(Params, AxisData->KeyName, CurrentValueFloat, PreviousValueFloat, UE::GameInput::GamepadLeftStickDeadzone); // We had a reading as long as it is non-zero return CurrentValueFloat != 0.0f; } void FGameInputRawDeviceProcessor::ClearState(const FGameInputEventParams& Params) { // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid()) { return; } const FGameInputDeviceConfiguration* DeviceConfig = GetDefault()->FindDeviceConfiguration(Params.GetDeviceInfo()); // Check that we have a valid config before bothering to read the raw report if (!DeviceConfig) { UE_LOG(LogGameInput, Verbose, TEXT("[ClearStateRawReport] No have a valid FGameInputDeviceConfiguration in the UGameInputDeveloperSettings. We can't process Raw Input without it. Exiting. (Device %s)"), *UE::GameInput::LexToString(Params.Device)); return; } if (!DeviceConfig->bProcessRawReportData) { return; } // Reset our current values to 0... for (int32 i = 0; i < CurrentRawData.Num(); ++i) { CurrentRawData[i] = 0; } // ... and then process the raw values as if there is 0 input. We want to process all types when clearing. constexpr bool bShouldPorcessButtons = true; constexpr bool bShouldPorcessAnalog = true; ProcessAllRawValues(Params, DeviceConfig, bShouldPorcessButtons, bShouldPorcessAnalog); } GameInputKind FGameInputRawDeviceProcessor::GetSupportedReadingKind() const { return GameInputKindRawDeviceReport; } /////////////////////////////////////////////////////////////////////////////////////// // FGameInputRacingWheelProcessor FGameInputRacingWheelProcessor::FGameInputRacingWheelProcessor() : IGameInputDeviceProcessor() { NumReadingsProcessedThisFrame = 0; FMemory::Memset(PreviousState, 0); FMemory::Memset(RepeatTime, 0); } bool FGameInputRacingWheelProcessor::ProcessInput(const FGameInputEventParams& Params) { bool bRes = false; // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid() || !Params.Reading) { return bRes; } GameInputRacingWheelState WheelState; if (!Params.Reading->GetRacingWheelState(&WheelState)) { return bRes; } // We only want to process the buttons here, as it might get called multiple times per frame. bRes |= ProcessWheelButtonState(Params, WheelState); ++NumReadingsProcessedThisFrame; return bRes; } bool FGameInputRacingWheelProcessor::PostProcessInput(const FGameInputEventParams& Params) { bool bRes = false; // Check if we have already processed buttons this frame. If we haven't we want to do it const bool bHasProcessedAnyButtonsThisFrame = NumReadingsProcessedThisFrame > 0; NumReadingsProcessedThisFrame = 0; // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid() || !Params.PreviousReading) { return bRes; } // Use the "PreviousRading" because we only want to process the analog inputs once, and this will // point to the most up to date reading. GameInputRacingWheelState WheelState; if (!Params.PreviousReading->GetRacingWheelState(&WheelState)) { return bRes; } if (!bHasProcessedAnyButtonsThisFrame) { bRes |= ProcessWheelButtonState(Params, WheelState); } bRes |= ProcessWheelAnalogState(Params, WheelState); return bRes; } void FGameInputRacingWheelProcessor::ClearState(const FGameInputEventParams& Params) { // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid()) { return; } // We can simply process a wheel state where everything is zero GameInputRacingWheelState ZeroValueState = {}; FMemory::Memset(ZeroValueState, 0); ProcessWheelButtonState(Params, ZeroValueState); ProcessWheelAnalogState(Params, ZeroValueState); // Zero out any state trackers NumReadingsProcessedThisFrame = 0; FMemory::Memset(PreviousState, 0); FMemory::Memset(RepeatTime, 0); } namespace UE::GameInput { static bool HasDifferentWheelAnalogInput(const GameInputRacingWheelState& CurrentState, const GameInputRacingWheelState& PreviousState) { // If anything differs from the previous reading, then it has different inputs return CurrentState.wheel != PreviousState.wheel || CurrentState.throttle != PreviousState.throttle || CurrentState.brake != PreviousState.brake || CurrentState.clutch != PreviousState.clutch || CurrentState.handbrake != PreviousState.handbrake; } }; const float FGameInputRacingWheelProcessor::GetRacingWheelDeadzone() { // These settings should always be available if (const UGameInputPlatformSettings* Settings = UGameInputPlatformSettings::Get()) { return Settings->RacingWheelDeadzone; } return UGameInputPlatformSettings::DefaultRacingWheelDeadzone; } bool FGameInputRacingWheelProcessor::ProcessWheelAnalogState(const FGameInputEventParams& Params, GameInputRacingWheelState& CurrentWheelState) { // ignore this input if the reading has remained empty from last time const float Deadzone = GetRacingWheelDeadzone(); // If the analog values haven't changed, don't bother sending any events for them const bool bHasDifferentAnalogInput = UE::GameInput::HasDifferentWheelAnalogInput(CurrentWheelState, PreviousState); if (!bHasDifferentAnalogInput) { return false; } OnControllerAnalog(Params, FGameInputKeys::RacingWheel_Brake.GetFName(), CurrentWheelState.brake, PreviousState.brake, Deadzone); OnControllerAnalog(Params, FGameInputKeys::RacingWheel_Clutch.GetFName(), CurrentWheelState.clutch, PreviousState.clutch, Deadzone); OnControllerAnalog(Params, FGameInputKeys::RacingWheel_Handbrake.GetFName(), CurrentWheelState.handbrake, PreviousState.handbrake, Deadzone); OnControllerAnalog(Params, FGameInputKeys::RacingWheel_Throttle.GetFName(), CurrentWheelState.throttle, PreviousState.throttle, Deadzone); OnControllerAnalog(Params, FGameInputKeys::RacingWheel_Wheel.GetFName(), CurrentWheelState.wheel, PreviousState.wheel, Deadzone); // Do we actually want this as a float? We should test the values that this can produce OnControllerAnalog(Params, FGameInputKeys::RacingWheel_PatternShifterGear.GetFName(), static_cast(CurrentWheelState.patternShifterGear), static_cast(PreviousState.patternShifterGear), Deadzone); // Keep track of the previous state PreviousState.brake = CurrentWheelState.brake; PreviousState.clutch = CurrentWheelState.clutch; PreviousState.handbrake = CurrentWheelState.handbrake; PreviousState.throttle = CurrentWheelState.throttle; PreviousState.wheel = CurrentWheelState.wheel; PreviousState.patternShifterGear = CurrentWheelState.patternShifterGear; return true; } bool FGameInputRacingWheelProcessor::ProcessWheelButtonState(const FGameInputEventParams& Params, GameInputRacingWheelState& CurrentWheelState) { // If there has been no buttons pressed on this state or the previous one, don't bother trying // to evaluate any events. if (CurrentWheelState.buttons == 0 && PreviousState.buttons == 0) { return false; } // This might not be necessary if the racing wheels also show up as "gamepad" devices... const uint32 CurrentButtonHeldMask = static_cast(CurrentWheelState.buttons); uint32 LastButtonHeldMask = static_cast(PreviousState.buttons); EvaluateButtonStates( Params, CurrentButtonHeldMask, OUT LastButtonHeldMask, RepeatTime, UE::GameInput::GetRacingWheelButtonMap(), MaxSupportedButtons); // Update the previous state here PreviousState.buttons = CurrentWheelState.buttons; return true; } GameInputKind FGameInputRacingWheelProcessor::GetSupportedReadingKind() const { return GameInputKindRacingWheel; } ////////////////////////////////////////////////// // Arcade Sticks namespace UE::GameInput { /** A map of uint32 GameInput button bitmask flags to the associated Unreal Engine FKey name. */ static const TMap& GetArcadeStickButtonMap() { static const TMap GamepadButtonMap { // Generic gamepad buttons { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickNone), FGamepadKeyNames::Invalid}, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickMenu), FGamepadKeyNames::SpecialRight }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickView), FGamepadKeyNames::SpecialLeft }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickUp), FGamepadKeyNames::DPadUp }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickDown), FGamepadKeyNames::DPadDown }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickLeft), FGamepadKeyNames::DPadLeft }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickRight), FGamepadKeyNames::DPadRight }, // Unique to arcade sticks { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickAction1), FGameInputKeys::ArcadeStick_Action1.GetFName() }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickAction2), FGameInputKeys::ArcadeStick_Action2.GetFName() }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickAction3), FGameInputKeys::ArcadeStick_Action3.GetFName() }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickAction4), FGameInputKeys::ArcadeStick_Action4.GetFName() }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickAction5), FGameInputKeys::ArcadeStick_Action5.GetFName() }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickAction6), FGameInputKeys::ArcadeStick_Action6.GetFName() }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickSpecial1), FGameInputKeys::ArcadeStick_Special1.GetFName() }, { static_cast(GameInputArcadeStickButtons::GameInputArcadeStickSpecial2), FGameInputKeys::ArcadeStick_Special2.GetFName() } }; return GamepadButtonMap; } }; FGameInputArcadeStickProcessor::FGameInputArcadeStickProcessor() : IGameInputDeviceProcessor() { FMemory::Memset(PreviousState, 0); FMemory::Memset(RepeatTime, 0); } bool FGameInputArcadeStickProcessor::ProcessInput(const FGameInputEventParams& Params) { bool bRes = false; // Can't do anything for an invalid platform user or no reading. if (!Params.PlatformUserId.IsValid() || !Params.Reading) { return bRes; } GameInputArcadeStickState StickState; if (!Params.Reading->GetArcadeStickState(&StickState)) { return bRes; } const uint32 CurrentButtonHeldMask = static_cast(StickState.buttons); uint32 LastButtonHeldMask = static_cast(PreviousState.buttons); EvaluateButtonStates( Params, CurrentButtonHeldMask, OUT LastButtonHeldMask, RepeatTime, UE::GameInput::GetArcadeStickButtonMap(), MaxSupportedButtons); // Keep track of the button state so that we can compare it next time it is processed PreviousState = StickState; return true; } void FGameInputArcadeStickProcessor::ClearState(const FGameInputEventParams& Params) { // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid()) { return; } // Evaluate the buttons as if none have been pressed (i.e. the button mask is 0) const uint32 CurrentButtonHeldMask = 0x00; uint32 LastButtonHeldMask = static_cast(PreviousState.buttons); EvaluateButtonStates( Params, CurrentButtonHeldMask, OUT LastButtonHeldMask, RepeatTime, UE::GameInput::GetArcadeStickButtonMap(), MaxSupportedButtons); // Zero out the repeat and previous states FMemory::Memset(PreviousState, 0); FMemory::Memset(RepeatTime, 0); } GameInputKind FGameInputArcadeStickProcessor::GetSupportedReadingKind() const { return GameInputKindArcadeStick; } ////////////////////////////////////////////////////////////////////// // FGameInputFlightStickProcessor namespace UE::GameInput { /** A map of uint32 GameInput button bitmask flags to the associated Unreal Engine FKey name. */ static const TMap& GetFlightStickButtonMap() { static const TMap GamepadButtonMap { // Generic gamepad buttons { static_cast(GameInputFlightStickButtons::GameInputFlightStickNone), FGamepadKeyNames::Invalid}, { static_cast(GameInputFlightStickButtons::GameInputFlightStickMenu), FGamepadKeyNames::Invalid}, { static_cast(GameInputFlightStickButtons::GameInputFlightStickView), FGamepadKeyNames::Invalid}, { static_cast(GameInputFlightStickButtons::GameInputFlightStickFirePrimary), FGamepadKeyNames::Invalid}, { static_cast(GameInputFlightStickButtons::GameInputFlightStickFireSecondary), FGamepadKeyNames::Invalid}, }; return GamepadButtonMap; } static bool HasDifferentFlightStickAnalogs(const GameInputFlightStickState& CurrentState, const GameInputFlightStickState& PreviousState) { // If anything differs from the previous reading, then it has different inputs return CurrentState.pitch != PreviousState.pitch || CurrentState.roll != PreviousState.roll || CurrentState.throttle != PreviousState.throttle || CurrentState.yaw != PreviousState.yaw; } inline bool IsEmptyFlightStickReading(const GameInputFlightStickState& State, const UGameInputPlatformSettings& PlatformSettings) { return State.buttons == 0 && State.hatSwitch == 0 && FMath::Abs(State.pitch) <= PlatformSettings.FlightStickPitchDeadzone && FMath::Abs(State.roll) <= PlatformSettings.FlightStickRollDeadzone && FMath::Abs(State.throttle) <= PlatformSettings.FlightStickThrottleDeadzone && FMath::Abs(State.yaw) <= PlatformSettings.FlightStickYawDeadzone; }; } FGameInputFlightStickProcessor::FGameInputFlightStickProcessor() : IGameInputDeviceProcessor() { FMemory::Memset(PreviousState, 0); FMemory::Memset(RepeatTime, 0); SwitchRepeatTimes.AddDefaulted(static_cast(GameInputSwitchUpLeft) + 1); } bool FGameInputFlightStickProcessor::ProcessInput(const FGameInputEventParams& Params) { bool bRes = false; // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid() || !Params.Reading) { return bRes; } GameInputFlightStickState FlightStickState = {}; if (!Params.Reading->GetFlightStickState(&FlightStickState)) { return bRes; } // We only want to process the buttons here, as it might get called multiple times per frame. bRes |= ProcessFlightStickButtons(Params, FlightStickState); ++NumReadingsProcessedThisFrame; return bRes; } bool FGameInputFlightStickProcessor::PostProcessInput(const FGameInputEventParams& Params) { bool bRes = false; // Check if we have already processed buttons this frame. If we haven't we want to do it const bool bHasProcessedAnyButtonsThisFrame = NumReadingsProcessedThisFrame > 0; NumReadingsProcessedThisFrame = 0; // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid() || !Params.PreviousReading) { return bRes; } GameInputFlightStickState FlightStickState = {}; if (!Params.PreviousReading->GetFlightStickState(&FlightStickState)) { return bRes; } if (!bHasProcessedAnyButtonsThisFrame) { bRes |= ProcessFlightStickButtons(Params, FlightStickState); } bRes |= ProcessFlightStickAnalog(Params, FlightStickState); return bRes; } void FGameInputFlightStickProcessor::ClearState(const FGameInputEventParams& Params) { // Can't do anything for an invalid platform user if (!Params.PlatformUserId.IsValid()) { return; } // Process input as if nothing is down (zero values for everything) GameInputFlightStickState ZeroState = {}; ProcessFlightStickAnalog(Params, ZeroState); ProcessFlightStickButtons(Params, ZeroState); // Zero out the repeat and previous states info to zero FMemory::Memset(PreviousState, 0); FMemory::Memset(RepeatTime, 0); } GameInputKind FGameInputFlightStickProcessor::GetSupportedReadingKind() const { return GameInputKindFlightStick; } bool FGameInputFlightStickProcessor::ProcessFlightStickButtons(const FGameInputEventParams& Params, GameInputFlightStickState& State) { const UGameInputPlatformSettings* PlatformSettings = UGameInputPlatformSettings::Get(); const bool bIsEmptyReading = UE::GameInput::IsEmptyFlightStickReading(State, *PlatformSettings); const bool bWasEmptyReading = UE::GameInput::IsEmptyFlightStickReading(PreviousState, *PlatformSettings); const bool bHasDifferentAnalogInput = UE::GameInput::HasDifferentFlightStickAnalogs(State, PreviousState); if (bIsEmptyReading && bWasEmptyReading && !bHasDifferentAnalogInput && PreviousState.buttons == 0) { return false; } const uint32 CurrentButtonHeldMask = static_cast(State.buttons); uint32 LastButtonHeldMask = static_cast(PreviousState.buttons); EvaluateButtonStates( Params, CurrentButtonHeldMask, OUT LastButtonHeldMask, RepeatTime, UE::GameInput::GetFlightStickButtonMap(), MaxSupportedButtons); // Update the previous state here PreviousState.buttons = State.buttons; // Update the hat switch EvaluateSwitchState(Params, State.hatSwitch, PreviousState.hatSwitch, SwitchRepeatTimes); return true; } bool FGameInputFlightStickProcessor::ProcessFlightStickAnalog(const FGameInputEventParams& Params, GameInputFlightStickState& State) { const UGameInputPlatformSettings* Settings = UGameInputPlatformSettings::Get(); check(Settings); // If the analog values haven't changed, don't bother sending any events for them const bool bIsEmptyReading = UE::GameInput::IsEmptyFlightStickReading(State, *Settings); const bool bWasEmptyReading = UE::GameInput::IsEmptyFlightStickReading(PreviousState, *Settings); const bool bHasDifferentAnalogInput = UE::GameInput::HasDifferentFlightStickAnalogs(State, PreviousState); if (bIsEmptyReading && bWasEmptyReading && !bHasDifferentAnalogInput) { return false; } OnControllerAnalog(Params, FGameInputKeys::FlightStick_Pitch.GetFName(), State.pitch, PreviousState.pitch, Settings->FlightStickPitchDeadzone); OnControllerAnalog(Params, FGameInputKeys::FlightStick_Roll.GetFName(), State.roll, PreviousState.roll, Settings->FlightStickRollDeadzone); OnControllerAnalog(Params, FGameInputKeys::FlightStick_Throttle.GetFName(), State.throttle, PreviousState.throttle, Settings->FlightStickThrottleDeadzone); OnControllerAnalog(Params, FGameInputKeys::FlightStick_Yaw.GetFName(), State.yaw, PreviousState.yaw, Settings->FlightStickYawDeadzone); PreviousState.pitch = State.pitch; PreviousState.roll = State.roll; PreviousState.throttle = State.throttle; PreviousState.yaw = State.yaw; return true; } #endif // GAME_INPUT_SUPPORT