UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)
- 55. UI 进入退出动画
- HideOther 面板出现时隐藏其他面板
- 添加面板出现和收起的动画效果
- 编写遮罩管理器前的准备
- 56. 弹窗进入界面
- 57. UI 显示隐藏与遮罩转移
- 完善遮罩管理器
55. UI 进入退出动画
HideOther 面板出现时隐藏其他面板
我们先前写的 “根据面板类型 PanelShowType
采用不同的首次进入界面方法”,只写了对于 DoNothing
面板类型的首次进入界面逻辑,接下来我们来补全 HideOther
面板类型的相关逻辑,Reverse
的留到后面再写。
HideOther
面板类型的进入界面后会隐藏同 Level 下的其他面板(不包括弹窗),如果进入的是 Level_All 层级,则隐藏所有层级的其他面板(不包括弹窗)。
DDFrameWidget.cpp
void UDDFrameWidget::EnterPanelHideOther(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget)
{
// 将显示组的同一层级的其他对象都隐藏,如果是 Level_All 就全部隐藏,Level_All 优先级高
for (TMap<FName, UDDPanelWidget*>::TIterator It(ShowPanelGroup); It; ++It) {
if (PanelWidget->UINature.LayoutLevel == ELayoutLevel::Level_All || PanelWidget->UINature.LayoutLevel == It.Value()->UINature.LayoutLevel) {
It.Value()->PanelHidden();
}
}
// 添加 UI 面板到 Layout
UCanvasPanelSlot* PanelSlot = WorkLayout->AddChildToCanvas(PanelWidget);
PanelSlot->SetAnchors(PanelWidget->UINature.Anchors);
PanelSlot->SetOffsets(PanelWidget->UINature.Offsets);
// 将 UI 面板添加到显示组
ShowPanelGroup.Add(PanelWidget->GetObjectName(), PanelWidget);
// 调用进入界面生命周期函数
PanelWidget->PanelEnter();
}
void UDDFrameWidget::EnterPanelHideOther(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget)
{
// 将显示组的同一层级的其他对象都隐藏,如果是 Level_All 就全部隐藏,Level_All 优先级高
for (TMap<FName, UDDPanelWidget*>::TIterator It(ShowPanelGroup); It; ++It) {
if (PanelWidget->UINature.LayoutLevel == ELayoutLevel::Level_All || PanelWidget->UINature.LayoutLevel == It.Value()->UINature.LayoutLevel) {
It.Value()->PanelHidden();
}
}
// 添加到 UOverlay
UOverlaySlot* PanelSlot = WorkLayout->AddChildToOverlay(PanelWidget);
PanelSlot->SetHorizontalAlignment(PanelWidget->UINature.HAlign);
PanelSlot->SetVerticalAlignment(PanelWidget->UINature.VAlign);
// 将 UI 面板添加到显示组
ShowPanelGroup.Add(PanelWidget->GetObjectName(), PanelWidget);
// 调用进入界面生命周期函数
PanelWidget->PanelEnter();
}
为了测试 HideOther
面板类型的面板首次进入界面,我们使用协程方法来安排各面板的出场顺序。等下会新建一个蓝图界面 BigMapPanel,并且将其设置为 HideOther
面板类型。
RCGameUIFrame.h
public:
// 协程方法,用于安排面板出现顺序
DDCoroTask* UIProcess();
RCGameUIFrame.cpp
void URCGameUIFrame::DDInit()
{
AddToViewport();
// 将显示面板代码放到协程方法里,此处替换为启动协程
StartCoroutine("UIProcess", UIProcess());
}
DDCoroTask* URCGameUIFrame::UIProcess()
{
DDCORO_PARAM(URCGameUIFrame);
#include DDCORO_BEGIN()
D->ShowUIPanel("StatePanel");
D->ShowUIPanel("MiniMapPanel");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(10.f); // 挂起 10 秒
D->ShowUIPanel("BigMapPanel");
#include DDCORO_END()
}
编译后,在 Blueprint/UIFrame 下创建一个 Widget Blueprint,命名为 BigMapPanel。打开界面并将其父类改成 RCBigMapPanel。随后将界面修改如下:
来到 HUDData,给 Class Wealth Data 添加一个元素如下:
运行游戏,10 秒后大地图面板会显示,原本界面上的状态栏和小地图面板会隐藏。(这里笔者为了方便读者查看效果,换了一张比较明显的图,后面课程也会要求替换)
(注:UI 框架的动图,笔者都会缩短挂起时间或稍作剪辑,所以从动图里看到会觉得变化比较快)
添加面板出现和收起的动画效果
接下来我们实现一下面板的出现和收起的动画效果。由于动画是在 UMG 里面做的,所以我们在面板类声明两个蓝图实现的方法,供 C++ 代码调用蓝图动画节点。
DDPanelWidget.h
public:
// 动画回调函数,返回的 float 是动画时长
UFUNCTION(BlueprintImplementableEvent)
float DisplayEnterMovie();
UFUNCTION(BlueprintImplementableEvent)
float DisplayLeaveMovie();
protected:
// 隐藏 UI 面板
void SetSelfHidden();
protected:
// 隐藏动画任务名
static FName PanelHiddenName;
在面板显示和收起的方法里加入调用动画的语句;并且让面板收起动画播放完毕后再隐藏面板(通过延时方法实现)。
DDPanelWidget.cpp
FName UDDPanelWidget::PanelHiddenName(TEXT("PanelHiddenTask"));
void UDDPanelWidget::PanelEnter()
{
SetVisibility(ESlateVisibility::Visible);
// 调用进入界面动画
DisplayEnterMovie();
}
void UDDPanelWidget::PanelDisplay()
{
SetVisibility(ESlateVisibility::Visible);
// 调用进入界面动画
DisplayEnterMovie();
}
void UDDPanelWidget::PanelHidden()
{
// 修改原本代码如下
// 运行完移出界面动画后调用隐藏函数
InvokeDelay(PanelHiddenName, DisplayLeaveMovie(), this, &UDDPanelWidget::SetSelfHidden);
}
void UDDPanelWidget::SetSelfHidden()
{
SetVisibility(ESlateVisibility::Hidden);
}
来到 StatePanel 蓝图界面,给它添加 UI 动画如下:(两个箭头指的是关键帧)
然后在其蓝图节点编辑界面重写 DisplayEnterMovie()
和 DisplayLeaveMovie()
节点:
运行游戏,此时左上角状态栏会有移入动画,10 秒后大地图面板出现,状态栏播放移出动画。
编写遮罩管理器前的准备
Reverse
面板类型一般是弹窗使用的,在写弹窗首次进入界面的逻辑之前我们先考虑写一下遮罩管理器,因为弹窗跟遮罩是同时出现的,遮罩用于覆盖其他界面的可视性以及可交互性。
此处就先添加两种布局类型的遮罩激活方法和遮罩移除方法,一共 3 个方法。后续的留到后面课程继续补充。
DDFrameWidget.h
protected:
// 激活遮罩,第一个参数是保存遮罩的父控件,第二个参数是遮罩透明度
void ActiveMask(UCanvasPanel* WorkLayout, EPanelLucencyType LucencyType);
void ActiveMask(UOverlay* WorkLayout, EPanelLucencyType LucencyType);
// 将 MaskPanel 移出,传入的 Layout 如果不为空,说明 MaskPanel 准备添加到这个 Layout
void RemoveMaskPanel(UPanelWidget* WorkLayout = NULL);
DDFrameWidget.cpp
void UDDFrameWidget::ActiveMask(UCanvasPanel* WorkLayout, EPanelLucencyType LucencyType)
{
}
void UDDFrameWidget::ActiveMask(UOverlay* WorkLayout, EPanelLucencyType LucencyType)
{
}
void UDDFrameWidget::RemoveMaskPanel(UPanelWidget* WorkLayout)
{
// 获取遮罩当前父控件
UPanelWidget* MaskParent = MaskPanel->GetParent();
if (MaskParent) {
// 比较当前父控件与将要插入的父控件是否相同,当前父控件的子控件为 1
if (MaskParent != WorkLayout && MaskParent->GetChildrenCount() == 1) {
MaskParent->RemoveFromParent();
UCanvasPanel* ParentCanvas = Cast<UCanvasPanel>(MaskParent);
UOverlay* ParentOverlay = Cast<UOverlay>(MaskParent);
if (ParentCanvas) {
ActiveCanvas.Remove(ParentCanvas);
UnActiveCanvas.Push(ParentCanvas);
}
else if (ParentOverlay) {
ActiveOverlay.Remove(ParentOverlay);
UnActiveOverlay.Push(ParentOverlay);
}
}
// 将遮罩从父级移除
MaskPanel->RemoveFromParent();
}
}
56. 弹窗进入界面
继续补充遮罩管理器的逻辑。
补全 ActiveMask()
的代码,因为遮罩只有一个,所以每次激活遮罩前都要移除遮罩。
接下来就是补充 Reverse
面板类型的弹窗首次进入界面的逻辑。我们先前声明了一个弹窗栈 PopPanelStack
,如果两个弹窗一前一后地出现在界面上,那么先进入的弹窗会进入冻结状态,不允许被操作。
DDFrameWidget.cpp
void UDDFrameWidget::EnterPanelReverse(UCanvasPanel* WorkLayout, UDDPanelWidget* PanelWidget)
{
// 把栈内最后一个节点冻结
if (PopPanelStack.Num() > 0) {
TArray<UDDPanelWidget*> PanelStack;
PopPanelStack.GenerateValueArray(PanelStack);
PanelStack[PanelStack.Num() - 1]->PanelFreeze();
}
// 激活遮罩
ActiveMask(WorkLayout, PanelWidget->UINature.PanelLucencyType);
// 添加弹窗到界面
UCanvasPanelSlot* PanelSlot = WorkLayout->AddChildToCanvas(PanelWidget);
PanelSlot->SetAnchors(PanelWidget->UINature.Anchors);
PanelSlot->SetOffsets(PanelWidget->UINature.Offsets);
// 添加弹窗到栈,并且运行进入生命周期函数
PopPanelStack.Add(PanelWidget->GetObjectName(), PanelWidget);
PanelWidget->PanelEnter();
}
void UDDFrameWidget::EnterPanelReverse(UOverlay* WorkLayout, UDDPanelWidget* PanelWidget)
{
// 把栈内最后一个节点冻结
if (PopPanelStack.Num() > 0) {
TArray<UDDPanelWidget*> PanelStack;
PopPanelStack.GenerateValueArray(PanelStack);
PanelStack[PanelStack.Num() - 1]->PanelFreeze();
}
// 激活遮罩
ActiveMask(WorkLayout, PanelWidget->UINature.PanelLucencyType);
// 添加弹窗到界面
UOverlaySlot* PanelSlot = WorkLayout->AddChildToOverlay(PanelWidget);
PanelSlot->SetPadding(PanelWidget->UINature.Offsets);
PanelSlot->SetHorizontalAlignment(PanelWidget->UINature.HAlign);
PanelSlot->SetVerticalAlignment(PanelWidget->UINature.VAlign);
// 添加弹窗到栈,并且运行进入生命周期函数
PopPanelStack.Add(PanelWidget->GetObjectName(), PanelWidget);
PanelWidget->PanelEnter();
}
void UDDFrameWidget::ActiveMask(UCanvasPanel* WorkLayout, EPanelLucencyType LucencyType)
{
// 移出遮罩
RemoveMaskPanel(WorkLayout);
// 添加遮罩到新的父控件
UCanvasPanelSlot* MaskSlot = WorkLayout->AddChildToCanvas(MaskPanel);
MaskSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));
MaskSlot->SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));
// 根据透明类型设置透明度
switch (LucencyType) {
case EPanelLucencyType::Lucency:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(NormalLucency);
break;
case EPanelLucencyType::Translucence:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(TranslucenceLucency);
break;
case EPanelLucencyType::ImPenetrable:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(ImPenetrableLucency);
break;
case EPanelLucencyType::Penetrate:
MaskPanel->SetVisibility(ESlateVisibility::Hidden);
MaskPanel->SetColorAndOpacity(NormalLucency);
break;
}
}
void UDDFrameWidget::ActiveMask(UOverlay* WorkLayout, EPanelLucencyType LucencyType)
{
// 移出遮罩
RemoveMaskPanel(WorkLayout);
// 添加遮罩到新的父控件
UOverlaySlot* MaskSlot = WorkLayout->AddChildToOverlay(MaskPanel);
MaskSlot->SetPadding(FMargin(0.f, 0.f, 0.f, 0.f));
MaskSlot->SetHorizontalAlignment(HAlign_Fill);
MaskSlot->SetVerticalAlignment(VAlign_Fill);
// 根据透明类型设置透明度
switch (LucencyType) {
case EPanelLucencyType::Lucency:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(NormalLucency);
break;
case EPanelLucencyType::Translucence:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(TranslucenceLucency);
break;
case EPanelLucencyType::ImPenetrable:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(ImPenetrableLucency);
break;
case EPanelLucencyType::Penetrate:
MaskPanel->SetVisibility(ESlateVisibility::Hidden);
MaskPanel->SetColorAndOpacity(NormalLucency);
break;
}
}
接下来验证一下弹窗面板首次显示在界面上的功能。来到 RCGameUIFrame 调整一下协程方法里的调用顺序。
RCGameUIFrame.cpp
// 将协程方法修改如下
DDCoroTask* URCGameUIFrame::UIProcess()
{
DDCORO_PARAM(URCGameUIFrame);
#include DDCORO_BEGIN()
D->ShowUIPanel("StatePanel");
D->ShowUIPanel("MiniMapPanel");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(10.f);
D->ShowUIPanel("MenuPanel");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(10.f);
D->ShowUIPanel("OptionPanel");
#include DDCORO_END()
}
编译后,在 Blueprint/UIFrame 下创建两个 Widget Blueprint,分别取名为 MenuPanel 和 OptionPanel。
将 MenuPanel 的父类更改为 RCMenuPanel;OptionPanel 的父类更改为 RCOptionMenu。
将 MenuPanel 更改界面如下:(对于弹窗类来说,面板层级是没有效果的,所以设为 Level 0)
将 OptionPanel 更改界面如下:
来到 HUDData,给 Class Wealth Data 添加两个元素如下:
运行游戏,十秒后可以看到 MenuPanel 出现,并且鼠标可以点击到按钮;再十秒后可以看到 OptionPanel 出现,鼠标可以拖动 Slider 的游标(需准确拖动,否则鼠标会消失,因为目前暂时没有设置好鼠标显示),并且此时 MenuPanel 的按钮无法互动。
最后本集课程结束之前会先给隐藏 UI 和显示 UI 的功能声明方法,不过为了减少重复部分我将笔记内容放到下一节课里。
57. UI 显示隐藏与遮罩转移
之前我们写的 DoEnterUIPanel()
是用于面板首次显示在界面上的,如果后续面板隐藏后重新显示,那就要用到 DoShowUIPanel()
,那么接下来我们需要编写隐藏 UI 面板和重新显示 UI 面板的逻辑。
DDFrameWidget.h
public:
// 隐藏 UI
UFUNCTION()
void HideUIPanel(FName PanelName);
protected:
// 显示 UI
void ShowPanelDoNothing(UDDPanelWidget* PanelWidget);
void ShowPanelHideOther(UDDPanelWidget* PanelWidget);
void ShowPanelReverse(UDDPanelWidget* PanelWidget);
// 隐藏 UI
void HidePanelDoNothing(UDDPanelWidget* PanelWidget);
void HidePanelHideOther(UDDPanelWidget* PanelWidget);
void HidePanelReverse(UDDPanelWidget* PanelWidget);
DDFrameWidget.cpp
void UDDFrameWidget::DoShowUIPanel(FName PanelName)
{
// 从全部组获取对象
UDDPanelWidget* PanelWidget = *AllPanelGroup.Find(PanelName);
// 根据 UI 面板类型调用不同的显示方法
switch (PanelWidget->UINature.PanelShowType) {
case EPanelShowType::DoNothing:
ShowPanelDoNothing(PanelWidget);
break;
case EPanelShowType::HideOther:
ShowPanelHideOther(PanelWidget);
break;
case EPanelShowType::Reverse:
ShowPanelReverse(PanelWidget);
break;
}
}
void UDDFrameWidget::ShowPanelDoNothing(UDDPanelWidget* PanelWidget)
{
// 添加 UI 面板到显示组
ShowPanelGroup.Add(PanelWidget->GetObjectName(), PanelWidget);
PanelWidget->PanelDisplay();
}
void UDDFrameWidget::ShowPanelHideOther(UDDPanelWidget* PanelWidget)
{
// 将显示组的同一层级的其他对象都隐藏,如果是 Level_All 就全部隐藏,Level_All 优先级高
for (TMap<FName, UDDPanelWidget*>::TIterator It(ShowPanelGroup); It; ++It) {
if (PanelWidget->UINature.LayoutLevel == ELayoutLevel::Level_All || PanelWidget->UINature.LayoutLevel == It.Value()->UINature.LayoutLevel) {
It.Value()->PanelHidden();
}
}
// 添加到显示组
ShowPanelGroup.Add(PanelWidget->GetObjectName(), PanelWidget);
PanelWidget->PanelDisplay();
}
// 弹窗的显示和隐藏留到后面再写
void UDDFrameWidget::ShowPanelReverse(UDDPanelWidget* PanelWidget)
{
}
void UDDFrameWidget::HideUIPanel(FName PanelName)
{
// 判断 UI 面板是否在显示组或者弹窗栈
if (!ShowPanelGroup.Contains(PanelName) && !PopPanelStack.Contains(PanelName))
return;
// 获取 UI 面板
UDDPanelWidget* PanelWidget = *AllPanelGroup.Find(PanelName);
// 根据 UI 面板类型调用不同的隐藏方法
switch (PanelWidget->UINature.PanelShowType) {
case EPanelShowType::DoNothing:
HidePanelDoNothing(PanelWidget);
break;
case EPanelShowType::HideOther:
HidePanelHideOther(PanelWidget);
break;
case EPanelShowType::Reverse:
HidePanelReverse(PanelWidget);
break;
}
}
void UDDFrameWidget::HidePanelDoNothing(UDDPanelWidget* PanelWidget)
{
// 从显示组移除
ShowPanelGroup.Remove(PanelWidget->GetObjectName());
// 运行隐藏生命周期
PanelWidget->PanelHidden();
}
void UDDFrameWidget::HidePanelHideOther(UDDPanelWidget* PanelWidget)
{
// 从显示组移除
ShowPanelGroup.Remove(PanelWidget->GetObjectName());
// 显示同一层级下的其他 UI 面板,如果该面板是 Level_All 层级,显示所有显示组的面板
for (TMap<FName, UDDPanelWidget*>::TIterator It(ShowPanelGroup); It; ++It) {
if (PanelWidget->UINature.LayoutLevel == ELayoutLevel::Level_All || PanelWidget->UINature.LayoutLevel == It.Value()->UINature.LayoutLevel)
It.Value()->PanelDisplay();
}
// 运行隐藏生命周期
PanelWidget->PanelHidden();
}
void UDDFrameWidget::HidePanelReverse(UDDPanelWidget* PanelWidget)
{
}
接下来为了测试,我们调整一下 RCGameUIFrame 里面协程方法里的调用逻辑。
同时为了方便测试 UI,我们把输入模式改成仅对 UI 生效,并且显示鼠标。
RCGameUIFrame.cpp
void URCGameUIFrame::DDInit()
{
FInputModeUIOnly InputMode;
InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
UDDCommon::Get()->GetController()->bShowMouseCursor = true;
UDDCommon::Get()->GetController()->SetInputMode(InputMode);
}
// 修改协程方法如下
DDCoroTask* URCGameUIFrame::UIProcess()
{
DDCORO_PARAM(URCGameUIFrame);
#include DDCORO_BEGIN()
D->ShowUIPanel("StatePanel");
D->ShowUIPanel("MiniMapPanel");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(6.f); // 缩短时间
//D->ShowUIPanel("MenuPanel");
D->HideUIPanel("StatePanel");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(6.f);
//D->ShowUIPanel("OptionPanel");
D->ShowUIPanel("StatePanel");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(6.f);
D->ShowUIPanel("BigMapPanel");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(6.f);
D->HideUIPanel("BigMapPanel");
#include DDYIELD_READY()
DDYIELD_RETURN_SECOND(6.f);
D->ShowUIPanel("BigMapPanel");
#include DDCORO_END()
}
编译后,将 BigMapPanel 的图片换成比较深色的图片,方便观察。
运行游戏,可见界面上有状态栏和小地图,
6 秒后状态栏收起,小地图无变化;
再 6 秒后状态栏出现;
再 6 秒后状态栏收起,小地图消失,出现大地图;
再 6 秒后大地图消失,状态栏和小地图重新出现。
最后再过 6 秒,状态栏收起、小地图消失,大地图重新出现,
完善遮罩管理器
在写弹窗的显示和隐藏之前,我们再完善一下之前写的遮罩管理器。
根据逻辑来说,我们设定只能隐藏掉当前界面上层级最靠前的弹窗。之前写的弹窗栈 PopPanelStack
可以让我们知道当前最靠前的弹窗是哪个。
前面我们说到过一前一后出现的两个弹窗,先出现的弹窗 1 会被冻结。那么按道理来说,如果要隐藏后出现的弹窗 2 的话,那么我们先隐藏掉这个弹窗 2,然后再移动遮罩到先出现的弹窗 1 的层级底下。
不过可惜的是,UE4 没有提供这样便捷地将某个控件移动到指定层级下面的功能。所以我们只能在隐藏掉弹窗 2 后,将当前父面板下、比弹窗 1 更高层级的所有面板和弹窗 1 临时保存在一个数组里,随后将它们从父面板移除,然后将遮罩添加进父面板里最靠前的层级,最后再将所有临时保存的面板一个个按顺序地放进父面板(置于遮罩的层级之上)。
DDFrameWidget.h
protected:
// 转移遮罩,将遮罩放置在传入的 UI 面板的下一层
void TransferMask(UDDPanelWidget* PanelWidget);
DDFrameWidget.cpp
void UDDFrameWidget::TransferMask(UDDPanelWidget* PanelWidget)
{
// 临时保存 PanelWidget 以及它上层的所有 UI 面板
TArray<UDDPanelWidget*> AbovePanelStack;
// 临时保存 PanelWidget 以及它上层的所有 UI 面板的布局数据
TArray<FUINature> AboveNatureStack;
// 区分布局
if (PanelWidget->UINature.LayoutType == ELayoutType::Canvas) {
UCanvasPanel* WorkLayout = Cast<UCanvasPanel>(PanelWidget->GetParent());
int32 StartOrder = WorkLayout->GetChildIndex(PanelWidget);
for (int i = StartOrder; i < WorkLayout->GetChildrenCount(); ++i) {
UDDPanelWidget* TempPanelWidget = Cast<UDDPanelWidget>(WorkLayout->GetChildAt(i));
// 如果不是 DDPanelWidget
if (!TempPanelWidget)
continue;
// 保存 UI 面板以及布局数据
AbovePanelStack.Push(TempPanelWidget);
FUINature TempUINature;
UCanvasPanelSlot* TempPanelSlot = Cast<UCanvasPanelSlot>(TempPanelWidget);
TempUINature.Anchors = TempPanelSlot->GetAnchors();
TempUINature.Offsets = TempPanelSlot->GetOffsets();
AboveNatureStack.Push(TempUINature);
}
// 循环移除上层 UI 面板
for (int i = 0; i < AbovePanelStack.Num(); ++i)
AbovePanelStack[i]->RemoveFromParent();
// 添加遮罩到新的父控件
UCanvasPanelSlot* MaskSlot = WorkLayout->AddChildToCanvas(MaskPanel);
MaskSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));
MaskSlot->SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));
// 根据透明类型设置透明度
switch (PanelWidget->UINature.PanelLucencyType) {
case EPanelLucencyType::Lucency:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(NormalLucency);
break;
case EPanelLucencyType::Translucence:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(TranslucenceLucency);
break;
case EPanelLucencyType::ImPenetrable:
MaskPanel->SetVisibility(ESlateVisibility::Visible);
MaskPanel->SetColorAndOpacity(ImPenetrableLucency);
break;
case EPanelLucencyType::Penetrate:
MaskPanel->SetVisibility(ESlateVisibility::Hidden);
MaskPanel->SetColorAndOpacity(NormalLucency);
break;
}
// 把刚才移除的 UI 面板按顺序重新添加到布局控件
for (int i = 0; i < AbovePanelStack.Num(); ++i) {
UCanvasPanelSlot* PanelSlot = WorkLayout->AddChildToCanvas(AbovePanelStack[i]);
PanelSlot->SetAnchors(AboveNatureStack[i].Anchors);
PanelSlot->SetOffsets(AboveNatureStack[i].Offsets);
}
}
// 对于 Overlay 布局类型的面板遮罩转移留到下一节课写
else {
}
}
剩余的代码留到下一节课。