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

news2024/11/17 16:21:20

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.coloradmin.cn/o/1438283.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DolphinScheduler本地安装

文章目录 前言1. 安装部署DolphinScheduler1.1 启动服务 2. 登录DolphinScheduler界面3. 安装内网穿透工具4. 配置Dolphin Scheduler公网地址5. 固定DolphinScheduler公网地址 前言 本篇教程和大家分享一下DolphinScheduler的安装部署及如何实现公网远程访问&#xff0c;结合内…

mysql入门到精通006-基础篇-多表查询

1、多表关系介绍 1.1 概念 项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求和业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也存在各种联系&#xff0c;基本上分为3种&#xf…

实战分享:SpringBoot在创新创业项目管理中的应用

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

【漏洞复现】斐讯FIR151M路由器未授权下载漏洞

Nx01 产品简介 斐讯数据通信技术有限公司成立于2009年&#xff0c;是为用户提供智慧家庭领域智能产品和云服务的科技创新性企业。 Nx02 漏洞描述 斐讯 FIR151M路由器配置文件未授权下载漏洞,攻击者可利用该漏洞获取敏感信息。 Nx03 产品主页 fofa-query: app"PHICOMM-F…

索引失效问题

1、 like 以%开头&#xff0c;索引无效&#xff1b;当like前缀没有%&#xff0c;后缀有%时&#xff0c;索引有效。 &#xff08;1&#xff09;创建索引 create index text1 on emp(name); &#xff08;2&#xff09;不走索引 EXPLAIN select id,name,age,workno from emp wh…

人工智能基础部分24-人工智能的数学基础,汇集了人工智能数学知识最全面的概况

、 大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能基础部分24-人工智能的数学基础&#xff0c;汇集了人工智能数学知识最全面的概况&#xff0c;深度学习是一种利用多层神经网络对数据进行特征学习和表示学习的机器学习方法。要全面了解深度学习的数学基…

【Linux】环境基础开发工具的使用之gdb详解(三)

前言&#xff1a;上一篇文章中我们讲解了Linux下的gcc与g的使用&#xff0c;今天我们将进一步的学习gdb与makefile来帮我们更好的理解与使用基础开发工具。 &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; 专栏分类:Linux的深度刨析 &#x1f448; …

LabVIEW多任务实时测控系统

LabVIEW多任务实时测控系统 面对现代化工业生产的复杂性和多变性&#xff0c;传统的测控系统已难以满足高效、精准、可靠的监控和控制需求。因此&#xff0c;开发一种基于LabVIEW的智能测控系统&#xff0c;能够提高生产效率&#xff0c;保证生产安全&#xff0c;是解决现代工…

echart - 柱状图添加阴影

有两种方法可以达到这个效果&#xff1a; 1. type: ‘shadow’ tooltip: {trigger: axis,axisPointer: {type: shadow,shadowStyle: {color: #FD8F67,opacity: 0.1,},},},2. type: ‘line’ 用这个方法可以改变阴影的宽度 tooltip: {trigger: axis,axisPointer: {type: li…

数据结构——D/二叉树

&#x1f308;个人主页&#xff1a;慢了半拍 &#x1f525; 创作专栏&#xff1a;《史上最强算法分析》 | 《无味生》 |《史上最强C语言讲解》 | 《史上最强C练习解析》 &#x1f3c6;我的格言&#xff1a;一切只是时间问题。 ​ 1.树概念及结构 1.1树的概念 树是一种非线性的…

S3 Browser工具得使用

新增账号 如果需要设置签名得版本&#xff0c;选择上图右下角得advanced setting

【C#】.net core 6.0 设置根目录下某个文件夹可访问,访问创建的图片等资源

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握。…

linux 06 磁盘管理

01.先管理vm中的磁盘&#xff0c;添加一个磁盘 第一步.vm软件&#xff0c;打开虚拟机设置&#xff0c;添加硬盘 第二步.选择推荐scsi 第三步.创建一个新的虚拟磁盘 第四步. 第五步. 02.在创建好的vm虚拟机中查看刚才创建的磁盘 在centos中/dev 目录是设备目录 sda是磁盘…

开关电源学习之Boost电路

如果我们需要给一个输入电压为5V的芯片供电&#xff0c;而我们只有一个3.3V的电源&#xff0c;那怎么办&#xff1f; 我们能不能把3.3V的电压升到5V&#xff1f; 一、电感的简介 而在升压的电路设计方案中&#xff0c;使用到一个重要的元器件&#xff1a;电感。 电感的特性…

ubuntu远程桌面配置以及常见问题

ubuntu桌面系统配置 ubuntu远程桌面配置如下 第一步&#xff0c;安装xrdp sudo apt-get isntall xrdp安装完检查一下服务是否可以正常启动&#xff0c; sudo systemctl status xrdp如果看到active应该就正常启动了 第二步&#xff0c;开启Ubuntu桌面共享 好接下来我们测试一…

113.乐理基础-五线谱-五线谱的调号(二)

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;五线谱的调号&#xff08;一&#xff09;-CSDN博客 调号一共有15个&#xff1a;如下图 上一个内容里写了&#xff0c;C、D、E、F、G、A、B这七个调号&#xff0c;如下图 然后所有调号的五线谱版本&#xff1a; 然后…

Termux配置安卓编译环境

前言 Termux安装后&#xff0c;就相当于把手机变成了一台Linux服务器&#xff0c;而且现在手机卡通常是能拿到ipv6公网地址的&#xff0c;所以&#xff0c;这个服务器能干啥&#xff1f; 编程搭建网站跑脚本 本文讲述的就是怎么在Termux搭建安卓编译环境&#xff0c;实现手机…

【QT】VS-code报错:LNK2019: 无法解析的外部符号

目录 0.环境 1.问题简述 2.分析报错原因 3.解决方法 1&#xff09;set() 相关语句 2&#xff09;target_link_libraries() 相关语句 4.参考 0.环境 windows11 、 vs-code 、 qt 、 c、编译器为vs2019-x86_amd64 1.问题简述 项目编译release版本时会报错&#xff1a;报错…

计算机毕业设计 基于SpringBoot的城市垃圾分类管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

哪些是暴利的行为?合法的暴利行为?哪些行为为极善 相对于极恶?

合法的暴利行为 在讨论暴利的合法行为时&#xff0c;重要的是要区分“暴利”一词通常带有负面含义&#xff0c;指的是通过不正当手段或在不具备公平竞争条件下获得的过高利润。然而&#xff0c;有些行业或商业模式在法律范围内运作&#xff0c;可能因为专利保护、市场垄断、技…