From 0cd30059f579df60ecd6c806e407b5ab155da4ab Mon Sep 17 00:00:00 2001 From: Steve Streeting Date: Thu, 2 Dec 2021 17:45:26 +0000 Subject: [PATCH] Improve input image behaviour when there are multiple device mappings. Add a device preference, which defaults to automatic (gamepad, then either mouse or keyboard depending if its a button or an axis). Also change the ordering so that multiple mappings for the same device list the first mapping as a preference. --- .../Private/StevesGameSubsystem.cpp | 63 ++++++++++--------- .../Private/StevesUI/InputImage.cpp | 2 +- .../RichTextBlockInputImageDecorator.cpp | 4 +- .../Private/StevesUI/StevesUI.h | 53 ++++++++++++++++ .../Public/StevesGameSubsystem.h | 25 +++++++- .../Public/StevesHelperCommon.h | 12 ++++ .../Public/StevesUI/InputImage.h | 4 ++ doc/InputImage.md | 10 +++ 8 files changed, 137 insertions(+), 36 deletions(-) diff --git a/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp b/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp index 18e22e4..e7f4a37 100644 --- a/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp +++ b/Source/StevesUEHelpers/Private/StevesGameSubsystem.cpp @@ -8,6 +8,7 @@ #include "GameFramework/PlayerController.h" #include "GameFramework/PlayerInput.h" #include "StevesUI/KeySprite.h" +#include "StevesUI/StevesUI.h" //PRAGMA_DISABLE_OPTIMIZATION @@ -126,15 +127,19 @@ FFocusSystem* UStevesGameSubsystem::GetFocusSystem() return &FocusSystem; } -UPaperSprite* UStevesGameSubsystem::GetInputImageSprite(EInputBindingType BindingType, FName ActionOrAxis, - FKey Key, int PlayerIdx, const UUiTheme* Theme) +UPaperSprite* UStevesGameSubsystem::GetInputImageSprite(EInputBindingType BindingType, + FName ActionOrAxis, + FKey Key, + EInputImageDevicePreference DevicePreference, + int PlayerIdx, + const UUiTheme* Theme) { switch(BindingType) { case EInputBindingType::Action: - return GetInputImageSpriteFromAction(ActionOrAxis, PlayerIdx, Theme); + return GetInputImageSpriteFromAction(ActionOrAxis, DevicePreference, PlayerIdx, Theme); case EInputBindingType::Axis: - return GetInputImageSpriteFromAxis(ActionOrAxis, PlayerIdx, Theme); + return GetInputImageSpriteFromAxis(ActionOrAxis, DevicePreference, PlayerIdx, Theme); case EInputBindingType::Key: return GetInputImageSpriteFromKey(Key, PlayerIdx, Theme); default: @@ -146,48 +151,46 @@ UPaperSprite* UStevesGameSubsystem::GetInputImageSprite(EInputBindingType Bindin TArray GS_TempActionMap; TArray GS_TempAxisMap; -UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAction(const FName& Name, int PlayerIdx, const UUiTheme* Theme) +UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAction(const FName& Name, + EInputImageDevicePreference DevicePreference, + int PlayerIdx, + const UUiTheme* Theme) { - - // Look up the key for this action UInputSettings* Settings = UInputSettings::GetInputSettings(); GS_TempActionMap.Empty(); Settings->GetActionMappingByName(Name, GS_TempActionMap); - const bool WantGamepad = LastInputWasGamePad(PlayerIdx); - for (auto && ActionMap : GS_TempActionMap) + + // For default, prefer keyboard for buttons + if (DevicePreference == EInputImageDevicePreference::Auto) + DevicePreference = EInputImageDevicePreference::Gamepad_Keyboard_Mouse; + + const auto Preferred = GetPreferedActionOrAxisMapping(GS_TempActionMap, Name, DevicePreference, LastInputWasGamePad(PlayerIdx)); + if (Preferred) { - if (ActionMap.Key.IsGamepadKey() == WantGamepad) - { - return GetInputImageSpriteFromKey(ActionMap.Key, PlayerIdx, Theme); - } - } - // if we fell through, didn't find a mapping which matched our gamepad preference - if (GS_TempActionMap.Num()) - { - return GetInputImageSpriteFromKey(GS_TempActionMap[0].Key, PlayerIdx, Theme); + return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme); } return nullptr; } -UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAxis(const FName& Name, int PlayerIdx, const UUiTheme* Theme) +UPaperSprite* UStevesGameSubsystem::GetInputImageSpriteFromAxis(const FName& Name, + EInputImageDevicePreference DevicePreference, + int PlayerIdx, + const UUiTheme* Theme) { // Look up the key for this axis UInputSettings* Settings = UInputSettings::GetInputSettings(); GS_TempAxisMap.Empty(); Settings->GetAxisMappingByName(Name, GS_TempAxisMap); - const bool WantGamepad = LastInputWasGamePad(PlayerIdx); - for (auto && AxisMap : GS_TempAxisMap) + + // For default, prefer mouse for axes + if (DevicePreference == EInputImageDevicePreference::Auto) + DevicePreference = EInputImageDevicePreference::Gamepad_Mouse_Keyboard; + + const auto Preferred = GetPreferedActionOrAxisMapping(GS_TempAxisMap, Name, DevicePreference, LastInputWasGamePad(PlayerIdx)); + if (Preferred) { - if (AxisMap.Key.IsGamepadKey() == WantGamepad) - { - return GetInputImageSpriteFromKey(AxisMap.Key, PlayerIdx, Theme); - } + return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme); } - // if we fell through, didn't find a mapping which matched our gamepad preference - if (GS_TempAxisMap.Num()) - { - return GetInputImageSpriteFromKey(GS_TempAxisMap[0].Key, PlayerIdx, Theme); - } return nullptr; } diff --git a/Source/StevesUEHelpers/Private/StevesUI/InputImage.cpp b/Source/StevesUEHelpers/Private/StevesUI/InputImage.cpp index b849de8..933426a 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/InputImage.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/InputImage.cpp @@ -72,7 +72,7 @@ void UInputImage::UpdateImage() auto GS = GetStevesGameSubsystem(GetWorld()); if (GS) { - auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, PlayerIndex, CustomTheme); + auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, DevicePreference, PlayerIndex, CustomTheme); if (Sprite) { // Match size is needed incase size has changed diff --git a/Source/StevesUEHelpers/Private/StevesUI/RichTextBlockInputImageDecorator.cpp b/Source/StevesUEHelpers/Private/StevesUI/RichTextBlockInputImageDecorator.cpp index d62bb4b..e7f9c29 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/RichTextBlockInputImageDecorator.cpp +++ b/Source/StevesUEHelpers/Private/StevesUI/RichTextBlockInputImageDecorator.cpp @@ -122,7 +122,7 @@ public: if (GS) { // Can only support default theme, no way to edit theme in decorator config - auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, PlayerIndex); + auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, EInputImageDevicePreference::Auto, PlayerIndex); if (Sprite && Brush.GetResourceObject() != Sprite) { UStevesGameSubsystem::SetBrushFromAtlas(&Brush, Sprite, true); @@ -214,7 +214,7 @@ protected: if (GS) { // Can only support default theme, no way to edit theme in decorator config - Params.InitialSprite = GS->GetInputImageSprite(Params.BindingType, Params.ActionOrAxisName, Params.Key, Params.PlayerIndex); + Params.InitialSprite = GS->GetInputImageSprite(Params.BindingType, Params.ActionOrAxisName, Params.Key, EInputImageDevicePreference::Auto, Params.PlayerIndex); } else { diff --git a/Source/StevesUEHelpers/Private/StevesUI/StevesUI.h b/Source/StevesUEHelpers/Private/StevesUI/StevesUI.h index ba917c9..d6aba8b 100644 --- a/Source/StevesUEHelpers/Private/StevesUI/StevesUI.h +++ b/Source/StevesUEHelpers/Private/StevesUI/StevesUI.h @@ -1,6 +1,7 @@ #pragma once #include "CoreMinimal.h" +#include "StevesHelperCommon.h" class UWidget; class SWidget; @@ -24,3 +25,55 @@ UWidget* FindWidgetFromSlate(SWidget* SW, UWidget* Parent); * @param Widget A UWidget */ void SetWidgetFocusProperly(UWidget* Widget); + +template +const T* GetPreferedActionOrAxisMapping(const TArray& AllMappings, const FName& Name, + EInputImageDevicePreference DevicePreference, + bool bLastInputWasGamepad) +{ + const T* MouseMapping = nullptr; + const T* KeyboardMapping = nullptr; + const T* GamepadMapping = nullptr; + for (const T& ActionMap : AllMappings) + { + // notice how we take the LAST one in the list as the final version + // this is because UInputSettings::GetActionMappingByName *reverses* the mapping list from Project Settings + if (ActionMap.Key.IsGamepadKey()) + { + GamepadMapping = &ActionMap; + } + else if (ActionMap.Key.IsMouseButton()) + { + MouseMapping = &ActionMap; + } + else + { + KeyboardMapping = &ActionMap; + } + } + + const T* Preferred = nullptr; + if (GamepadMapping && bLastInputWasGamepad) + { + Preferred = GamepadMapping; + } + else + { + switch (DevicePreference) + { + // Auto should be pre-converted to another + case EInputImageDevicePreference::Auto: + UE_LOG(LogStevesUI, Error, TEXT("Device Preference should have been converted before this call")) + break; + case EInputImageDevicePreference::Gamepad_Keyboard_Mouse: + Preferred = KeyboardMapping ? KeyboardMapping : MouseMapping; + break; + case EInputImageDevicePreference::Gamepad_Mouse_Keyboard: + Preferred = MouseMapping ? MouseMapping : KeyboardMapping; + break; + default: + break; + } + } + return Preferred; +} diff --git a/Source/StevesUEHelpers/Public/StevesGameSubsystem.h b/Source/StevesUEHelpers/Public/StevesGameSubsystem.h index d7df976..70e924c 100644 --- a/Source/StevesUEHelpers/Public/StevesGameSubsystem.h +++ b/Source/StevesUEHelpers/Public/StevesGameSubsystem.h @@ -161,29 +161,48 @@ public: * @param BindingType The type of input binding to look up * @param ActionOrAxis The name of the action or axis, if BindingType is looking for that * @param Key The explicit key you want to display, if the BindingType is set to custom key + * @param DevicePreference The order of preference for images where multiple devices have mappings. In the case of multiple mappings for the same device, the first one will be used. * @param PlayerIndex The player index to look up the binding for * @param Theme Optional explicit theme, if blank use the default theme * @return */ UFUNCTION(BlueprintCallable) - UPaperSprite* GetInputImageSprite(EInputBindingType BindingType, FName ActionOrAxis, FKey Key, int PlayerIndex = 0, const UUiTheme* Theme = nullptr); + UPaperSprite* GetInputImageSprite(EInputBindingType BindingType, + FName ActionOrAxis, + FKey Key, + EInputImageDevicePreference DevicePreference, + int PlayerIndex = 0, + const UUiTheme* Theme = nullptr); /** * @brief Get an input button / key image from an action * @param Name The name of the action + * @param DevicePreference The order of preference for images where multiple devices have mappings. In the case of multiple mappings for the same device, the first one will be used. * @param PlayerIndex The player index to look up the binding for * @param Theme Optional explicit theme, if blank use the default theme * @return */ - UPaperSprite* GetInputImageSpriteFromAction(const FName& Name, int PlayerIndex = 0, const UUiTheme* Theme = nullptr); + UPaperSprite* GetInputImageSpriteFromAction(const FName& Name, + EInputImageDevicePreference DevicePreference = + EInputImageDevicePreference::Gamepad_Keyboard_Mouse, + int PlayerIndex = 0, + const UUiTheme* Theme = nullptr); /** * @brief Get an input image from an axis * @param Name The name of the axis + * @param DevicePreference The order of preference for images where multiple devices have mappings. In the case of multiple mappings for the same device, the first one will be used. * @param PlayerIndex The player index to look up the binding for * @param Theme Optional explicit theme, if blank use the default theme * @return */ - UPaperSprite* GetInputImageSpriteFromAxis(const FName& Name, int PlayerIndex = 0, const UUiTheme* Theme = nullptr); + UPaperSprite* GetInputImageSpriteFromAxis(const FName& Name, + EInputImageDevicePreference DevicePreference = + EInputImageDevicePreference::Gamepad_Mouse_Keyboard, + int PlayerIndex = 0, + const UUiTheme* Theme = nullptr); + + + /** * @brief Get an input image for a specific key diff --git a/Source/StevesUEHelpers/Public/StevesHelperCommon.h b/Source/StevesUEHelpers/Public/StevesHelperCommon.h index 12154cd..784a7c0 100644 --- a/Source/StevesUEHelpers/Public/StevesHelperCommon.h +++ b/Source/StevesUEHelpers/Public/StevesHelperCommon.h @@ -49,3 +49,15 @@ enum class EInputBindingType : uint8 Key = 2 }; +/// What order of preference should we return input images where an action/axis has multiple mappings +UENUM(BlueprintType) +enum class EInputImageDevicePreference : uint8 +{ + /// For actions, use Gamepad_Keyboard_Mouse, for axes, use Gamepad_Mouse_Keyboard + Auto, + /// Gamepad first, then keyboard, then mouse - this is usually best for actions (buttons) + Gamepad_Keyboard_Mouse UMETA(DisplayName="Prefer Gamepad (if active), then keyboard, then mouse"), + /// Gamepad first, then mouse, then keyboard - this is usually best for axes + Gamepad_Mouse_Keyboard UMETA(DisplayName="Prefer Gamepad (if active), then mouse, then keyboard") +}; + diff --git a/Source/StevesUEHelpers/Public/StevesUI/InputImage.h b/Source/StevesUEHelpers/Public/StevesUI/InputImage.h index a242880..0872063 100644 --- a/Source/StevesUEHelpers/Public/StevesUI/InputImage.h +++ b/Source/StevesUEHelpers/Public/StevesUI/InputImage.h @@ -23,6 +23,10 @@ protected: UPROPERTY(EditAnywhere) FName ActionOrAxisName; + /// Where there are multiple mappings, which to prefer + UPROPERTY(EditAnywhere) + EInputImageDevicePreference DevicePreference = EInputImageDevicePreference::Auto; + /// If BindingType is Key, the key UPROPERTY(EditAnywhere) FKey Key; diff --git a/doc/InputImage.md b/doc/InputImage.md index 9023683..25006d8 100644 --- a/doc/InputImage.md +++ b/doc/InputImage.md @@ -28,6 +28,16 @@ key that we'll need an image for. You can leave this blank if you set Binding Type to "Key". +### Device Preference + +When using an Action or Axis Name, which device to prefer to show the image for +where there are multiple mappings. The default is "Auto", which means: + +1. Gamepad, if the last used device was gamepad +2. If an Action (button/key), prefer Keyboard over Mouse buttons +3. If an Axis, prefer Mouse over Keyboard + + ### Key If you don't want the InputImage to look up an action, but want to manually specify