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/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<FInputActionKeyMapping> GS_TempActionMap;
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();
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<FInputActionKeyMapping>(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<FInputAxisKeyMapping>(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;
}

View File

@ -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

View File

@ -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
{

View File

@ -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 <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 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

View File

@ -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")
};

View File

@ -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;

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".
### 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