一、外部接入接口:UGameUIManagerSubsystem

主要用来从GameInstance获取玩家的加入退出和销毁通知,并传入UGameUIPolicy
DefaultUIPolicyClassDefaultGame.ini中进行配置

UPROPERTY(config, EditAnywhere)
TSoftClassPtr<UGameUIPolicy> DefaultUIPolicyClass;
void UGameUIManagerSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{if (!CurrentPolicy && !DefaultUIPolicyClass.IsNull()){TSubclassOf<UGameUIPolicy> PolicyClass = DefaultUIPolicyClass.LoadSynchronous();SwitchToPolicy(NewObject<UGameUIPolicy>(this, PolicyClass));}
}
void UGameUIManagerSubsystem::NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer){if (ensure(LocalPlayer) && CurrentPolicy){CurrentPolicy->NotifyPlayerAdded(LocalPlayer);}}void UGameUIManagerSubsystem::NotifyPlayerRemoved(UCommonLocalPlayer* LocalPlayer){if (LocalPlayer && CurrentPolicy){CurrentPolicy->NotifyPlayerRemoved(LocalPlayer);}}

二、对内总管:UGameUIPolicy

用来自动控制·UPrimaryGameLayout·不同情况下(主要是Player的改动)的创建与销毁
LayoutClass可以由GameUIPolicy的蓝图实例来指定

UPROPERTY(EditAnywhere)
TSoftClassPtr<UPrimaryGameLayout> LayoutClass;void UGameUIPolicy::NotifyPlayerAdded(UCommonLocalPlayer* LocalPlayer){LocalPlayer->OnPlayerControllerSet.AddWeakLambda(this, [this](UCommonLocalPlayer* LocalPlayer, APlayerController* PlayerController){NotifyPlayerRemoved(LocalPlayer);if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer)){AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout);LayoutInfo->bAddedToViewport = true;}else{CreateLayoutWidget(LocalPlayer);}});if (FRootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer)){AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout);LayoutInfo->bAddedToViewport = true;}else{CreateLayoutWidget(LocalPlayer);}}void UGameUIPolicy::CreateLayoutWidget(UCommonLocalPlayer* LocalPlayer)
{if (APlayerController* PlayerController = LocalPlayer->GetPlayerController(GetWorld())){TSubclassOf<UPrimaryGameLayout> LayoutWidgetClass = GetLayoutWidgetClass(LocalPlayer);if (ensure(LayoutWidgetClass && !LayoutWidgetClass->HasAnyClassFlags(CLASS_Abstract))){UPrimaryGameLayout* NewLayoutObject = CreateWidget<UPrimaryGameLayout>(PlayerController, LayoutWidgetClass);RootViewportLayouts.Emplace(LocalPlayer, NewLayoutObject, true);AddLayoutToViewport(LocalPlayer, NewLayoutObject);}}
}

三、Layout分层管理:UPrimaryGameLayout

作为游戏的主UI,负责对整个UI框架逻辑的管理,包括栈状态机、Layout、动态加载等。不参与布局与响应逻辑。继承自UCommonUserWidget

UPROPERTY(Transient, meta = (Categories = "UI.Layer"))
TMap<FGameplayTag, UCommonActivatableWidgetContainerBase*> Layers;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, 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;
}void UPrimaryGameLayout::RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase* LayerWidget)
{if (!IsDesignTime()){//过渡相关LayerWidget->OnTransitioningChanged.AddUObject(this, &UPrimaryGameLayout::OnWidgetStackTransitioning);LayerWidget->SetTransitionDuration(0.0);Layers.Add(LayerTag, LayerWidget);}
}

bSuspendInputUntilComplete:在ui加载完成前暂停布局,防止玩家按下delete等误操作
RegisterLayer注册新的LayerWidget容器到PrimaryWidget中,并将Tag作为Key

PushWidgetToLayerStackAsyncUCommonActivatableWidget异步载入后推入对应Tag的UCommonActivatableWidgetContainerBase容器(目前是Stack)中
蓝图节点中与之对应的Task:PushContentTolayerForPlayer

由此,实现了UI的动态加载以及层级显示问题
蓝图实例W_OverallUILayout
仅用于确定层级和Layer注册

四、Widget管理容器:UCommonActivatableWidgetContainerBase

Widget基础容器、Layout层级管理来使用一个Container就是一层Layout,而不再使用优先级
继承自UWidget,不参与布局与响应逻辑。

所有参数:
UPROPERTY(Transient)
TArray<UCommonActivatableWidget*> WidgetList;
UPROPERTY(Transient)
FUserWidgetPool GeneratedWidgetsPool;
UPROPERTY(Transient)
UCommonActivatableWidget* DisplayedWidget;TSharedPtr<SCommonAnimatedSwitcher> MySwitcher;
TSharedPtr<SOverlay> MyOverlay;
TSharedPtr<SSpacer> MyInputGuard;UPROPERTY(EditAnywhere, Category = Transition)
ECommonSwitcherTransition TransitionType;
UPROPERTY(EditAnywhere, Category = Transition)
ETransitionCurve TransitionCurveType;
UPROPERTY(EditAnywhere, Category = Transition)
float TransitionDuration = 0.4f;
  1. 容器WidgetListGeneratedWidgetsPoolDisplayedWidget
    WidgetList:存储激活的Widget
    GeneratedWidgetsPool:存储整个Widget池并负责Widget的动态创建与销毁,DisplayedWidgetMySwitcher确定当前顶层的Widget
  2. UI布局
    MySwitcher:储存并显示位于顶部的SWidget,其在变化时会更新DisplayedWidget为对应的Widget
    MyOverlay:UI的顶层布局
    MyInputGuard:布局的一部分,没有细看
  3. 过渡动画参数
    不表

AddWidget

template <typename ActivatableWidgetT = UCommonActivatableWidget>
ActivatableWidgetT* AddWidget(TSubclassOf<UCommonActivatableWidget> ActivatableWidgetClass)
{// Don't actually add the widget if the cast will failif (ActivatableWidgetClass && ActivatableWidgetClass->IsChildOf<ActivatableWidgetT>()){return Cast<ActivatableWidgetT>(AddWidgetInternal(ActivatableWidgetClass, [](UCommonActivatableWidget&) {}));}return nullptr;
}
UCommonActivatableWidget* UCommonActivatableWidgetContainerBase::AddWidgetInternal(TSubclassOf<UCommonActivatableWidget> ActivatableWidgetClass, TFunctionRef<void(UCommonActivatableWidget&)> InitFunc)
{if (UCommonActivatableWidget* WidgetInstance = GeneratedWidgetsPool.GetOrCreateInstance(ActivatableWidgetClass)){InitFunc(*WidgetInstance);RegisterInstanceInternal(*WidgetInstance);return WidgetInstance;}return nullptr;
}
void UCommonActivatableWidgetContainerBase::RegisterInstanceInternal(UCommonActivatableWidget& NewWidget)
{if (ensure(!WidgetList.Contains(&NewWidget))){WidgetList.Add(&NewWidget);OnWidgetAddedToList(NewWidget);
}}
void UCommonActivatableWidgetStack::OnWidgetAddedToList(UCommonActivatableWidget& AddedWidget)
{unimplamentation();
}}

首先创建(已有则略过)并添加Widget的实例到GeneratedWidgetsPool中,然后注册进WidgetList中。
可以看到,GeneratedWidgetsPoolWidgetList都仅仅是用于存储Widget本体与其对应SWidget的容器,都无法控制容器中Widget的显示与隐藏。所以在这里Widget仅仅是进入了容器,但对Widget的进一步处理被设置为unimplamentation.
GeneratedWidgetsPool仅仅传入的参数仅仅是Class,如果有多个实例的话不确定会返回哪一个,详情请看GeneratedWidgetsPool

RemoveWidget

void UCommonActivatableWidgetContainerBase::RemoveWidget(UCommonActivatableWidget& WidgetToRemove)
{if (&WidgetToRemove == GetActiveWidget()){// To remove the active widget, just deactivate it (if it's already deactivated, then we're already in the process of ditching it)if (WidgetToRemove.IsActivated()){WidgetToRemove.DeactivateWidget();}else{bRemoveDisplayedWidgetPostTransition = true;}}else{// Otherwise if the widget isn't actually being shown right now, yank it right on outTSharedPtr<SWidget> CachedWidget = WidgetToRemove.GetCachedWidget();if (CachedWidget && MySwitcher){ReleaseWidget(CachedWidget.ToSharedRef());}}
}
void UCommonActivatableWidgetContainerBase::ReleaseWidget(const TSharedRef<SWidget>& WidgetToRelease)
{if (UCommonActivatableWidget* ActivatableWidget = ActivatableWidgetFromSlate(WidgetToRelease)){GeneratedWidgetsPool.Release(ActivatableWidget, true);WidgetList.Remove(ActivatableWidget);}if (MySwitcher->RemoveSlot(WidgetToRelease) != INDEX_NONE){ReleasedWidgets.Add(WidgetToRelease);if (ReleasedWidgets.Num() == 1){FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateWeakLambda(this,[this](float){QUICK_SCOPE_CYCLE_COUNTER(STAT_UCommonActivatableWidgetContainerBase_ReleaseWidget);ReleasedWidgets.Reset();return false;}));}}
}
void UCommonActivatableWidgetContainerBase::HandleActiveWidgetDeactivated(UCommonActivatableWidget* DeactivatedWidget)
{if (ensure(DeactivatedWidget == DisplayedWidget) && MySwitcher && MySwitcher->GetActiveWidgetIndex() > 0){DisplayedWidget->OnDeactivated().RemoveAll(this);MySwitcher->TransitionToIndex(MySwitcher->GetActiveWidgetIndex() - 1);}
}void UCommonActivatableWidgetContainerBase::HandleActiveIndexChanged(int32 ActiveWidgetIndex)
{// 1while (MySwitcher->GetNumWidgets() - 1 > ActiveWidgetIndex){TSharedPtr<SWidget> WidgetToRelease = MySwitcher->GetWidget(MySwitcher->GetNumWidgets() - 1);if (ensure(WidgetToRelease)){ReleaseWidget(WidgetToRelease.ToSharedRef());}}//2. Also remove the widget that we just transitioned away from if desiredif (DisplayedWidget && bRemoveDisplayedWidgetPostTransition){if (TSharedPtr<SWidget> DisplayedSlateWidget = DisplayedWidget->GetCachedWidget()){ReleaseWidget(DisplayedSlateWidget.ToSharedRef());}}bRemoveDisplayedWidgetPostTransition = false;// Activate the widget that's now being displayedDisplayedWidget = ActivatableWidgetFromSlate(MySwitcher->GetActiveWidget());if (DisplayedWidget){SetVisibility(ESlateVisibility::SelfHitTestInvisible);DisplayedWidget->OnDeactivated().AddUObject(this, &UCommonActivatableWidgetContainerBase::HandleActiveWidgetDeactivated, DisplayedWidget);DisplayedWidget->ActivateWidget();if (UWorld* MyWorld = GetWorld()){FTimerManager& TimerManager = MyWorld->GetTimerManager();TimerManager.SetTimerForNextTick(FSimpleDelegate::CreateWeakLambda(this, [this]() { InvalidateLayoutAndVolatility(); }));}}else{SetVisibility(ESlateVisibility::Collapsed);}OnDisplayedWidgetChanged().Broadcast(DisplayedWidget);
}
  • 如果是正在激活的,则根据是否是可激活执行取消激活或者标记关闭。否则进行Release,将Widget从GeneratedWidgetsPool和WidgetList移除,并从MySwitcher中进行移除,最后将该Widget对应的的SWidget扔进ReleasedWidgets中。
  • MySwitcher决定了Widget的显示层级。添加时并没有规定进入MySwitcher的方式,而不管什么方式进入MySwitcher的,移除的时候都一样。
    Release时将MySwitcher的ActiveIndex设置为其所在的下一层,而**TransitionToIndex()**会执行绑定的HandleActiveIndexChanged

HandleActiveIndexChanged:

  1. 将当前激活Index之上的Widget都Deactive
  2. 如果需要的话我们也release刚刚显示的Widget?(需要查看别的地方)
  3. 激活现在在MySwitcher中激活的Widget。这里对DisplayedWidget进行了赋值

ClearWidget

void UCommonActivatableWidgetContainerBase::ClearWidgets()
{SetSwitcherIndex(0);
}
void UCommonActivatableWidgetContainerBase::SetSwitcherIndex(int32 TargetIndex, bool bInstantTransition /*= false*/)
{if (MySwitcher && MySwitcher->GetActiveWidgetIndex() != TargetIndex){if (DisplayedWidget){DisplayedWidget->OnDeactivated().RemoveAll(this);if (DisplayedWidget->IsActivated()){DisplayedWidget->DeactivateWidget();}else if (MySwitcher->GetActiveWidgetIndex() != 0){// The displayed widget has already been deactivated by something other than us, so it should be removed from the container// We still need it to remain briefly though until we transition to the new index - then we can remove this entry's slotbRemoveDisplayedWidgetPostTransition = true;}}MySwitcher->TransitionToIndex(TargetIndex, bInstantTransition);
}
}

首先将正在激活的Widget关闭,然后设置MySwitcher的Index为0。

GetActiveWidget

UCommonActivatableWidget* UCommonActivatableWidgetContainerBase::GetActiveWidget() const
{return MySwitcher ? ActivatableWidgetFromSlate(MySwitcher->GetActiveWidget()) : nullptr;
}

返回MySwitcherActiveWidget,而不是自己存储的DisplayedWidget
可以看出显示的主导在于MySwitcher而不是另外两个容器

UCommonActivatableWidgetStack

将进入MySwitcher的方式设置为栈
给栈提供了一个RootWidget

UPROPERTY(EditAnywhere, Category = Content)
TSubclassOf<UCommonActivatableWidget> RootContentWidgetClass;
UPROPERTY(Transient)
UCommonActivatableWidget* RootContentWidget;void UCommonActivatableWidgetStack::SynchronizeProperties()
{Super::SynchronizeProperties();#if WITH_EDITORif (IsDesignTime() && RootContentWidget && RootContentWidget->GetClass() != RootContentWidgetClass){// At design time, account for the possibility of the preview class changingif (RootContentWidget->GetCachedWidget()){MySwitcher->GetChildSlot(0)->DetachWidget();}RootContentWidget = nullptr;}
#endifif (!RootContentWidget && RootContentWidgetClass){// Establish the root content as the blank 0th slot contentRootContentWidget = CreateWidget<UCommonActivatableWidget>(this, RootContentWidgetClass);MySwitcher->GetChildSlot(0)->AttachWidget(RootContentWidget->TakeWidget());SetVisibility(ESlateVisibility::SelfHitTestInvisible);}
}void UCommonActivatableWidgetStack::OnWidgetAddedToList(UCommonActivatableWidget& AddedWidget)
{if (MySwitcher){MySwitcher->AddSlot() [AddedWidget.TakeWidget()];SetSwitcherIndex(MySwitcher->GetNumWidgets() - 1);}
}

SynchronizeProperties
编辑器下如果RootContentWidget与RootContentWidgetClass的类型不同,直接Detach,见英文注释
如果当前还没有RootContentWidget则创建并放置到MySwitcher的首位
OnWidgetAddedToList
实现基类没有处理的添加到MySwitcher问题,直接将Widget放入,并设置SwitcherIndex为新放入的Widget

UCommonActivatableWidgetQueue

将Widget进入MySwitcher的方式设置为队列

void UCommonActivatableWidgetQueue::OnWidgetAddedToList(UCommonActivatableWidget& AddedWidget)
{if (MySwitcher){// Insert after the empty slot 0 and before the already queued widgetsMySwitcher->AddSlot(1) [AddedWidget.TakeWidget()];if (MySwitcher->GetNumWidgets() == 2){// The queue was empty, so we'll show this one immediatelySetSwitcherIndex(1);}else if (DisplayedWidget && !DisplayedWidget->IsActivated() && MySwitcher->GetNumWidgets() == 3){//The displayed widget is on its way out and we should not be going to 0 anymore, we should go to the new oneSetSwitcherIndex(1, true); //and get there fast, we need to finish and evict the old widget before anything else happens}}
}

将Widget插入到index1(index0一直为空),如果之前队列为空则直接显示,之前队列还存在一个但已经Deactive也可以直接显示

总结:

该容器一方面负责了Widget的创建与缓存,另一方面也使用Switch来对容器中UI的显示进行了控制。位于Switch顶层的UI则显示,保证了容器中UI是互斥的,而Pool保证了UI在被Switch移除且Deactive后不被销毁。
而Widget加入时并没有确定进入Switch的方式,而将其放入了子类中实现。
Stack与Queue分别定义了两种进入Switch的方式

五、Widget池:FUserWidgetPool

一个管理Widget创建与销毁的池,主要用Active和Deactive池来存储不同状态下的Widget
细节以后再看

六、用于管理Widget显示的UI:SCommonAnimatedSwitcher

一个实现了过渡动画的SwitcherUI,不表

UE5 Lyra中的UI层级与资产管理相关推荐

  1. ue5 lyra的角色动画系统 持续更新中。。。。

    框架两方面 1.先说动画蓝图 基础角色用了A动画蓝图( ABP_Mannequin_Base),A继承了一个接口AI, 在A中写了整个的动画逻辑比如locomotion,上半身特定动画融合,挨枪子的动 ...

  2. Android产品研发(二十一)--Android中的UI优化

    转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了Android产品研发过程中的代码Review.通过代码Review能够提高产品质量,增强团队成员之间的沟通,提高开发效率,所以良好的产品开发迭代 ...

  3. android 不能在子线程中更新ui的讨论和分析

    问题描述 做过android开发基本都遇见过 ViewRootImpl$CalledFromWrongThreadException,上网一查,得到结果基本都是只能在主线程中更改 ui,子线程要修改 ...

  4. UE5 Lyra游戏内容制作学习总纲

    Lyra,或者说Lyra Starter Game,是一个由虚幻引擎提供的演示项目,旨在向游戏开发人员展示虚幻引擎5的新功能和技术. 简单来看,这个项目是一个第三人称射击对战游戏,让玩家使用各种枪械武 ...

  5. Unity特效和UI层级

    Unity Canvas的三种渲染模式 1.Screen Space - Overlay模式 Overlay 模式下 UI 元素总是渲染在3D元素的上面. 2.Screen Space - Camer ...

  6. 【Based Android】Android Sensor感应器介绍(二)线程中刷新UI 创建一个android测力计...

    上一篇文章http://www.cnblogs.com/octobershiner/archive/2011/11/06/2237880.html介绍了sensor的基本知识以及一个使用其中加速度感应 ...

  7. 二十四、Struts2中的UI标签

    二十四.Struts2中的UI标签 Struts2中UI标签的优势: 数据回显 页面布局和排版(Freemark),struts2提供了一些常用的排版(主题:xhtml默认 simple ajax) ...

  8. Android利用Looper在子线程中改变UI

    MainActivity如下: package cn.testlooper; import android.app.Activity; import android.os.Bundle; import ...

  9. ActionScript工程如何使用Flash CS的fl包中的UI组件(转)

    最近在看ActionScript 3.0 设计模式,书中的例子都是在Flash CS3中开发和测试通过的.我用的开发环境是Flex 3,我新建了一个ActionScript 项目,需要使用fl组件包. ...

最新文章

  1. 关于 JShell,开发人员需要知道的10件事情
  2. boost::inplace_merge相关的测试程序
  3. new fabu
  4. Python高手之路【十】python基础之反射
  5. java 装饰器_JAVA装饰器模式
  6. UML建模工具Visio、Rational Rose、PowerDesign,Visual Paradigm for UML
  7. 培养你的“翁格玛丽”
  8. videojs如何获取请求消息_消息队列中,如何保证消息的顺序性?
  9. 无锁同步-C++11之Atomic和CAS
  10. 一个简单的数字记忆训练软件介绍
  11. 韩昊 20190919-4 单元测试,结对
  12. 不是“饭饭之交”! 李彦宏丁磊CP乌镇神同步
  13. JDBC百宝箱方法集合(增删改等)
  14. web课程设计:网上商城系统
  15. 推荐一个比较好用的画廊展示图片(支持无限轮播)的控件ViewPagerGallery
  16. Word中跨页表格都显示表头
  17. Wiley开放科学大使访谈——刘永鑫
  18. python 梦幻西游_GitHub - BestBurning/mhxy: tensorflow实践:梦幻西游人物弹窗识别
  19. Remove Element
  20. graphics.h头文件_C语言图形(graphics.h头文件功能和示例)

热门文章

  1. redis写入mysql 使用redis做mysql缓存
  2. 两台服务器双向同步文件,远程同步文件 两台服务器上
  3. 面向对象:希望找一位稳重坦荡的大哥哥,走向远方
  4. 蒙特卡洛树搜索-黑白棋(一):黑白棋介绍及棋盘类
  5. pb将字符串s中的c1替换为c2
  6. 教你备赛大唐杯国赛预选赛
  7. 交友盲盒源码PHP附搭建部署教程
  8. 开源进展 | WeCross v1.2.0 发布,实现FISCO BCOS与Fabric 2.0 的跨链适配
  9. freemarker 详细介绍
  10. Freemarker日志优化输出