珠海企业落户申请网站,网站建设存在哪些问题,自己怎么设计公司的logo,广州专业建设网站UE4运用C和框架开发坦克大战教程笔记#xff08;十七#xff09;#xff08;第51~54集#xff09; 51. UI 框架介绍UE4 使用 UI 所面临的问题以及解决思路关于即将编写的 UI 框架的思维导图 52. 管理类与面板类53. 预加载与直接加载54. UI 首次进入界面 51. UI 框架介绍
U… UE4运用C和框架开发坦克大战教程笔记十七第51~54集 51. UI 框架介绍UE4 使用 UI 所面临的问题以及解决思路关于即将编写的 UI 框架的思维导图 52. 管理类与面板类53. 预加载与直接加载54. UI 首次进入界面 51. UI 框架介绍
UE4 使用 UI 所面临的问题以及解决思路
下面的文字截取自梁迪老师准备的 DataDriven 框架文档篇幅稍长即便看了可能也弄不清楚读者可以在后续编写 UI 框架的过程中再回过头来看。
UE4 中运用 UI 时需要解决的问题以及解决思路
1问题 :
UE4 通过蓝图方式开发 UI 界面时需要创建非常多的蓝图 Widget然后手动地逐层添加到主界面设定其位置变换容易造成结构混乱和逻辑混乱在蓝图内创建蓝图 Widget 并且添加进界面的这一过程也会增加耦合不利于项目维护。
通过 C 生成新的 UI 面板并且添加到主界面需要先在主界面设定好放置该 UI 面板的父控件由父控件定义 UI 面板的位置变换获取添加到的父级控件的对象实例进行界面的添加父级控件的对象实例又有自己的父级对象层层获取实例需要引入大量头文件和实例耦合性非常高。
解决思路:
分层叠加 UI 结构所有独立功能的 UI 面板在界面上显示的位置与形式不由主界面决定通过在 UI 面板上设置相应的 UI 类型和变换属性生成时由 UI 管理器通过这些属性进行 UI 的添加。
2问题 :
UI 面板的生成、销毁、显示、隐藏、冻结、激活等生命周期功能往往在复杂的 UI 逻辑中会被自己或者其他对象大量调用各个 UI 面板脚本之间相互引用容易出现 “紧耦合” 的情况导致项目的 “可复用性” 降低。
解决思路:
建立统一的 UI 管理器所有 UI 面板的生成、销毁、显示、隐藏、冻结、激活等接口都只跟 UI 管理器对接使用 FName 对所有 UI 面板进行标识由 UI 管理器通过标识对相应 UI 进行生命周期操作不存在一个 UI 面板直接调用另一个 UI 面板的生命周期功能。
3问题:
UI 面板包括很多不同类型在执行生命周期功能时对其他 UI 面板的影响不同比如弹窗类型的 UI 面板弹出时需要保持 UI 窗体的 “模态显示不允许操作父窗体”普通开发模式下需要手动维护弹窗类型 UI 面板的层级关系手动设置遮罩等十分麻烦。
解决思路:
为 UI 面板设定显示类型包括无影响DoNothing隐藏其他HideOther反向反转Reverse等几种不同的类型在 UI 管理器下实现不同的生命周期函数提供遮罩管理器定义不同透明度与可否穿透遮罩的类型。
4问题 :
UI 面板使用 C 创建时需要获取蓝图 Widget 链接并且加载 UClass 再进行创建。以及 UI 开发需要加载数量众多的图片等资源用普通的方式进行加载十分麻烦。
解决思路:
使用框架的资源加载系统进行 UI 面板的异步生成以及各种 UI 资源的异步加载并且为 UI 面板资源提供预加载提前加载到内存随时调用。
5问题:
UI 面板与其他 UI 面板或者玩家对象之间有事件交互时普通的框架一般是通过 UI 管理器进行消息的传递一般是使用委托或者回调函数等但是这种方式有其局限性面对大量不同类型的方法调用时需要定义很多不同类型的委托。
解决思路:
使用框架的反射事件系统以及注册事件系统爱怎么调就怎么调随心所欲。
关于即将编写的 UI 框架的思维导图
下图截取自梁迪老师准备的 DataDriven 思维导图 下图 弹窗遮罩透明度 的 全透明 英文应该是 Penetrate。 52. 管理类与面板类
基于 DDUserWidget 创建两个 C 类类目标模组选择 DataDriven路径为 /Public/DDUI
一个命名为 DDFrameWidget作为主界面和 UI 管理器。
一个命名为 DDPanelWidget作为面板类。
随后在 DDTypes.h 里添加上一节课的思维导图列出来的枚举为开发 UI 框架作铺垫。
DDTypes.h
// 引入头文件
#include Widgets/Layout/Anchors.h#pragma region UIFrame// 布局类型
UENUM()
enum class ELayoutType : uint8 {Canvas, // 对应 CanvasPanelOverlay, // 对应 Overlay
};// UI层级类型, 可以自己动态添加, 一般6层够用了
UENUM()
enum class ELayoutLevel : uint8
{Level_0 0,Level_1, Level_2,Level_3,Level_All, // 这个层级会隐藏所有ShowGroup的对象
};// 面板类型
UENUM()
enum class EPanelShowType : uint8 {DoNothing, // 不影响其他面板HideOther, // 隐藏其他Reverse, // 反向切换弹窗类型
};// 弹窗遮罩透明度
UENUM()
enum class EPanelLucencyType : uint8 { // 此处老师将 Lucency 拼写错了Lucency, // 全透明, 不能穿透Translucence, // 半透明不能穿透ImPenetrable, // 低透明度不能穿透Penetrate, // 全透明, 可以穿透此处老师拼写错了
};// 面板属性在面板类使用
USTRUCT()
struct FUINature
{GENERATED_BODY()public:// 布局类型UPROPERTY(EditAnywhere)ELayoutType LayoutType;// UI 层级给 HideOther 类型的面板使用指定影响的范围UPROPERTY(EditAnywhere)ELayoutLevel LayoutLevel;// 面板类型UPROPERTY(EditAnywhere)EPanelShowType PanelShowType;// 弹窗遮罩透明度UPROPERTY(EditAnywhere)EPanelLucencyType PanelLucencyType;// Canvas 锚点UPROPERTY(EditAnywhere)FAnchors Anchors;// Canvas 的 Offset(pos, size) Overlay 的 paddingUPROPERTY(EditAnywhere)FMargin Offsets;// Overlay 的水平布局UPROPERTY(EditAnywhere)TEnumAsByteEHorizontalAlignment HAlign;// Overlay 的垂直布局UPROPERTY(EditAnywhere)TEnumAsByteEVerticalAlignment VAlign;};#pragma endregion接下来在面板类加入面板属性结构体 FUINature 的实例以及面板类的生命周期所用到的一些方法。
DDPanelWidget.h
UCLASS()
class DATADRIVEN_API UDDPanelWidget : public UDDUserWidget
{GENERATED_BODY()public:// UI 面板生命周期virtual void PanelEnter(); // 第一次进入界面只会执行一次virtual void PanelDisplay(); // 第二次以及以后 N 次显示在界面virtual void PanelHidden(); // 隐藏virtual void PanelFreeze(); // 冻结virtual void PanelResume(); // 解冻virtual void PanelExit(); // 销毁public:// 面板属性初始化工作留到蓝图内手动配置UPROPERTY(EditAnywhere)FUINature UINature;
};DDPanelWidget.cpp
void UDDPanelWidget::PanelEnter()
{SetVisibility(ESlateVisibility::Visible);
}void UDDPanelWidget::PanelDisplay()
{SetVisibility(ESlateVisibility::Visible);
}void UDDPanelWidget::PanelHidden()
{SetVisibility(ESlateVisibility::Hidden);
}// 下面的先不写
void UDDPanelWidget::PanelFreeze()
{
}void UDDPanelWidget::PanelResume()
{
}void UDDPanelWidget::PanelExit()
{
}来到界面管理类写一下初始化相关的代码。
DDFrameWidget.h
// 提前声明
class UCanvasPanel;
class UImage;
class UOverlay;
class UDDPanelWidget;UCLASS()
class DATADRIVEN_API UDDFrameWidget : public UDDUserWidget
{GENERATED_BODY()public:virtual bool Initialize() override;protected:// 根节点即新建 Widget 蓝图时自带的那个 Canvas PanelUCanvasPanel* RootCanvas;// 此处如果想优化的话可以写成结构体// 分别保存激活的和未激活的 Overlay 控件UPROPERTY() // 通过这个宏避免被回收TArrayUOverlay* ActiveOverlay;UPROPERTY()TArrayUOverlay* UnActiveOverlay;// 分别保存激活的和未激活的 Canvas 控件TArrayUCanvasPanel* ActiveCanvas;TArrayUCanvasPanel* UnActiveCanvas;// 所有 UI 面板键 FName 必须是该面板注册到框架的 ObjectNameTMapFName, UDDPanelWidget* AllPanelGroup;// 已经显示的 UITMapFName, UDDPanelWidget* ShowPanelGroup;// 弹窗栈TMapFName, UDDPanelWidget* PopPanelStack;// 已经加载过的 UI 面板的名字TArrayFName LoadedPanelName;// 遮罩图片UPROPERTY()UImage* MaskPanel;// 透明度值FLinearColor NormalLucency;FLinearColor TranslucenceLucency;FLinearColor ImPenetrableLucency;
};DDFrameWidget.cpp
// 引入头文件
#include Components/CanvasPanel.h
#include Components/CanvasPanelSlot.h
#include Components/Image.h
#include Blueprint/WidgetTree.hbool UDDFrameWidget::Initialize()
{if (!Super::Initialize()) return false;// 获取根节点RootCanvas CastUCanvasPanel(GetRootWidget());// 使自身忽略鼠标事件检测但子控件可以接受检测RootCanvas-SetVisibility(ESlateVisibility::SelfHitTestInvisible);// 生成遮罩MaskPanel WidgetTree-ConstructWidgetUImage(UImage::StaticClass());// 设置透明度NormalLucency FLinearColor(1.f, 1.f, 1.f, 0.f);TranslucenceLucency FLinearColor(0.f, 0.f, 0.f, 0.6f);ImPenetrableLucency FLinearColor(0.f, 0.f, 0.f, 0.3f);return true;
}剩余部分留到下一节课继续编写。
53. 预加载与直接加载
我们先来编写一下 UI 的加载功能。加载 UI 的方式有两种截取自梁迪老师的文档
// (1)提前加载到内存
// 该方法提前加载 UI 面板到内存保存到字典里
void AdvanceLoadPanel(FName PanelName);// (2)显示时如果发现未加载则进行加载
// 该方法为显示 UI 面板如果该名字对应的面板已经存在于内存中则不进行加载
// 如果不存在先加载再进行显示
void ShowUIPanel(FName PanelName);来到 UI 管理类来添加加载 UI 的相关逻辑。
DDFrameWidget.h
public:// 提前加载UFUNCTION()void AdvanceLoadPanel(FName PanelName);// 显示面板 面板 UI 功能面板UFUNCTION()void ShowUIPanel(FName PanelName);// 提前加载面板回调函数UFUNCTION()void AcceptAdvancePanel(FName BackName, UUserWidget* BackWidget);// 显示时加载回调函数UFUNCTION()void AcceptPanelWidget(FName BackName, UUserWidget* BackWidget);protected:// 执行第一次进入 UIvoid DoEnterUIPanel(FName PanelName);// 执行显示 UIvoid DoShowUIPanel(FName PanelName);// 进入界面第一次区分面板类型和布局类型来声明方法void EnterPanelDoNothing(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget);void EnterPanelDoNothing(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget);void EnterPanelHideOther(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget);void EnterPanelHideOther(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget);void EnterPanelReverse(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget);void EnterPanelReverse(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget);DDFrameWidget.cpp
void UDDFrameWidget::AdvanceLoadPanel(FName PanelName)
{// 如果全部组已经存在该面板或者已加载面板名组存在该面板名if (AllPanelGroup.Contains(PanelName) || LoadedPanelName.Contains(PanelName))return;// 进行异步加载BuildSingleClassWealth(EWealthType::Widget, PanelName, AcceptAdvancePanel);// 添加面板名到已加载面板名组LoadedPanelName.Push(PanelName);
}void UDDFrameWidget::ShowUIPanel(FName PanelName)
{// 判断面板是否已经显示在界面上if (ShowPanelGroup.Contains(PanelName) || PopPanelStack.Contains(PanelName))return;// 判断是否已经加载该面板if (!AllPanelGroup.Contains(PanelName) !LoadedPanelName.Contains(PanelName)) {BuildSingleClassWealth(EWealthType::Widget, PanelName, AcceptPanelWidget);LoadedPanelName.Push(PanelName);return;}// 如果存在该 UI 面板if (AllPanelGroup.Contains(PanelName)) {// 判定是否是第一次显示在界面上UDDPanelWidget* PanelWidget *AllPanelGroup.Find(PanelName);// 如果没有父控件说明没有进入过界面if (PanelWidget-GetParent()) DoShowUIPanel(PanelName);else DoEnterUIPanel(PanelName);}
}void UDDFrameWidget::AcceptAdvancePanel(FName BackName, UUserWidget* BackWidget)
{UDDPanelWidget* PanelWidget CastUDDPanelWidget(BackWidget);// 如果加载的界面不是继承自 PanelWidgetif (!PanelWidget) {DDH::Debug() Load UI Panel : Is Not DDPanelWidget DDH::Endl();return;}// 注册到框架不注册类名BackName 必须是面板名以及 ObjectNamePanelWidget-RegisterToModule(ModuleIndex, BackName);// 添加到全部组AllPanelGroup.Add(BackName, PanelWidget);
}void UDDFrameWidget::AcceptPanelWidget(FName BackName, UUserWidget* BackWidget)
{UDDPanelWidget* PanelWidget CastUDDPanelWidget(BackWidget);// 如果加载的界面不是继承自 PanelWidgetif (!PanelWidget) {DDH::Debug() Load UI Panel : Is Not DDPanelWidget DDH::Endl();return;}// 注册到框架不注册类名BackName 必须是面板名以及 ObjectNamePanelWidget-RegisterToModule(ModuleIndex, BackName);// 添加到全部组AllPanelGroup.Add(BackName, PanelWidget);// 进行第一次显示执行进入界面方法DoEnterUIPanel(BackName);
}void UDDFrameWidget::DoEnterUIPanel(FName PanelName)
{// 获取面板实例UDDPanelWidget* PanelWidget *AllPanelGroup.Find(PanelName);// 区分布局类型是 Canvas 还是 Overlay以便添加到相应界面if (PanelWidget-UINature.LayoutType ELayoutType::Canvas) {// 获取布局控件父控件UCanvasPanel* WorkLayout NULL;if (RootCanvas-GetChildrenCount() 0) {// 判断最底层的布局控件是否是 CanvasWorkLayout CastUCanvasPanel(RootCanvas-GetChildAt(RootCanvas-GetChildrenCount() - 1));if (!WorkLayout) {// 判断是否有可用的 Canvasif (UnActiveCanvas.Num() 0) {// 没有就创建一个WorkLayout WidgetTree-ConstructWidgetUCanvasPanel(UCanvasPanel::StaticClass());WorkLayout-SetVisibility(ESlateVisibility::SelfHitTestInvisible);UCanvasPanelSlot* FrameCanvasSlot RootCanvas-AddChildToCanvas(WorkLayout);FrameCanvasSlot-SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));FrameCanvasSlot-SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));}elseWorkLayout UnActiveCanvas.Pop();// 添加到激活组ActiveCanvas.Push(WorkLayout);}}// 如果根节点下没有任何对象else {// 判断是否有可用的 Canvasif (UnActiveCanvas.Num() 0) {WorkLayout WidgetTree-ConstructWidgetUCanvasPanel(UCanvasPanel::StaticClass());WorkLayout-SetVisibility(ESlateVisibility::SelfHitTestInvisible);UCanvasPanelSlot* FrameCanvasSlot RootCanvas-AddChildToCanvas(WorkLayout);FrameCanvasSlot-SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));FrameCanvasSlot-SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));}elseWorkLayout UnActiveCanvas.Pop();// 添加到激活画布组ActiveCanvas.Push(WorkLayout);}// 根据面板类型采用不同的首次进入界面方法switch (PanelWidget-UINature.PanelShowType) {case EPanelShowType::DoNothing:EnterPanelDoNothing(WorkLayout, PanelWidget);break;case EPanelShowType::HideOther:EnterPanelHideOther(WorkLayout, PanelWidget);break;case EPanelShowType::Reverse:EnterPanelReverse(WorkLayout, PanelWidget);break;}}// 布局类型为 Overlay 的留到后面再写else {}
}// 下面这些方法留到后面再写
void UDDFrameWidget::DoShowUIPanel(FName PanelName)
{
}void UDDFrameWidget::EnterPanelDoNothing(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget)
{
}void UDDFrameWidget::EnterPanelDoNothing(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget)
{
}void UDDFrameWidget::EnterPanelHideOther(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget)
{
}void UDDFrameWidget::EnterPanelHideOther(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget)
{
}void UDDFrameWidget::EnterPanelReverse(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget)
{
}void UDDFrameWidget::EnterPanelReverse(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget)
{
}剩余的代码留到下一节课。
54. UI 首次进入界面
上一节课里写的 ShowUIPanel() 需要补充一段逻辑避免刚调用 AdvanceLoadPanel() 提前加载面板资源就立刻显示面板这会导致面板没加载出来就被使用。
接下来补充 EnterPanelDoNothing() 的逻辑即 UI 首次显示需要用到的方法。需要区分 Canvas 和 Overlay 这两种布局类型执行不同的逻辑。
DDFrameWidget.h
protected:// 正在预加载但是收到显示到界面命令时进行循环检测是否加载完毕加载完毕则进行显示void WaitShowPanel();protected:// 正在提前加载但是已经收到显示命令的界面名简称预显示组TArrayFName WaitShowPanelName;// 保存循环检测加载完毕则显示方法的延时循环任务名字FName WaitShowTaskName;DDFrameWidget.cpp
#include Components/Overlay.h
#include Components/OverlaySlot.h
#include DDUI/DDPanelWidget.hbool UDDFrameWidget::Initialize()
{WaitShowTaskName FName(WaitShowTask); return true;
}void UDDFrameWidget::ShowUIPanel(FName PanelName);
{if (ShowPanelGroup.Contains(PanelName) || PopPanelStack.Contains(PanelName))return;if (!AllPanelGroup.Contains(PanelName) !LoadedPanelName.Contains(PanelName)) {BuildSingleClassWealth(EWealthType::Widget, PanelName, AcceptPanelWidget);LoadedPanelName.Push(PanelName);return;}// 如果预加载未完成就调用显示命令启动循环检测函数检测到预加载完成的时候显示 UI 面板if (!AllPanelGroup.Contains(PanelName) LoadedPanelName.Contains(PanelName) !WaitShowPanelName.Contains(PanelName)) {// 添加名字到预显示名字组WaitShowPanelName.Push(PanelName);// 启动循环检测加载完毕则显示函数每 0.3 秒检测一次InvokeRepeat(WaitShowTaskName, 0.3f, 0.3f, this, UDDFrameWidget::WaitShowPanel);return;}// ... 省略
}void UDDFrameWidget::WaitShowPanel()
{TArrayFName CompleteName;// for 循环条件表达式缺了 i 这个错误会在第 60 集改正for (int i 0; i WaitShowPanelName.Num(); i) {if (AllPanelGroup.Contains(WaitShowPanelName[i])) {// 执行进入界面方法DoEnterUIPanel(WaitShowPanelName[i]);// 添加到完成组CompleteName.Push(WaitShowPanelName[i]);}}// 移除完成的 UIfor (int i 0; i CompleteName.Num(); i)WaitShowPanelName.Remove(CompleteName[i]);// 如果没有等待显示的 UI 了停止该循环函数if (WaitShowPanelName.Num() 0)StopInvoke(WaitShowTaskName);
}void UDDFrameWidget::EnterPanelDoNothing(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget)
{// 添加 UI 面板到父控件UCanvasPanelSlot* PanelSlot WorkLayout-AddChildToCanvas(PanelWidget);PanelSlot-SetAnchors(PanelWidget-UINature.Anchors);PanelSlot-SetOffsets(PanelWidget-UINature.Offsets);// 把 UI 面板添加到显示组UI 面板的 GetObjectName()PanelName资源系统下的 WealthName 必须一致ShowPanelGroup.Add(PanelWidget-GetObjectName(), PanelWidget);// 调用 UI 面板的进入界面生命周期PanelWidget-PanelEnter();
}void UDDFrameWidget::EnterPanelDoNothing(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget)
{// 添加 UI 面板到 Overlay 布局UOverlaySlot* PanelSlot WorkLayout-AddChildToOverlay(PanelWidget);PanelSlot-SetPadding(PanelWidget-UINature.Offsets);PanelSlot-SetHorizontalAlignment(PanelWidget-UINature.HAlign);PanelSlot-SetVerticalAlignment(PanelWidget-UINature.VAlign);// 把 UI 面板添加到显示组UI 面板的 GetObjectName()PanelName资源系统下的 WealthName 必须一致ShowPanelGroup.Add(PanelWidget-GetObjectName(), PanelWidget);// 调用 UI 面板的进入界面生命周期PanelWidget-PanelEnter();
}接下来为了测试 UI 首次进入界面的功能我们需要创建 DDFrameWidget 和 DDPanelWidget 的具体类并且创建它们的蓝图界面。
基于 DDFrameWidget 创建一个 C 类目标模组为项目名命名为 RCGameUIFrame路径为默认路径 /UIFrame。如果创建后编译不通过笔者这里的解决方法是在 .cpp 的引入头文件路径前补全 RaceCarFrame/即项目名
再基于 DDPanelWidget 创建六个 C 类目标模组为项目名路径为默认路径 /UIFrame。命名分别为 RCStatePanel、RCShortCutPanel、RCMiniMapPanel、RCBigMapPanel、RCMenuPanel 和 RCOptionPanel。
在 Blueprint 下创建一个名为 UIFrame 的文件夹。在里面创建一个 Widget Blueprint命名为 GameUIFrame。作为游戏主界面 UI。
打开界面将其父类指定为 RCGameUIFrame。
为了让主界面可以在运行时直接生成打开 HUDData将 Auto Widget Data 的配置修改如下 继续在 /Blueprint/UIFrame 下创建两个 Widget Blueprint分别命名为 StatePanel 和 MiniMapPanel作为状态栏面板和小地图面板。然后分别将它们的父类指定为 RCStatePanel 和 RCMiniMapPanel。
给 StatePanel 界面调整如下控件名字都是随机的“_53” 只是为了标明对象 Layout Level 的 Level 0 一般是给背景使用的所以选 Level_1 给普通面板比较合适 如果读者对 UMG 不太熟悉的话可以先去看看 UMG 的相关教程。Offsets 属性控制界面的偏移位置Anchors 控制界面的锚点。
接下来修改 MiniMapPanel 如下 读者可以发现状态栏面板设定为 Overlay 的布局类型小地图设定为 Canvas以便测试两种不同布局类型的 UI 显示在主界面上是否正常。
来到 HUDData指定状态栏和小地图面板的资源数据。将原本 Class Wealth Data 的内容删除掉添加内容如下 在游戏主界面类里重写初始化函数添加界面到窗口并显示状态栏和小地图面板。
RCGameUIFrame.h
public:virtual void DDInit() override;RCGameUIFrame.cpp
void URCGameUIFrame::DDInit()
{AddToViewport();ShowUIPanel(StatePanel);ShowUIPanel(MiniMapPanel);
}之前在 DDFrameWidget 的 DoEnterUIPanel() 只写了 Canvas 首次显示在界面的逻辑还没写 Overlay 的现在给它补上。
DDFrameWidget.cpp
void UDDFrameWidget::DoEnterUIPanel(FName PanelName)
{UDDPanelWidget* PanelWidget *AllPanelGroup.Find(PanelName);if (PanelWidget-UINature.LayoutType ELayoutType::Canvas) {UCanvasPanel* WorkLayout NULL;// 下面这一段作些许调整// 判断最底层的布局控件是否是 Canvasif (RootCanvas-GetChildrenCount() 0)WorkLayout CastUCanvasPanel(RootCanvas-GetChildAt(RootCanvas-GetChildrenCount() - 1));// 如果没有任何对象if (!WorkLayout) {if (UnActiveCanvas.Num() 0) {WorkLayout WidgetTree-ConstructWidgetUCanvasPanel(UCanvasPanel::StaticClass());WorkLayout-SetVisibility(ESlateVisibility::SelfHitTestInvisible);UCanvasPanelSlot* FrameCanvasSlot RootCanvas-AddChildToCanvas(WorkLayout);FrameCanvasSlot-SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));FrameCanvasSlot-SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));}elseWorkLayout UnActiveCanvas.Pop();ActiveCanvas.Push(WorkLayout);}switch (PanelWidget-UINature.PanelShowType) {case EPanelShowType::DoNothing:EnterPanelDoNothing(WorkLayout, PanelWidget);break;case EPanelShowType::HideOther:EnterPanelHideOther(WorkLayout, PanelWidget);break;case EPanelShowType::Reverse:EnterPanelReverse(WorkLayout, PanelWidget);break;}}// 对于 Overlay 的布局类型else {UOverlay* WorkLayout NULL;// 如果存在布局控件试图把最后一个布局控件转换成 Overlayif (RootCanvas-GetChildrenCount() 0) WorkLayout CastUOverlay(RootCanvas-GetChildAt(RootCanvas-GetChildrenCount() - 1));if (!WorkLayout) {if (UnActiveOverlay.Num() 0) {WorkLayout WidgetTree-ConstructWidgetUOverlay(UOverlay::StaticClass());WorkLayout-SetVisibility(ESlateVisibility::SelfHitTestInvisible);UCanvasPanelSlot* FrameCanvasSlot RootCanvas-AddChildToCanvas(WorkLayout);FrameCanvasSlot-SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));FrameCanvasSlot-SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));}elseWorkLayout UnActiveOverlay.Pop();ActiveOverlay.Push(WorkLayout);}switch (PanelWidget-UINature.PanelShowType) {case EPanelShowType::DoNothing:EnterPanelDoNothing(WorkLayout, PanelWidget);break;case EPanelShowType::HideOther:EnterPanelHideOther(WorkLayout, PanelWidget);break;case EPanelShowType::Reverse:EnterPanelReverse(WorkLayout, PanelWidget);break;}}
}编译后运行游戏可以看到状态栏和小地图面板都出现在界面上。说明我们的 UI 框架目前已经可以显示出目标面板了。