135 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
		
		
			
		
	
	
			135 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								title: UE5 Lyra学习笔记(6)—CommonUI
							 | 
						|||
| 
								 | 
							
								date: 2024-03-22 15:03:59
							 | 
						|||
| 
								 | 
							
								excerpt: 
							 | 
						|||
| 
								 | 
							
								tags: 
							 | 
						|||
| 
								 | 
							
								rating: ⭐
							 | 
						|||
| 
								 | 
							
								---
							 | 
						|||
| 
								 | 
							
								# 前言
							 | 
						|||
| 
								 | 
							
								- 全局管理类
							 | 
						|||
| 
								 | 
							
									- UGameUIManagerSubsystem:父类为UGameInstanceSubsystem,主要的功能是管理CurrentPolicy(UGameUIPolicy)。
							 | 
						|||
| 
								 | 
							
										- UGameUIPolicy:将UPrimaryGameLayout添加到Viewport中。
							 | 
						|||
| 
								 | 
							
											- UPrimaryGameLayout
							 | 
						|||
| 
								 | 
							
								- Widgets
							 | 
						|||
| 
								 | 
							
								使用[[#GameplayAbility]]检测玩家输入并且开启UI。
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								# UPrimaryGameLayout
							 | 
						|||
| 
								 | 
							
								父类为UCommonUserWidget。主要功能为UILayer以及Dormant状态管理。
							 | 
						|||
| 
								 | 
							
								## UILayer
							 | 
						|||
| 
								 | 
							
								使用`TMap<FGameplayTag, TObjectPtr<UCommonActivatableWidgetContainerBase>> Layers;`进行多UI层的管理,不同Layer使用GameplayTag进行区分。Lyra的UILayer分为以下4个层:
							 | 
						|||
| 
								 | 
							
								- UI.Layer.Game
							 | 
						|||
| 
								 | 
							
								- UI.Layer.GameMenu
							 | 
						|||
| 
								 | 
							
								- UI.Layer.Menu
							 | 
						|||
| 
								 | 
							
								- UI.Layer.Modal
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								template <typename ActivatableWidgetT = UCommonActivatableWidget>
							 | 
						|||
| 
								 | 
							
								TSharedPtr<FStreamableHandle> PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr<UCommonActivatableWidget> ActivatableWidgetClass, TFunction<void(EAsyncWidgetLayerState, ActivatableWidgetT*)> StateFunc)
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									static_assert(TIsDerivedFrom<ActivatableWidgetT, UCommonActivatableWidget>::IsDerived, "Only CommonActivatableWidgets can be used here");
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									static FName NAME_PushingWidgetToLayer("PushingWidgetToLayer");
							 | 
						|||
| 
								 | 
							
									const FName SuspendInputToken = bSuspendInputUntilComplete ? UCommonUIExtensions::SuspendInputForPlayer(GetOwningPlayer(), NAME_PushingWidgetToLayer) : NAME_None;
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									FStreamableManager& StreamableManager = UAssetManager::Get().GetStreamableManager();
							 | 
						|||
| 
								 | 
							
									TSharedPtr<FStreamableHandle> StreamingHandle = StreamableManager.RequestAsyncLoad(ActivatableWidgetClass.ToSoftObjectPath(), FStreamableDelegate::CreateWeakLambda(this,
							 | 
						|||
| 
								 | 
							
										[this, LayerName, ActivatableWidgetClass, StateFunc, SuspendInputToken]()
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											UCommonUIExtensions::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
											ActivatableWidgetT* Widget = PushWidgetToLayerStack<ActivatableWidgetT>(LayerName, ActivatableWidgetClass.Get(), [StateFunc](ActivatableWidgetT& WidgetToInit) {
							 | 
						|||
| 
								 | 
							
												StateFunc(EAsyncWidgetLayerState::Initialize, &WidgetToInit);
							 | 
						|||
| 
								 | 
							
											});
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
											StateFunc(EAsyncWidgetLayerState::AfterPush, Widget);
							 | 
						|||
| 
								 | 
							
										})
							 | 
						|||
| 
								 | 
							
									);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									// Setup a cancel delegate so that we can resume input if this handler is canceled.
							 | 
						|||
| 
								 | 
							
									StreamingHandle->BindCancelDelegate(FStreamableDelegate::CreateWeakLambda(this,
							 | 
						|||
| 
								 | 
							
										[this, StateFunc, SuspendInputToken]()
							 | 
						|||
| 
								 | 
							
										{
							 | 
						|||
| 
								 | 
							
											UCommonUIExtensions::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken);
							 | 
						|||
| 
								 | 
							
											StateFunc(EAsyncWidgetLayerState::Canceled, nullptr);
							 | 
						|||
| 
								 | 
							
										})
							 | 
						|||
| 
								 | 
							
									);
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									return StreamingHandle;
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								template <typename ActivatableWidgetT = UCommonActivatableWidget>
							 | 
						|||
| 
								 | 
							
								ActivatableWidgetT* PushWidgetToLayerStack(FGameplayTag LayerName, UClass* ActivatableWidgetClass)
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									return PushWidgetToLayerStack<ActivatableWidgetT>(LayerName, ActivatableWidgetClass, [](ActivatableWidgetT&) {});
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								template <typename ActivatableWidgetT = UCommonActivatableWidget>
							 | 
						|||
| 
								 | 
							
								ActivatableWidgetT* PushWidgetToLayerStack(FGameplayTag LayerName, UClass* ActivatableWidgetClass, TFunctionRef<void(ActivatableWidgetT&)> InitInstanceFunc)
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									static_assert(TIsDerivedFrom<ActivatableWidgetT, UCommonActivatableWidget>::IsDerived, "Only CommonActivatableWidgets can be used here");
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									if (UCommonActivatableWidgetContainerBase* Layer = GetLayerWidget(LayerName))
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										return Layer->AddWidget<ActivatableWidgetT>(ActivatableWidgetClass, InitInstanceFunc);
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
									return nullptr;
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								### RegisterLayer
							 | 
						|||
| 
								 | 
							
								用于向PrimaryGameLayout注册UILayer。(为了方便管理里面的CommonActivatabbleWidgetStack)
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								void RegisterLayer(UPARAM(meta = (Categories = "UI.Layer")) FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase* LayerWidget);
							 | 
						|||
| 
								 | 
							
								```
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								Content/UI的W_OverallUILayout调用,它是一个根组件为Overlay,下面有4个子组件(CommonActivatabbleWidgetStack):
							 | 
						|||
| 
								 | 
							
								- GameLayer_Stack:游戏内UI,类似HUD。
							 | 
						|||
| 
								 | 
							
								- GameMenu_Stack:游戏相关的 "菜单",例如游戏中的库存用户界面。
							 | 
						|||
| 
								 | 
							
								- Menu_Stack:设置界面等。
							 | 
						|||
| 
								 | 
							
								- Model_Stack:游戏内的模态确认对话框、错误对话框。
							 | 
						|||
| 
								 | 
							
								# GameplayAbility
							 | 
						|||
| 
								 | 
							
								Lyra使用2个位于Plugins/ShooterCore/Content/Input/Abilites/ 
							 | 
						|||
| 
								 | 
							
								- GAB_ShowWidget_WhenInputPressed
							 | 
						|||
| 
								 | 
							
								- GAB_ShowWidget_WhileInputHeld
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								 GAB_ShowWidget_WhenInputPressed:![[GAB_ShowWidget_WhenInputPressed.png|800]]
							 | 
						|||
| 
								 | 
							
								 GAB_ShowWidget_WhileInputHeld,略微复杂一些会多一些:![[GAB_ShowWidget_WhileInputHeld.png|1200]]
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								## GameplayTasks
							 | 
						|||
| 
								 | 
							
								- UCancellableAsyncAction
							 | 
						|||
| 
								 | 
							
									- [[#UAsyncAction_PushContentToLayerForPlayer]]
							 | 
						|||
| 
								 | 
							
								
							 | 
						|||
| 
								 | 
							
								核心函数逻辑如下,主要调用UPrimaryGameLayout::PushWidgetToLayerStackAsync()
							 | 
						|||
| 
								 | 
							
								```c++
							 | 
						|||
| 
								 | 
							
								void UAsyncAction_PushContentToLayerForPlayer::Activate()
							 | 
						|||
| 
								 | 
							
								{
							 | 
						|||
| 
								 | 
							
									if (UPrimaryGameLayout* RootLayout = UPrimaryGameLayout::GetPrimaryGameLayout(OwningPlayerPtr.Get()))
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										TWeakObjectPtr<UAsyncAction_PushContentToLayerForPlayer> WeakThis = this;
							 | 
						|||
| 
								 | 
							
										StreamingHandle = RootLayout->PushWidgetToLayerStackAsync<UCommonActivatableWidget>(LayerName, bSuspendInputUntilComplete, WidgetClass, [this, WeakThis](EAsyncWidgetLayerState State, UCommonActivatableWidget* Widget) {
							 | 
						|||
| 
								 | 
							
											if (WeakThis.IsValid())
							 | 
						|||
| 
								 | 
							
											{
							 | 
						|||
| 
								 | 
							
												switch (State)
							 | 
						|||
| 
								 | 
							
												{
							 | 
						|||
| 
								 | 
							
													case EAsyncWidgetLayerState::Initialize:
							 | 
						|||
| 
								 | 
							
														BeforePush.Broadcast(Widget);
							 | 
						|||
| 
								 | 
							
														break;
							 | 
						|||
| 
								 | 
							
													case EAsyncWidgetLayerState::AfterPush:
							 | 
						|||
| 
								 | 
							
														AfterPush.Broadcast(Widget);
							 | 
						|||
| 
								 | 
							
														SetReadyToDestroy();
							 | 
						|||
| 
								 | 
							
														break;
							 | 
						|||
| 
								 | 
							
													case EAsyncWidgetLayerState::Canceled:
							 | 
						|||
| 
								 | 
							
														SetReadyToDestroy();
							 | 
						|||
| 
								 | 
							
														break;
							 | 
						|||
| 
								 | 
							
												}
							 | 
						|||
| 
								 | 
							
											}
							 | 
						|||
| 
								 | 
							
											SetReadyToDestroy();
							 | 
						|||
| 
								 | 
							
										});
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
									else
							 | 
						|||
| 
								 | 
							
									{
							 | 
						|||
| 
								 | 
							
										SetReadyToDestroy();
							 | 
						|||
| 
								 | 
							
									}
							 | 
						|||
| 
								 | 
							
								}
							 | 
						|||
| 
								 | 
							
								```
							 |