UE5 CommonUI的使用(附源码版)

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

cpp 复制代码
// 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

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

cpp 复制代码
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

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时候会使用它,通俗来说,就是切平台时候,我们有按键提示,不同平台里面的按键不同,意味着显示不同的按键提醒图片,这时候,我们就可以考虑用它来处理。

cpp 复制代码
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

cpp 复制代码
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

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 源码

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

相关推荐
UTwelve4 小时前
【UE5】使用基元数据对材质传参,从而避免新建材质实例
ue5·材质
UTwelve4 小时前
【UE5】在材质中计算模型在屏幕上的比例
ue5·材质
心怀梦想的咸鱼1 天前
UE5 第一人称射击项目学习(二)
学习·ue5
暮志未晚Webgl1 天前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
心怀梦想的咸鱼1 天前
UE5 第一人称射击项目学习(完结)
学习·ue5
暮志未晚Webgl2 天前
110. UE5 GAS RPG 实现玩家角色数据存档
java·前端·ue5
Zhichao_973 天前
【UE5】Slider控件样式
ue5
流行易逝3 天前
23.UE5删除存档
ue5
心怀梦想的咸鱼3 天前
UE5 第一人称射击项目学习(三)
学习·ue5
流行易逝3 天前
22.UE5控件切换器,存档列表,
ue5