UE5 CommonUI的使用
- 前言
- 快速配置
- 配置Game Viewport Client Class
- CommonGameViewportClient源代码
- 创建CommonInputAction表
- 默认导航Action设置
- CommonUIInputData源码
- Bind CommonInputBaseControllerData
- CommonInputBaseControllerData源码
- Common UI控件库和控件样式
- CommonUserWidget 源码
- 小结
注:此处使用版本是UE5.4,不爱看源码的朋友跳过往后翻就行,不影响使用
前言
为啥UE5要推CommonUI?
用过原生的UserWidget的朋友,应该都清楚,UE的UI输入都来自于Focus的UI,当遇到主机游戏上,要频繁切UI,切输入的时候,老会发现当前的UI没有Focus,导致界面按钮没输入,然后卡死。又或者鼠标上切到外部,再切回来的时候,会发现Focus又丢失掉了,当你点了Viewport时候,会发现输入跑到了GameViewPort上,整体UI也就丢失了输入。包括它的输入,蓝图WidgetTree里面的UI接收输入时候,它的WidgetTree拥有者,也会接收到输入,没有PlayerController里面的输入的Consume操作。当然,还有很多风格化(样式),手柄导航等诸多考校,大家可以去阅读官方文档:
官网
快速配置
配置Game Viewport Client Class
Project Setting->All Settings下搜索Game Viewport Client Class,将视口切换成CommonGameViewportClient(CommonGameViewportClient包含了有关输入内容)
CommonGameViewportClient源代码
跳过
.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Engine/GameViewportClient.h"
#include "CommonGameViewportClient.generated.h"
class FReply;
DECLARE_DELEGATE_FourParams(FOnRerouteInputDelegate, FInputDeviceId /* InputDeviceId */, FKey /* Key */, EInputEvent /* EventType */, FReply& /* Reply */);
DECLARE_DELEGATE_FourParams(FOnRerouteAxisDelegate, FInputDeviceId /* InputDeviceId */, FKey /* Key */, float /* Delta */, FReply& /* Reply */);
DECLARE_DELEGATE_FiveParams(FOnRerouteTouchDelegate, int32 /* ControllerId */, uint32 /* TouchId */, ETouchType::Type /* TouchType */, const FVector2D& /* TouchLocation */, FReply& /* Reply */);
/**
* CommonUI Viewport to reroute input to UI first. Needed to allow CommonUI to route / handle inputs.
*/
UCLASS(Within = Engine, transient, config = Engine)
class COMMONUI_API UCommonGameViewportClient : public UGameViewportClient
{
GENERATED_BODY()
public:
UCommonGameViewportClient(FVTableHelper& Helper);
virtual ~UCommonGameViewportClient();
// UGameViewportClient interface begin
virtual bool InputKey(const FInputKeyEventArgs& EventArgs) override;
virtual bool InputAxis(FViewport* InViewport, FInputDeviceId InputDevice, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad) override;
virtual bool InputTouch(FViewport* InViewport, int32 ControllerId, uint32 Handle, ETouchType::Type Type, const FVector2D& TouchLocation, float Force, FDateTime DeviceTimestamp, uint32 TouchpadIndex) override;
// UGameViewportClient interface end
FOnRerouteInputDelegate& OnRerouteInput() { return RerouteInput; }
FOnRerouteAxisDelegate& OnRerouteAxis() { return RerouteAxis; }
FOnRerouteTouchDelegate& OnRerouteTouch() { return RerouteTouch; }
FOnRerouteInputDelegate& OnRerouteBlockedInput() { return RerouteBlockedInput; }
/** Default Handler for Key input. */
UE_DEPRECATED(5.1, "This version of HandleRerouteInput has been deprecated. Please use the version that takes an FInputDeviceId instead")
virtual void HandleRerouteInput(int32 ControllerId, FKey Key, EInputEvent EventType, FReply& Reply);
/** Default Handler for Key input. */
virtual void HandleRerouteInput(FInputDeviceId DeviceId, FKey Key, EInputEvent EventType, FReply& Reply);
/** Default Handler for Axis input. */
UE_DEPRECATED(5.1, "This version of HandleRerouteAxis has been deprecated. Please use the version that takes an FInputDeviceId instead")
virtual void HandleRerouteAxis(int32 ControllerId, FKey Key, float Delta, FReply& Reply);
/** Default Handler for Axis input. */
virtual void HandleRerouteAxis(FInputDeviceId DeviceId, FKey Key, float Delta, FReply& Reply);
/** Default Handler for Touch input. */
virtual void HandleRerouteTouch(int32 ControllerId, uint32 TouchId, ETouchType::Type TouchType, const FVector2D& TouchLocation, FReply& Reply);
protected:
/** Console window & fullscreen shortcut have higher priority than UI */
virtual bool IsKeyPriorityAboveUI(const FInputKeyEventArgs& EventArgs);
FOnRerouteInputDelegate RerouteInput;
FOnRerouteAxisDelegate RerouteAxis;
FOnRerouteTouchDelegate RerouteTouch;
FOnRerouteInputDelegate RerouteBlockedInput;
};
#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_2
#include "CoreMinimal.h"
#include "Input/Reply.h"
#include "InputCoreTypes.h"
#include "InputKeyEventArgs.h"
#include "UObject/ObjectMacros.h"
#endif
.cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonGameViewportClient.h"
#include "Engine/Console.h"
#include "Engine/GameInstance.h"
#include "Engine/LocalPlayer.h"
#include "InputKeyEventArgs.h"
#if WITH_EDITOR
#endif // WITH_EDITOR
#include "Input/CommonUIActionRouterBase.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonGameViewportClient)
#define LOCTEXT_NAMESPACE ""
static const FName NAME_Typing = FName(TEXT("Typing"));
static const FName NAME_Open = FName(TEXT("Open"));
UCommonGameViewportClient::UCommonGameViewportClient(FVTableHelper& Helper) : Super(Helper)
{
}
UCommonGameViewportClient::~UCommonGameViewportClient()
{
}
bool UCommonGameViewportClient::InputKey(const FInputKeyEventArgs& InEventArgs)
{
FInputKeyEventArgs EventArgs = InEventArgs;
if (IsKeyPriorityAboveUI(EventArgs))
{
return true;
}
// Check override before UI
if (OnOverrideInputKey().IsBound())
{
if (OnOverrideInputKey().Execute(EventArgs))
{
return true;
}
}
// The input is fair game for handling - the UI gets first dibs
#if !UE_BUILD_SHIPPING
if (ViewportConsole && !ViewportConsole->ConsoleState.IsEqual(NAME_Typing) && !ViewportConsole->ConsoleState.IsEqual(NAME_Open))
#endif
{
FReply Result = FReply::Unhandled();
if (!OnRerouteInput().ExecuteIfBound(EventArgs.InputDevice, EventArgs.Key, EventArgs.Event, Result))
{
HandleRerouteInput(EventArgs.InputDevice, EventArgs.Key, EventArgs.Event, Result);
}
if (Result.IsEventHandled())
{
return true;
}
}
return Super::InputKey(EventArgs);
}
bool UCommonGameViewportClient::InputAxis(FViewport* InViewport, FInputDeviceId InputDevice, FKey Key, float Delta, float DeltaTime, int32 NumSamples, bool bGamepad)
{
FReply RerouteResult = FReply::Unhandled();
if (!OnRerouteAxis().ExecuteIfBound(InputDevice, Key, Delta, RerouteResult))
{
HandleRerouteAxis(InputDevice, Key, Delta, RerouteResult);
}
if (RerouteResult.IsEventHandled())
{
return true;
}
return Super::InputAxis(InViewport, InputDevice, Key, Delta, DeltaTime, NumSamples, bGamepad);
}
bool UCommonGameViewportClient::InputTouch(FViewport* InViewport, int32 ControllerId, uint32 Handle, ETouchType::Type Type, const FVector2D& TouchLocation, float Force, FDateTime DeviceTimestamp, uint32 TouchpadIndex)
{
#if !UE_BUILD_SHIPPING
if (ViewportConsole != NULL && (ViewportConsole->ConsoleState != NAME_Typing) && (ViewportConsole->ConsoleState != NAME_Open))
#endif
{
FReply Result = FReply::Unhandled();
if (!OnRerouteTouch().ExecuteIfBound(ControllerId, Handle, Type, TouchLocation, Result))
{
HandleRerouteTouch(ControllerId, Handle, Type, TouchLocation, Result);
}
if (Result.IsEventHandled())
{
return true;
}
}
return Super::InputTouch(InViewport, ControllerId, Handle, Type, TouchLocation, Force, DeviceTimestamp, TouchpadIndex);
}
void UCommonGameViewportClient::HandleRerouteInput(FInputDeviceId DeviceId, FKey Key, EInputEvent EventType, FReply& Reply)
{
FPlatformUserId OwningPlatformUser = IPlatformInputDeviceMapper::Get().GetUserForInputDevice(DeviceId);
ULocalPlayer* LocalPlayer = GameInstance->FindLocalPlayerFromPlatformUserId(OwningPlatformUser);
Reply = FReply::Unhandled();
if (LocalPlayer)
{
UCommonUIActionRouterBase* ActionRouter = LocalPlayer->GetSubsystem<UCommonUIActionRouterBase>();
if (ensure(ActionRouter))
{
ERouteUIInputResult InputResult = ActionRouter->ProcessInput(Key, EventType);
if (InputResult == ERouteUIInputResult::BlockGameInput)
{
// We need to set the reply as handled otherwise the input won't actually be blocked from reaching the viewport.
Reply = FReply::Handled();
// Notify interested parties that we blocked the input.
OnRerouteBlockedInput().ExecuteIfBound(DeviceId, Key, EventType, Reply);
}
else if (InputResult == ERouteUIInputResult::Handled)
{
Reply = FReply::Handled();
}
}
}
}
void UCommonGameViewportClient::HandleRerouteInput(int32 ControllerId, FKey Key, EInputEvent EventType, FReply& Reply)
{
// Remap the old int32 ControllerId to the new platform user and input device ID
FPlatformUserId UserId = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerId);
FInputDeviceId DeviceID = INPUTDEVICEID_NONE;
IPlatformInputDeviceMapper::Get().RemapControllerIdToPlatformUserAndDevice(ControllerId, UserId, DeviceID);
return HandleRerouteInput(DeviceID, Key, EventType, Reply);
}
void UCommonGameViewportClient::HandleRerouteAxis(FInputDeviceId DeviceId, FKey Key, float Delta, FReply& Reply)
{
// Get the ownign platform user for this input device and their local player
FPlatformUserId OwningPlatformUser = IPlatformInputDeviceMapper::Get().GetUserForInputDevice(DeviceId);
ULocalPlayer* LocalPlayer = GameInstance->FindLocalPlayerFromPlatformUserId(OwningPlatformUser);
Reply = FReply::Unhandled();
if (LocalPlayer)
{
UCommonUIActionRouterBase* ActionRouter = LocalPlayer->GetSubsystem<UCommonUIActionRouterBase>();
if (ensure(ActionRouter))
{
// We don't actually use axis inputs that reach the game viewport UI land for anything, we just want block them reaching the game when they shouldn't
if (!ActionRouter->CanProcessNormalGameInput())
{
Reply = FReply::Handled();
}
}
}
}
void UCommonGameViewportClient::HandleRerouteAxis(int32 ControllerId, FKey Key, float Delta, FReply& Reply)
{
// Remap the old int32 ControllerId to the new platform user and input device ID
FPlatformUserId UserId = FGenericPlatformMisc::GetPlatformUserForUserIndex(ControllerId);
FInputDeviceId DeviceID = INPUTDEVICEID_NONE;
IPlatformInputDeviceMapper::Get().RemapControllerIdToPlatformUserAndDevice(ControllerId, UserId, DeviceID);
return HandleRerouteAxis(DeviceID, Key, Delta, Reply);
}
void UCommonGameViewportClient::HandleRerouteTouch(int32 ControllerId, uint32 TouchId, ETouchType::Type TouchType, const FVector2D& TouchLocation, FReply& Reply)
{
ULocalPlayer* LocalPlayer = GameInstance->FindLocalPlayerFromControllerId(ControllerId);
Reply = FReply::Unhandled();
if (LocalPlayer && TouchId < EKeys::NUM_TOUCH_KEYS)
{
FKey KeyPressed = EKeys::TouchKeys[TouchId];
if (KeyPressed.IsValid())
{
UCommonUIActionRouterBase* ActionRouter = LocalPlayer->GetSubsystem<UCommonUIActionRouterBase>();
if (ensure(ActionRouter))
{
EInputEvent SimilarInputEvent = IE_MAX;
switch (TouchType)
{
case ETouchType::Began:
SimilarInputEvent = IE_Pressed;
break;
case ETouchType::Ended:
SimilarInputEvent = IE_Released;
break;
default:
SimilarInputEvent = IE_Repeat;
break;
}
if (ActionRouter->ProcessInput(KeyPressed, SimilarInputEvent) != ERouteUIInputResult::Unhandled)
{
Reply = FReply::Handled();
}
}
}
}
}
bool UCommonGameViewportClient::IsKeyPriorityAboveUI(const FInputKeyEventArgs& EventArgs)
{
#if !UE_BUILD_SHIPPING
// First priority goes to the viewport console regardless any state or setting
if (ViewportConsole && ViewportConsole->InputKey(EventArgs.InputDevice, EventArgs.Key, EventArgs.Event, EventArgs.AmountDepressed, EventArgs.IsGamepad()))
{
return true;
}
#endif
// We'll also treat toggling fullscreen as a system-level sort of input that isn't affected by input filtering
if (TryToggleFullscreenOnInputKey(EventArgs.Key, EventArgs.Event))
{
return true;
}
return false;
}
#undef LOCTEXT_NAMESPACE
创建CommonInputAction表
Common UI使用输入InputAction表来创建能够与各种平台的输入所关联的Action。
选用CommonInputActionDataBase 结构体来创建DataTable:
打开新建的DataTable,进行简单的输入配置
Common UI控件将这些抽象的Action映射到实际的输入。比如,你可以将数据表和RowName名称参考添加到 CommonButtonBase 控件中的 触发InputAction。之后,按下该Action所关联的按钮会触发Common UI按钮。
默认导航Action设置
虚幻引擎原生支持指向导航。但是这里,Common UI使用 CommonUIInputData来定义所有平台通用的 点击(Click) 和 返回(Back) 输Input Action。
(简单来说,就是实现多个界面打开关闭时候使用的,比如按A键打开某个界面,按B键关闭某个界面)
创建一个CommonUIInputData。
打开其ClassDefault,配置默认的点击与返回Action
此前我在DataTable里创建了俩测试按键配置
ProjectSetting下搜索InputData,并配置自己的刚创建的测试的CommonUIInputData
CommonUIInputData源码
跳过
.h
UCLASS(Abstract, Blueprintable, ClassGroup = Input, meta = (Category = "Common Input"))
class COMMONINPUT_API UCommonUIInputData : public UObject
{
GENERATED_BODY()
public:
virtual bool NeedsLoadForServer() const override;
public:
UPROPERTY(EditDefaultsOnly, Category = "Properties", meta = (RowType = "/Script/CommonUI.CommonInputActionDataBase"))
FDataTableRowHandle DefaultClickAction;
UPROPERTY(EditDefaultsOnly, Category = "Properties", meta = (RowType = "/Script/CommonUI.CommonInputActionDataBase"))
FDataTableRowHandle DefaultBackAction;
/**
* Newly created CommonButton widgets will use these hold values by default if bRequiresHold is true.
* Inherits from UCommonUIHoldData.
*/
UPROPERTY(EditDefaultsOnly, Category = "Properties")
TSoftClassPtr<UCommonUIHoldData> DefaultHoldData;
UPROPERTY(EditDefaultsOnly, Category = "Properties", meta = (EditCondition = "CommonInput.CommonInputSettings.IsEnhancedInputSupportEnabled", EditConditionHides))
TObjectPtr<UInputAction> EnhancedInputClickAction;
UPROPERTY(EditDefaultsOnly, Category = "Properties", meta = (EditCondition = "CommonInput.CommonInputSettings.IsEnhancedInputSupportEnabled", EditConditionHides))
TObjectPtr<UInputAction> EnhancedInputBackAction;
};
.cpp
bool UCommonUIInputData::NeedsLoadForServer() const
{
const UUserInterfaceSettings* UISettings = GetDefault<UUserInterfaceSettings>();
return UISettings->bLoadWidgetsOnDedicatedServer;
}
Bind CommonInputBaseControllerData
新建CommonInputBaseControllerData
将你的资产配置到Platform Input里面,对应平台配置对应平台内容
很可惜,我只看到Android、IOS、Linux、LinuxArm64、Mac、TVOS、Windows,其他平台还是需要自己写(比如Ps5、XBox,看了一圈源代码,也没找到有哪可以新增平台,虚幻会通过FProjectManager查询支持注册好的平台,如果有哪位朋友找到添加平台的方式,可以分享一波)。
查引用时候,会发现就是获取InputInfo时候会使用它,通俗来说,就是切平台时候,我们有按键提示,不同平台里面的按键不同,意味着显示不同的按键提醒图片,这时候,我们就可以考虑用它来处理。
bool FCommonInputActionDataBase::IsKeyBoundToInputActionData(const FKey& Key) const
{
if (Key == KeyboardInputTypeInfo.GetKey() || Key == TouchInputTypeInfo.GetKey())
{
return true;
}
for (const FName& GamepadName : UCommonInputBaseControllerData::GetRegisteredGamepads())
{
const FCommonInputTypeInfo& TypeInfo = GetInputTypeInfo(ECommonInputType::Gamepad, GamepadName);
if (Key == TypeInfo.GetKey())
{
return true;
}
}
return false;
}
CommonInputBaseControllerData源码
跳过
.h
UCLASS(Abstract, Blueprintable, ClassGroup = Input, meta = (Category = "Common Input"))
class COMMONINPUT_API UCommonInputBaseControllerData : public UObject
{
GENERATED_BODY()
public:
virtual bool NeedsLoadForServer() const override;
virtual bool TryGetInputBrush(FSlateBrush& OutBrush, const FKey& Key) const;
virtual bool TryGetInputBrush(FSlateBrush& OutBrush, const TArray<FKey>& Keys) const;
virtual void PreSave(FObjectPreSaveContext ObjectSaveContext) override;
virtual void PostLoad() override;
private:
#if WITH_EDITORONLY_DATA
UPROPERTY(Transient, EditAnywhere, Category = "Editor")
int32 SetButtonImageHeightTo = 0;
#endif
public:
UPROPERTY(EditDefaultsOnly, Category = "Default")
ECommonInputType InputType;
UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta=(EditCondition="InputType == ECommonInputType::Gamepad", GetOptions = GetRegisteredGamepads))
FName GamepadName;
UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta = (EditCondition = "InputType == ECommonInputType::Gamepad"))
FText GamepadDisplayName;
UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta=(EditCondition="InputType == ECommonInputType::Gamepad"))
FText GamepadCategory;
UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta = (EditCondition = "InputType == ECommonInputType::Gamepad"))
FText GamepadPlatformName;
UPROPERTY(EditDefaultsOnly, Category = "Gamepad", meta=(EditCondition="InputType == ECommonInputType::Gamepad"))
TArray<FInputDeviceIdentifierPair> GamepadHardwareIdMapping;
UPROPERTY(EditDefaultsOnly, Category = "Display")
TSoftObjectPtr<UTexture2D> ControllerTexture;
UPROPERTY(EditDefaultsOnly, Category = "Display")
TSoftObjectPtr<UTexture2D> ControllerButtonMaskTexture;
UPROPERTY(EditDefaultsOnly, Category = "Display", Meta = (TitleProperty = "Key"))
TArray<FCommonInputKeyBrushConfiguration> InputBrushDataMap;
UPROPERTY(EditDefaultsOnly, Category = "Display", Meta = (TitleProperty = "Keys"))
TArray<FCommonInputKeySetBrushConfiguration> InputBrushKeySets;
UFUNCTION()
static const TArray<FName>& GetRegisteredGamepads();
private:
#if WITH_EDITOR
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
};
.cpp
bool UCommonInputBaseControllerData::NeedsLoadForServer() const
{
const UUserInterfaceSettings* UISettings = GetDefault<UUserInterfaceSettings>();
return UISettings->bLoadWidgetsOnDedicatedServer;
}
bool UCommonInputBaseControllerData::TryGetInputBrush(FSlateBrush& OutBrush, const FKey& Key) const
{
const FCommonInputKeyBrushConfiguration* DisplayConfig = InputBrushDataMap.FindByPredicate([&Key](const FCommonInputKeyBrushConfiguration& KeyBrushPair) -> bool
{
return KeyBrushPair.Key == Key;
});
if (DisplayConfig)
{
OutBrush = DisplayConfig->GetInputBrush();
return true;
}
return false;
}
bool UCommonInputBaseControllerData::TryGetInputBrush(FSlateBrush& OutBrush, const TArray<FKey>& Keys) const
{
if (Keys.Num() == 0)
{
return false;
}
if (Keys.Num() == 1)
{
return TryGetInputBrush(OutBrush, Keys[0]);
}
const FCommonInputKeySetBrushConfiguration* DisplayConfig = InputBrushKeySets.FindByPredicate([&Keys](const FCommonInputKeySetBrushConfiguration& KeyBrushPair) -> bool
{
if (KeyBrushPair.Keys.Num() < 2)
{
return false;
}
if (Keys.Num() == KeyBrushPair.Keys.Num())
{
for (const FKey& Key : Keys)
{
if (!KeyBrushPair.Keys.Contains(Key))
{
return false;
}
}
return true;
}
return false;
});
if (DisplayConfig)
{
OutBrush = DisplayConfig->GetInputBrush();
return true;
}
return false;
}
void UCommonInputBaseControllerData::PreSave(FObjectPreSaveContext ObjectSaveContext)
{
Super::PreSave(ObjectSaveContext);
if (!ObjectSaveContext.IsProceduralSave())
{
// These have been organized by a human already, better to sort using this array.
TArray<FKey> AllKeys;
EKeys::GetAllKeys(AllKeys);
// Organize the keys so they're nice and clean
InputBrushDataMap.Sort([&AllKeys](const FCommonInputKeyBrushConfiguration& A, const FCommonInputKeyBrushConfiguration& B) {
return AllKeys.IndexOfByKey(A.Key) < AllKeys.IndexOfByKey(B.Key);
});
// Delete any brush data where we have no image assigned
InputBrushDataMap.RemoveAll([](const FCommonInputKeyBrushConfiguration& A) {
return A.GetInputBrush().GetResourceObject() == nullptr;
});
}
}
void UCommonInputBaseControllerData::PostLoad()
{
Super::PostLoad();
#if WITH_EDITOR
// Have to clear it even though it's transient because it's saved into the CDO.
SetButtonImageHeightTo = 0;
#endif
}
const TArray<FName>& UCommonInputBaseControllerData::GetRegisteredGamepads()
{
auto GenerateRegisteredGamepads = []()
{
TArray<FName> RegisteredGamepads;
RegisteredGamepads.Add(FCommonInputDefaults::GamepadGeneric);
for (const TPair<FName, FDataDrivenPlatformInfo>& Platform : FDataDrivenPlatformInfoRegistry::GetAllPlatformInfos())
{
const FName PlatformName = Platform.Key;
const FDataDrivenPlatformInfo& PlatformInfo = Platform.Value;
// Don't add fake platforms that are used to group real platforms to make configuration for groups of platforms
// simpler.
if (PlatformInfo.bIsFakePlatform)
{
continue;
}
// If the platform uses the standard keyboard for default input, ignore it, all of those platforms will use "PC"
// as their target, so Windows, Linux, but not Mac.
if (PlatformInfo.bDefaultInputStandardKeyboard)
{
continue;
}
// Only add platforms with dedicated gamepads.
if (PlatformInfo.bHasDedicatedGamepad)
{
RegisteredGamepads.Add(PlatformName);
}
}
return RegisteredGamepads;
};
static TArray<FName> RegisteredGamepads = GenerateRegisteredGamepads();
return RegisteredGamepads;
}
#if WITH_EDITOR
void UCommonInputBaseControllerData::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ValueSet)
{
if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UCommonInputBaseControllerData, SetButtonImageHeightTo))
{
if (SetButtonImageHeightTo != 0)
{
for (FCommonInputKeyBrushConfiguration& BrushConfig : InputBrushDataMap)
{
FVector2D NewBrushSize = BrushConfig.KeyBrush.GetImageSize();
if (NewBrushSize.X != 0 && NewBrushSize.Y != 0)
{
NewBrushSize.X = FMath::RoundToInt(SetButtonImageHeightTo * (NewBrushSize.X / NewBrushSize.Y));
NewBrushSize.Y = SetButtonImageHeightTo;
BrushConfig.KeyBrush.SetImageSize(NewBrushSize);
}
}
for (FCommonInputKeySetBrushConfiguration& BrushConfig : InputBrushKeySets)
{
FVector2D NewBrushSize = BrushConfig.KeyBrush.GetImageSize();
if (NewBrushSize.X != 0 && NewBrushSize.Y != 0)
{
NewBrushSize.X = FMath::RoundToInt(SetButtonImageHeightTo * (NewBrushSize.X / NewBrushSize.Y));
NewBrushSize.Y = SetButtonImageHeightTo;
BrushConfig.KeyBrush.SetImageSize(NewBrushSize);
}
}
}
SetButtonImageHeightTo = 0;
}
}
}
#endif
Common UI控件库和控件样式
CommonUI带了一些Style,在它自己的控件里面可以使用这些
一样的ProjectSetting里面配置Style
支持的控件类型:
CommonUIl有俩种主要有UserWdiget,一个CommonActivatableWidget,一个CommonUserWidget 。CommonUserWidget接管了原生虚幻的输入(也像PlayerController一样的方式,采用Consume的方式)。CommonActivatableWidget是继承自CommonUserWidget,它比起原生的CommonUserWidget添加了,激活的一些内容。CommonUserWidget接管了原生虚幻的输入(也像PlayerController一样的方式,采用Consume的方式)
当然,还有其他子控件:
CommonActivatableWidget里面带是带堆栈的,方便用于新旧界面之间的交互
之前的Button,也得继承CommonButtonBase使用
CommonUserWidget 源码
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Blueprint/UserWidget.h"
#include "Input/UIActionBindingHandle.h"
#include "CommonUserWidget.generated.h"
class UCommonInputSubsystem;
class UCommonUISubsystemBase;
class FSlateUser;
struct FUIActionTag;
struct FBindUIActionArgs;
enum class ECommonInputMode : uint8;
UCLASS(ClassGroup = UI, meta = (Category = "Common UI", DisableNativeTick))
class COMMONUI_API UCommonUserWidget : public UUserWidget
{
GENERATED_UCLASS_BODY()
public:
/** Sets whether or not this widget will consume ALL pointer input that reaches it */
UFUNCTION(BlueprintCallable, Category = CommonUserWidget)
void SetConsumePointerInput(bool bInConsumePointerInput);
/** Add a widget to the list of widgets to get scroll events for this input root node */
UFUNCTION(BlueprintCallable, Category = CommonUserWidget)
void RegisterScrollRecipientExternal(const UWidget* AnalogScrollRecipient);
/** Remove a widget from the list of widgets to get scroll events for this input root node */
UFUNCTION(BlueprintCallable, Category = CommonUserWidget)
void UnregisterScrollRecipientExternal(const UWidget* AnalogScrollRecipient);
public:
const TArray<FUIActionBindingHandle>& GetActionBindings() const { return ActionBindings; }
const TArray<TWeakObjectPtr<const UWidget>> GetScrollRecipients() const { return ScrollRecipients; }
/**
* Convenience methods for menu action registrations (any UWidget can register via FCommonUIActionRouter directly, though generally that shouldn't be needed).
* Persistent bindings are *always* listening for input while registered, while normal bindings are only listening when all of this widget's activatable parents are activated.
*/
FUIActionBindingHandle RegisterUIActionBinding(const FBindUIActionArgs& BindActionArgs);
void RemoveActionBinding(FUIActionBindingHandle ActionBinding);
void AddActionBinding(FUIActionBindingHandle ActionBinding);
protected:
virtual void OnWidgetRebuilt() override;
virtual void NativeDestruct() override;
virtual FReply NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual FReply NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual FReply NativeOnMouseWheel(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual FReply NativeOnMouseButtonDoubleClick(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent) override;
virtual FReply NativeOnTouchGesture(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override;
virtual FReply NativeOnTouchStarted(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override;
virtual FReply NativeOnTouchMoved(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override;
virtual FReply NativeOnTouchEnded(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override;
UCommonInputSubsystem* GetInputSubsystem() const;
UCommonUISubsystemBase* GetUISubsystem() const;
TSharedPtr<FSlateUser> GetOwnerSlateUser() const;
template <typename GameInstanceT = UGameInstance>
GameInstanceT& GetGameInstanceChecked() const
{
GameInstanceT* GameInstance = GetGameInstance<GameInstanceT>();
check(GameInstance);
return *GameInstance;
}
template <typename PlayerControllerT = APlayerController>
PlayerControllerT& GetOwningPlayerChecked() const
{
PlayerControllerT* PC = GetOwningPlayer<PlayerControllerT>();
check(PC);
return *PC;
}
void RegisterScrollRecipient(const UWidget& AnalogScrollRecipient);
void UnregisterScrollRecipient(const UWidget& AnalogScrollRecipient);
/** True to generally display this widget's actions in the action bar, assuming it has actions. */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = true))
bool bDisplayInActionBar = false;
private:
/** Set this to true if you don't want any pointer (mouse and touch) input to bubble past this widget */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = true))
bool bConsumePointerInput = false;
private:
TArray<FUIActionBindingHandle> ActionBindings;
TArray<TWeakObjectPtr<const UWidget>> ScrollRecipients;
};
#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_2
#include "CommonUITypes.h"
#endif
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CommonUserWidget.h"
#include "Engine/GameInstance.h"
#include "CommonInputSubsystem.h"
#include "CommonUISubsystemBase.h"
#include "Input/CommonUIActionRouterBase.h"
#include "Input/CommonUIInputTypes.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(CommonUserWidget)
UCommonUserWidget::UCommonUserWidget(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
#if WITH_EDITORONLY_DATA
PaletteCategory = FText::FromString(TEXT("Common UI"));
#endif
}
void UCommonUserWidget::SetConsumePointerInput(bool bInConsumePointerInput)
{
bConsumePointerInput = bInConsumePointerInput;
}
UCommonInputSubsystem* UCommonUserWidget::GetInputSubsystem() const
{
return UCommonInputSubsystem::Get(GetOwningLocalPlayer());
}
UCommonUISubsystemBase* UCommonUserWidget::GetUISubsystem() const
{
return UGameInstance::GetSubsystem<UCommonUISubsystemBase>(GetGameInstance());
}
TSharedPtr<FSlateUser> UCommonUserWidget::GetOwnerSlateUser() const
{
ULocalPlayer* LocalPlayer = GetOwningLocalPlayer();
return LocalPlayer ? LocalPlayer->GetSlateUser() : nullptr;
}
FReply UCommonUserWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
return bConsumePointerInput ? FReply::Handled() : Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
}
FReply UCommonUserWidget::NativeOnMouseButtonUp(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
return bConsumePointerInput ? FReply::Handled() : Super::NativeOnMouseButtonUp(InGeometry, InMouseEvent);
}
FReply UCommonUserWidget::NativeOnMouseWheel(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
return bConsumePointerInput ? FReply::Handled() : Super::NativeOnMouseWheel(InGeometry, InMouseEvent);
}
FReply UCommonUserWidget::NativeOnMouseButtonDoubleClick(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
return bConsumePointerInput ? FReply::Handled() : Super::NativeOnMouseButtonDoubleClick(InGeometry, InMouseEvent);
}
FReply UCommonUserWidget::NativeOnTouchGesture(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
{
return bConsumePointerInput ? FReply::Handled() : Super::NativeOnTouchGesture(InGeometry, InGestureEvent);
}
FReply UCommonUserWidget::NativeOnTouchStarted(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
{
return bConsumePointerInput ? FReply::Handled() : Super::NativeOnTouchStarted(InGeometry, InGestureEvent);
}
FReply UCommonUserWidget::NativeOnTouchMoved(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
{
return bConsumePointerInput ? FReply::Handled() : Super::NativeOnTouchMoved(InGeometry, InGestureEvent);
}
FReply UCommonUserWidget::NativeOnTouchEnded(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
{
return bConsumePointerInput ? FReply::Handled() : Super::NativeOnTouchEnded(InGeometry, InGestureEvent);
}
FUIActionBindingHandle UCommonUserWidget::RegisterUIActionBinding(const FBindUIActionArgs& BindActionArgs)
{
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
{
FBindUIActionArgs FinalBindActionArgs = BindActionArgs;
if (bDisplayInActionBar && !BindActionArgs.bDisplayInActionBar)
{
FinalBindActionArgs.bDisplayInActionBar = true;
}
FUIActionBindingHandle BindingHandle = ActionRouter->RegisterUIActionBinding(*this, FinalBindActionArgs);
ActionBindings.Add(BindingHandle);
return BindingHandle;
}
return FUIActionBindingHandle();
}
void UCommonUserWidget::RemoveActionBinding(FUIActionBindingHandle ActionBinding)
{
ActionBindings.Remove(ActionBinding);
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
{
ActionRouter->RemoveBinding(ActionBinding);
}
}
void UCommonUserWidget::AddActionBinding(FUIActionBindingHandle ActionBinding)
{
ActionBindings.Add(ActionBinding);
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
{
ActionRouter->AddBinding(ActionBinding);
}
}
void UCommonUserWidget::RegisterScrollRecipient(const UWidget& AnalogScrollRecipient)
{
if (!ScrollRecipients.Contains(&AnalogScrollRecipient))
{
ScrollRecipients.Add(&AnalogScrollRecipient);
if (GetCachedWidget())
{
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
{
ActionRouter->RegisterScrollRecipient(AnalogScrollRecipient);
}
}
}
}
void UCommonUserWidget::UnregisterScrollRecipient(const UWidget& AnalogScrollRecipient)
{
if (ScrollRecipients.Remove(&AnalogScrollRecipient) && GetCachedWidget())
{
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
{
ActionRouter->UnregisterScrollRecipient(AnalogScrollRecipient);
}
}
}
void UCommonUserWidget::RegisterScrollRecipientExternal(const UWidget* AnalogScrollRecipient)
{
if (AnalogScrollRecipient != nullptr)
{
RegisterScrollRecipient(*AnalogScrollRecipient);
}
}
void UCommonUserWidget::UnregisterScrollRecipientExternal(const UWidget* AnalogScrollRecipient)
{
if (AnalogScrollRecipient != nullptr)
{
UnregisterScrollRecipient(*AnalogScrollRecipient);
}
}
void UCommonUserWidget::OnWidgetRebuilt()
{
// Using OnWidgetRebuilt instead of NativeConstruct to ensure we register ourselves with the ActionRouter before the child receives NativeConstruct
if (!IsDesignTime())
{
// Clear out any invalid bindings before we bother trying to register them
for (int32 BindingIdx = ActionBindings.Num() - 1; BindingIdx >= 0; --BindingIdx)
{
if (!ActionBindings[BindingIdx].IsValid())
{
ActionBindings.RemoveAt(BindingIdx);
}
}
if (ActionBindings.Num() > 0)
{
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
{
ActionRouter->NotifyUserWidgetConstructed(*this);
}
}
}
Super::OnWidgetRebuilt();
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
{
for (const TWeakObjectPtr<const UWidget>& WidgetPtr : GetScrollRecipients())
{
if (const UWidget* Widget = WidgetPtr.Get())
{
ActionRouter->RegisterScrollRecipient(*Widget);
}
}
}
}
void UCommonUserWidget::NativeDestruct()
{
if (ActionBindings.Num() > 0)
{
if (UCommonUIActionRouterBase* ActionRouter = UCommonUIActionRouterBase::Get(*this))
{
ActionRouter->NotifyUserWidgetDestructed(*this);
}
}
Super::NativeDestruct();
}
小结
总体来说,CommonUI的使用不是很理想,相关联的配置内容也比较繁琐,使用的成本较高,支持的也不够全面,大家根据自己的需求来看把。