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.
This commit is contained in:
Steve Streeting 2021-12-02 17:45:26 +00:00
parent 5ba7a841e4
commit 0cd30059f5
8 changed files with 137 additions and 36 deletions

View File

@ -8,6 +8,7 @@
#include "GameFramework/PlayerController.h" #include "GameFramework/PlayerController.h"
#include "GameFramework/PlayerInput.h" #include "GameFramework/PlayerInput.h"
#include "StevesUI/KeySprite.h" #include "StevesUI/KeySprite.h"
#include "StevesUI/StevesUI.h"
//PRAGMA_DISABLE_OPTIMIZATION //PRAGMA_DISABLE_OPTIMIZATION
@ -126,15 +127,19 @@ FFocusSystem* UStevesGameSubsystem::GetFocusSystem()
return &FocusSystem; return &FocusSystem;
} }
UPaperSprite* UStevesGameSubsystem::GetInputImageSprite(EInputBindingType BindingType, FName ActionOrAxis, UPaperSprite* UStevesGameSubsystem::GetInputImageSprite(EInputBindingType BindingType,
FKey Key, int PlayerIdx, const UUiTheme* Theme) FName ActionOrAxis,
FKey Key,
EInputImageDevicePreference DevicePreference,
int PlayerIdx,
const UUiTheme* Theme)
{ {
switch(BindingType) switch(BindingType)
{ {
case EInputBindingType::Action: case EInputBindingType::Action:
return GetInputImageSpriteFromAction(ActionOrAxis, PlayerIdx, Theme); return GetInputImageSpriteFromAction(ActionOrAxis, DevicePreference, PlayerIdx, Theme);
case EInputBindingType::Axis: case EInputBindingType::Axis:
return GetInputImageSpriteFromAxis(ActionOrAxis, PlayerIdx, Theme); return GetInputImageSpriteFromAxis(ActionOrAxis, DevicePreference, PlayerIdx, Theme);
case EInputBindingType::Key: case EInputBindingType::Key:
return GetInputImageSpriteFromKey(Key, PlayerIdx, Theme); return GetInputImageSpriteFromKey(Key, PlayerIdx, Theme);
default: default:
@ -146,48 +151,46 @@ UPaperSprite* UStevesGameSubsystem::GetInputImageSprite(EInputBindingType Bindin
TArray<FInputActionKeyMapping> GS_TempActionMap; TArray<FInputActionKeyMapping> GS_TempActionMap;
TArray<FInputAxisKeyMapping> GS_TempAxisMap; TArray<FInputAxisKeyMapping> 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(); UInputSettings* Settings = UInputSettings::GetInputSettings();
GS_TempActionMap.Empty(); GS_TempActionMap.Empty();
Settings->GetActionMappingByName(Name, GS_TempActionMap); 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<FInputActionKeyMapping>(GS_TempActionMap, Name, DevicePreference, LastInputWasGamePad(PlayerIdx));
if (Preferred)
{ {
if (ActionMap.Key.IsGamepadKey() == WantGamepad) return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme);
{
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 nullptr; 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 // Look up the key for this axis
UInputSettings* Settings = UInputSettings::GetInputSettings(); UInputSettings* Settings = UInputSettings::GetInputSettings();
GS_TempAxisMap.Empty(); GS_TempAxisMap.Empty();
Settings->GetAxisMappingByName(Name, GS_TempAxisMap); 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<FInputAxisKeyMapping>(GS_TempAxisMap, Name, DevicePreference, LastInputWasGamePad(PlayerIdx));
if (Preferred)
{ {
if (AxisMap.Key.IsGamepadKey() == WantGamepad) return GetInputImageSpriteFromKey(Preferred->Key, PlayerIdx, Theme);
{
return GetInputImageSpriteFromKey(AxisMap.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; return nullptr;
} }

View File

@ -72,7 +72,7 @@ void UInputImage::UpdateImage()
auto GS = GetStevesGameSubsystem(GetWorld()); auto GS = GetStevesGameSubsystem(GetWorld());
if (GS) if (GS)
{ {
auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, PlayerIndex, CustomTheme); auto Sprite = GS->GetInputImageSprite(BindingType, ActionOrAxisName, Key, DevicePreference, PlayerIndex, CustomTheme);
if (Sprite) if (Sprite)
{ {
// Match size is needed incase size has changed // Match size is needed incase size has changed

View File

@ -122,7 +122,7 @@ public:
if (GS) if (GS)
{ {
// Can only support default theme, no way to edit theme in decorator config // 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) if (Sprite && Brush.GetResourceObject() != Sprite)
{ {
UStevesGameSubsystem::SetBrushFromAtlas(&Brush, Sprite, true); UStevesGameSubsystem::SetBrushFromAtlas(&Brush, Sprite, true);
@ -214,7 +214,7 @@ protected:
if (GS) if (GS)
{ {
// Can only support default theme, no way to edit theme in decorator config // 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 else
{ {

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "StevesHelperCommon.h"
class UWidget; class UWidget;
class SWidget; class SWidget;
@ -24,3 +25,55 @@ UWidget* FindWidgetFromSlate(SWidget* SW, UWidget* Parent);
* @param Widget A UWidget * @param Widget A UWidget
*/ */
void SetWidgetFocusProperly(UWidget* Widget); void SetWidgetFocusProperly(UWidget* Widget);
template <typename T>
const T* GetPreferedActionOrAxisMapping(const TArray<T>& 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;
}

View File

@ -161,29 +161,48 @@ public:
* @param BindingType The type of input binding to look up * @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 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 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 PlayerIndex The player index to look up the binding for
* @param Theme Optional explicit theme, if blank use the default theme * @param Theme Optional explicit theme, if blank use the default theme
* @return * @return
*/ */
UFUNCTION(BlueprintCallable) 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 * @brief Get an input button / key image from an action
* @param Name The name of the 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 PlayerIndex The player index to look up the binding for
* @param Theme Optional explicit theme, if blank use the default theme * @param Theme Optional explicit theme, if blank use the default theme
* @return * @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 * @brief Get an input image from an axis
* @param Name The name of the 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 PlayerIndex The player index to look up the binding for
* @param Theme Optional explicit theme, if blank use the default theme * @param Theme Optional explicit theme, if blank use the default theme
* @return * @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 * @brief Get an input image for a specific key

View File

@ -49,3 +49,15 @@ enum class EInputBindingType : uint8
Key = 2 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")
};

View File

@ -23,6 +23,10 @@ protected:
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere)
FName ActionOrAxisName; FName ActionOrAxisName;
/// Where there are multiple mappings, which to prefer
UPROPERTY(EditAnywhere)
EInputImageDevicePreference DevicePreference = EInputImageDevicePreference::Auto;
/// If BindingType is Key, the key /// If BindingType is Key, the key
UPROPERTY(EditAnywhere) UPROPERTY(EditAnywhere)
FKey Key; FKey Key;

View File

@ -28,6 +28,16 @@ key that we'll need an image for.
You can leave this blank if you set Binding Type to "Key". 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 ### Key
If you don't want the InputImage to look up an action, but want to manually specify If you don't want the InputImage to look up an action, but want to manually specify