UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)

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 面板到 LayoutUCanvasPanelSlot* 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();}}// 添加到 UOverlayUOverlaySlot* 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 准备添加到这个 Layoutvoid 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) {// 比较当前父控件与将要插入的父控件是否相同,当前父控件的子控件为 1if (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,分别取名为 MenuPanelOptionPanel

将 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:// 隐藏 UIUFUNCTION()void HideUIPanel(FName PanelName);protected:// 显示 UIvoid ShowPanelDoNothing(UDDPanelWidget* PanelWidget);void ShowPanelHideOther(UDDPanelWidget* PanelWidget);void ShowPanelReverse(UDDPanelWidget* PanelWidget);// 隐藏 UIvoid 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));// 如果不是 DDPanelWidgetif (!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 {}
}

剩余的代码留到下一节课。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/459112.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯刷题day07——斐波那契与7

1、题目描述 斐波那契数列的递推公式为:FnFn-1Fn-2, 其中F1F21. 请问, 斐波那契数列的第 1 至 202202011200 项&#xff08;含&#xff09;中, 有多少项的个位 是 7 。 答案提交 这是一道结果填空的题, 你只需要算出结果后提交即可。本题的结果为一 个整数, 在提交答案时只填…

157基于matlab的GVF-snake算法能自动收敛到目标区域

基于matlab的GVF-snake算法能自动收敛到目标区域。关键技术GVF snake模型算法matlab源程序&#xff0c;GVF是根据光流场原理,利用变分方法,从图像中得到的一种向量场,该向量场被称为梯度矢量流(GVF)场。 Snake模型称为动态轮廓模型&#xff08;Active Contour Model&#xff0…

图灵日记之java奇妙历险记--抽象类和接口

目录 抽象类概念抽象类语法 接口概念规则使用特性实现多个接口接口的继承接口使用实例Clonable接口和深拷贝抽象类和接口的区别 Object类 抽象类 概念 在面向对象的概念中,所有对象都是通过类来描述的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够…

linux、windows 安装 vue-cli

Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统&#xff0c;提供&#xff1a; 通过 vue/cli 实现的交互式的项目脚手架。 通过 vue/cli vue/cli-service-global 实现的零配置原型开发。 一个运行时依赖 (vue/cli-service) 可升级&#xff1b; 基于 webpack 构建&#…

第九个知识点:内部对象

Date对象: <script>var date new Date();date.getFullYear();//年date.getMonth();//月date.getDate();//日date.getDay();//星期几date.getHours();//时date.getMinutes();//分date.getSeconds();//秒date.getTime();//获取时间戳&#xff0c;时间戳时全球统一&#x…

Django模板(二)

标签if 标签在渲染过程中提供使用逻辑的方法,比如:if和for 标签被 {% 和 %} 包围,如下所示: 由于在模板中,没有办法通过代码缩进判断代码块,所以控制标签都需要有结束的标签 if判断标签{% if %} {% endif %} : # athlete_list 不为空 {% if athlete_list %}# 输出 ath…

152基于matlab的GUI滚动轴承特征频率计算

基于matlab的GUI滚动轴承特征频率计算&#xff0c;输入轴承参数&#xff0c;包括转速&#xff0c;节圆直径、滚子直径、滚子数、接触角&#xff0c;就可得滚动特征频率结果&#xff0c;程序已调通&#xff0c;可直接运行。 152 matlab 轴承特征频率 GUI (xiaohongshu.com)

Leetcode刷题笔记题解(C++):64. 最小路径和

思路一&#xff1a;dfs深度优先搜索&#xff0c;然后取最小路径值&#xff0c;但是时间消耗较大&#xff0c;时间复杂度可能不满足&#xff0c;代码如下&#xff1a; class Solution { public:int res 1000000;int rows,cols;int minPathSum(vector<vector<int>>…

LINUX基础培训二十四之shell字符串处理

一、shell字符串 字符串&#xff08;String&#xff09;就是一系列字符的组合。字符串是 Shell 编程中最常用的数据类型之一&#xff08;除了数字和字符串&#xff0c;也没有其他类型了&#xff09;。字符串可以由单引号 包围&#xff0c;也可以由双引号" "包围&…

LabVIEW双光子荧光显微成像系统开发

双光子显微成像是一种高级荧光显微技术&#xff0c;广泛用于生物学和医学研究&#xff0c;尤其是用于活体组织的深层成像。在双光子成像过程中&#xff0c;振镜&#xff08;Galvo镜&#xff09;扮演了非常关键的角色&#xff0c;它负责精确控制激光束在样本上的扫描路径。以下是…

机器学习 | 揭示EM算法和马尔可夫链的实际应用

目录 初识EM算法 马尔可夫链 HMM模型基础 HMM模型使用 初识EM算法 EM算法是一种求解含有隐变量的概率模型参数的迭代算法。该算法通过交替进行两个步骤&#xff1a;E步骤和M步骤&#xff0c;从而不断逼近模型的最优参数值。EM算法也称期望最大化算法&#xff0c;它是一个基…

如何设计一个预约抢购活动

总体架构设计 互联网大量数据的存储设计 1&#xff09;哈希算法&#xff0c;对商品ID进行分片 节点取模的形式&#xff0c;优点是均匀分布&#xff0c;缺点是扩展性不好。所以&#xff0c;我们可以采用一致性hash。 一致性HASH的优点&#xff1a; 解决单一热点问题&#xf…