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 面板到 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,分别取名为 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:

	// 隐藏 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 {
	
	}
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ARM汇编[0] hello world

文章目录 简述寄存器语法系统调用例程 简述 如果不了解x86汇编的话建议先了解下&#xff0c;x86资料多、环境好搞、容易入门 阿尔可是急于求成的人&#xff0c;希望赶快看到成果&#xff1b; 所以本篇文章不会东讲西讲展开讲&#xff0c;只讲让hello world汇编能跑起来的关键…

2 月 9 日算法练习- 数据结构 - 除夕快乐♪٩(´ω`)و♪

翻转括号序列 暴力过20%数据 思路&#xff1a;括号合法序列问题可以利用前缀和&#xff0c;将"(“看成 1&#xff0c;”)"看成 0&#xff0c;规律是到某个位置为止的前缀和>0并且到最后前缀和0。 #include<bits/stdc.h> using namespace std; const int N…

鸿蒙原生应用再添新丁!央视新闻 入局鸿蒙

鸿蒙原生应用再添新丁&#xff01;央视新闻 入局鸿蒙 来自 HarmonyOS 微博2月9日消息&#xff0c;#央视新闻启动鸿蒙原生应用开发#中央广播电视总台旗舰央视新闻客户端正式宣布&#xff0c;将基于HarmonyOS NEXT鸿蒙星河版&#xff0c;启动央视新闻 鸿蒙原生应用开发&#xf…

扑克牌大小(模拟)

题目 import java.util.Scanner; public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String s sc.nextLine();String[] ss s.split("-");StringBuffer s1 new StringBuffer();StringBuffer s2 new StringBuffer(…

Java基础知识总结(持续更新中)

Java基础知识&#xff08;持续更新&#xff09; 类型转化&#xff1a;数字、字符串、字符之间相互转化 数字 <-> 字符串 // 数字转字符串 // method1int number 5;String str String.valueOf(number);// method2int number 5;Integer itr number; //int装箱为对…

vue3 之 通用组件统一注册全局

components/index.js // 把components中的所组件都进行全局化注册 // 通过插件的方式 import ImageView from ./ImageView/index.vue import Sku from ./XtxSku/index.vue export const componentPlugin {install (app) {// app.component(组件名字&#xff0c;组件配置对象)…

moduleID的使用

整个平台上有很多相同的功能&#xff0c;但是需要不同的内容。例如各个模块自己的首页上有滚动新闻、有友好链接等等。为了公用这些功能&#xff0c;平台引入了moduleID的解决方案。 在前端的配置文件中&#xff0c;配置了模块号&#xff1a; 前端页面请求滚动新闻时&#xff0…

一款VMP内存DUMP及IAT修复工具

前言 加壳是恶意软件常用的技巧之一&#xff0c;随着黑客组织技术的不断成熟&#xff0c;越来越多的恶意软件家族都开始使用更高级的加壳方式&#xff0c;以逃避各种安全软件的检测&#xff0c;还有些恶意软件在代码中会使用各种多态变形、加密混淆、反调试、反反分析等技巧&a…

基于JAVA的教学资源共享平台 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 课程档案模块2.3 课程资源模块2.4 课程作业模块2.5 课程评价模块 三、系统设计3.1 用例设计3.2 类图设计3.3 数据库设计3.3.1 课程档案表3.3.2 课程资源表3.3.3 课程作业表3.3.4 课程评价表 四、系统展…

单片机学习笔记---DS1302时钟

上一节我们讲了DS1302的工作原理&#xff0c;这一节我们开始代码演示。 新创建一个工程写上框架 我们需要LCD1602进行显示&#xff0c;所以我们要将LCD1602调试工具那一节的LCD1602的模块化代码给添加进来 然后我们开始创建一个DS1302.c和DS1302.h 根据原理图&#xff0c;为了…

005集——shp格式数据转换乱码问题——arcgis

shp数据格式与其他数据格式转换过程中会遇到乱码等问题&#xff0c;原因如下&#xff1a; 在Shapefile头文件&#xff08;dBase Header&#xff09;中&#xff0c;一般会包含字符编码信息&#xff0c;这个信息称为 LDID &#xff08; Language Driver ID&#xff09;。在使用ar…

博主:今日无更

今天放个假&#xff0c;不更新文章 &#xff08;占位符&#xff09;

龙芯开启ssh服务——使用Putty连接

本文采用龙芯3A6000处理器&#xff0c;Loongnix操作系统。 为了能使用其他电脑远程操控龙芯电脑&#xff0c;需要打开loongnix的ssh服务&#xff0c;并在其他电脑里使用putty连接loongnix。 1 修改ssh配置文件 命令行输入&#xff1a; sudo vim /etc/ssh/sshd_config按下i插…

防疫物资管理新篇章:Java+SpringBoot实战

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

问题:A注册会计师必须在期中实施实质性程序的情形是()。 #学习方法#其他

问题&#xff1a;A注册会计师必须在期中实施实质性程序的情形是&#xff08;&#xff09;。 A&#xff0e;甲公司整体控制环境不佳 B&#xff0e;将期中实质性程序所获证据与期末数据进行比较 C&#xff0e;评估的认定层次重大错报风险很高 D&#xff0e;没有把握通过在期中…

Sklearn、TensorFlow 与 Keras 机器学习实用指南第三版(七)

原文&#xff1a;Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 第十六章&#xff1a;使用 RNN 和注意力进行自然语言处理 当艾伦图灵在 1950 年想象他著名的Turing 测试时&#xff0c;他提出了…

leetcode(双指针)283.移动零(C++详细题解)DAY3

文章目录 1.题目示例提示 2.解答思路3.实现代码结果 4.总结 1.题目 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 示例 示例 1: 输入…

C语言每日一题(52)单值二叉树

力扣网 965 单值二叉树 题目描述 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff1b;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;[1,1,1,1,1,null,1] 输出&#xff1a;t…

MySQL数据库⑦_复合查询+内外链接(多表/子查询)

目录 1. 回顾基本查询 2. 多表查询 2.1 笛卡尔积初步过滤 3. 自连接 4. 子查询 4.1 单行子查询 4.2 多行子查询 4.2 多列子查询 4.2 from子句中使用子查询 5. 合并查询 6. 内外链接 6.1 内连接 6.2 左外链接 6.2 右外连接 本篇完。 1. 回顾基本查询 先回顾一下…

ctfshow-web11~20-WP

web11 根据提示,查询对ctfshow域名进行dns查询,查看TXT记录 阿里云查询链接:阿里云网站运维检测平台 获取flag成功 web12 根据题目提示,我们访问robots.txt,获取到后台地址 然后我们访问一下后台
最新文章