• UE5 CommonUI的使用(附源码版)



    注:此处使用版本是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的使用不是很理想,相关联的配置内容也比较繁琐,使用的成本较高,支持的也不够全面,大家根据自己的需求来看把。

  • 相关阅读:
    C语言 cortex-A7核UART总线实验
    Java update scheduler
    唐山海德教育二级建造师报考-----考试科目
    Nacos配置中心交互模型
    Qt post 传base64图片 服务器接收解析图片失败
    秋招/考研面试-操作系统
    强大的音频制作软件 Logic Pro X 最新中文 for mac
    基于STM32的IIC驱动协议实现
    市面上最适合跑步用的耳机有哪些、分享五款最优秀的跑步耳机
    力扣(LeetCode)116. 填充每个节点的下一个右侧节点指针(C++)
  • 原文地址:https://blog.csdn.net/weixin_56946623/article/details/139242238