赞
踩
我们先前写的 “根据面板类型 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(); } }
继续补充遮罩管理器的逻辑。
补全 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 的功能声明方法,不过为了减少重复部分我将笔记内容放到下一节课里。
之前我们写的 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 { } }
剩余的代码留到下一节课。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。