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。随后将界面修改如下:

UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)_第1张图片
来到 HUDData,给 Class Wealth Data 添加一个元素如下:

UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)_第2张图片
运行游戏,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 动画如下:(两个箭头指的是关键帧)

UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)_第3张图片
然后在其蓝图节点编辑界面重写 DisplayEnterMovie()DisplayLeaveMovie() 节点:

UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)_第4张图片
运行游戏,此时左上角状态栏会有移入动画,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)

UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)_第5张图片
将 OptionPanel 更改界面如下:

UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)_第6张图片
来到 HUDData,给 Class Wealth Data 添加两个元素如下:

UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)_第7张图片
运行游戏,十秒后可以看到 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 的图片换成比较深色的图片,方便观察。

UE4运用C++和框架开发坦克大战教程笔记(十八)(第55~57集)_第8张图片
运行游戏,可见界面上有状态栏和小地图,
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 {
	
	}
}

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

你可能感兴趣的:(UE4/5,的学习笔记,ue4,c++,笔记)