UE4运用C++和框架开发坦克大战教程笔记(十九)(第58~60集)完结

news2025/1/20 18:37:36

UE4运用C++和框架开发坦克大战教程笔记(十九)(第58~60集)完结

  • 58. 弹窗显示与隐藏
  • 59. UI 面板销毁
  • 60. 框架完成与总结

58. 弹窗显示与隐藏

这节课我们先来补全 TransferMask() 里对于 Overlay 布局类型面板的遮罩转移逻辑,大体上与 Canvas 布局类型的差不多。

接下来就是编写弹窗的隐藏和重新显示的逻辑。

在写重新显示弹窗的逻辑时我们发现 DoEnterUIPanel() 有一段代码可以复用,但是发现了一处逻辑上的错误,所以要调整一下代码。

DDFrameWidget.cpp

void UDDFrameWidget::DoEnterUIPanel(FName PanelName)
{
	// ... 省略
	
		// 此处作更改	
		if (!WorkLayout) {
			if (UnActiveCanvas.Num() == 0) {
				WorkLayout = WidgetTree->ConstructWidget<UCanvasPanel>(UCanvasPanel::StaticClass());
				WorkLayout->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
			}
			else
				WorkLayout = UnActiveCanvas.Pop();
				
			// 添加布局控件到界面最顶层
			UCanvasPanelSlot* FrameCanvasSlot = RootCanvas->AddChildToCanvas(WorkLayout);
			FrameCanvasSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));
			FrameCanvasSlot->SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));
			// 添加到激活画布组
			ActiveCanvas.Push(WorkLayout);	
		}

		// ... 省略

		// 此处作更改
		if (!WorkLayout) {
			if (UnActiveOverlay.Num() == 0) {
				WorkLayout = WidgetTree->ConstructWidget<UOverlay>(UOverlay::StaticClass());
				WorkLayout->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
			}
			else
				WorkLayout = UnActiveOverlay.Pop();

			// 添加布局控件到界面最顶层
			UCanvasPanelSlot* FrameCanvasSlot = RootCanvas->AddChildToCanvas(WorkLayout);
			FrameCanvasSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));
			FrameCanvasSlot->SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));
			// 添加到激活画布组
			ActiveOverlay.Push(WorkLayout);	
		}

	// ... 省略
}

void UDDFrameWidget::HidePanelReverse(UDDPanelWidget* PanelWidget)
{
	// 获取弹窗栈到数组
	TArray<UDDPanelWidget*> PopStack;
	PopPanelStack.GenerateValueArray(PopStack);

	// 如果不是最上层的弹窗,直接返回
	if (PopStack[PopStack.Num() - 1] != PanelWidget) {
		DDH::Debug() << PanelWidget->GetObjectName() << " Is Not Last Panel In PopPanelStack" << DDH::Endl();
		return;
	}

	// 从栈中移除
	PopPanelStack.Remove(PanelWidget->GetObjectName());
	// 执行隐藏函数
	PanelWidget->PanelHidden();

	// 调整弹窗栈
	PopStack.Pop();
	if (PopStack.Num() > 0) {
		UDDPanelWidget* PrePanelWidget = PopStack[PopStack.Num() - 1];
		// 转移遮罩到新的最顶层的弹窗的下一层
		TransferMask(PrePanelWidget);
		// 恢复被冻结的最顶层的弹窗
		PrePanelWidget->PanelResume();
	}
	// 如果没有弹窗就移除遮罩
	else
		RemoveMaskPanel();
}

void UDDFrameWidget::ShowPanelReverse(UDDPanelWidget* PanelWidget)
{
	// 如果弹窗栈里有元素,冻结最顶层的弹窗
	if (PopPanelStack.Num() > 0) {
		TArray<UDDPanelWidget*> PanelStack;
		PopPanelStack.GenerateValueArray(PanelStack);
		PanelStack[PanelStack.Num() - 1]->PanelFreeze();
	}

	// 弹窗对象必须从当前父控件移除,再重新添加到最顶层的界面(因为弹窗只是隐藏而不是销毁了)
	if (PanelWidget->UINature.LayoutType == ELayoutType::Canvas) {
		UCanvasPanel* PreWorkLayout = Cast<UCanvasPanel>(PanelWidget->GetParent());
		UCanvasPanelSlot* PrePanelSlot = Cast<UCanvasPanelSlot>(PanelWidget->Slot);
		FAnchors PreAnchors = PrePanelSlot->GetAnchors();
		FMargin PreOffsets = PrePanelSlot->GetOffsets();

		// 将 PanelWidget 从当前父控件移除
		PanelWidget->RemoveFromParent();
		// 处理父控件
		if (PreWorkLayout->GetChildrenCount() == 0) {
			PreWorkLayout->RemoveFromParent();
			ActiveCanvas.Remove(PreWorkLayout);
			UnActiveCanvas.Push(PreWorkLayout);
		}

		// 寻找最顶层的 WorkLayout
		UCanvasPanel* WorkLayout = NULL;

		// 判断最底层的布局控件是否是 Canvas
		if (RootCanvas->GetChildrenCount() > 0)
			WorkLayout = Cast<UCanvasPanel>(RootCanvas->GetChildAt(RootCanvas->GetChildrenCount() - 1));
			
		if (!WorkLayout) {
			// 如果没有任何对象
			if (UnActiveCanvas.Num() == 0) {
				WorkLayout = WidgetTree->ConstructWidget<UCanvasPanel>(UCanvasPanel::StaticClass());
				WorkLayout->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
			}
			else
				WorkLayout = UnActiveCanvas.Pop();

			// 添加布局控件到界面最顶层
			UCanvasPanelSlot* FrameCanvasSlot = RootCanvas->AddChildToCanvas(WorkLayout);
			FrameCanvasSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));
			FrameCanvasSlot->SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));
			// 添加到激活画布组
			ActiveCanvas.Push(WorkLayout);	
		}

		// 激活遮罩到最顶层弹窗
		ActiveMask(WorkLayout, PanelWidget->UINature.PanelLucencyType);

		// 把弹窗添加到获取的最顶层的父控件
		UCanvasPanelSlot* PanelSlot = WorkLayout->AddChildToCanvas(PanelWidget);
		PanelSlot->SetAnchors(PreAnchors);
		PanelSlot->SetOffsets(PreOffsets);
	}
	else {
		UOverlay* PreWorkLayout = Cast<UOverlay>(PanelWidget->GetParent());
		UOverlaySlot* PrePanelSlot = Cast<UOverlaySlot>(PanelWidget->Slot);
		FMargin PrePadding = PrePanelSlot->Padding;
		TEnumAsByte<EHorizontalAlignment> PreHAlign = PrePanelSlot->HorizontalAlignment;
		TEnumAsByte<EVerticalAlignment> PreVAlign = PrePanelSlot->VerticalAlignment;

		// 将 PanelWidget 从当前父控件移除
		PanelWidget->RemoveFromParent();
		// 处理父控件
		if (PreWorkLayout->GetChildrenCount() == 0) {
			PreWorkLayout->RemoveFromParent();
			ActiveOverlay.Remove(PreWorkLayout);
			UnActiveOverlay.Push(PreWorkLayout);
		}

		UOverlay* WorkLayout = NULL;
		
		// 如果存在布局控件,试图把最后一个布局控件转换成 Overlay
		if (RootCanvas->GetChildrenCount() > 0)
			WorkLayout = Cast<UOverlay>(RootCanvas->GetChildAt(RootCanvas->GetChildrenCount() - 1));

		if (!WorkLayout) {
			if (UnActiveOverlay.Num() == 0) {
				WorkLayout = WidgetTree->ConstructWidget<UOverlay>(UOverlay::StaticClass());
				WorkLayout->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
			}
			else
				WorkLayout = UnActiveOverlay.Pop();

			// 添加布局控件到界面最顶层
			UCanvasPanelSlot* FrameCanvasSlot = RootCanvas->AddChildToCanvas(WorkLayout);
			FrameCanvasSlot->SetAnchors(FAnchors(0.f, 0.f, 1.f, 1.f));
			FrameCanvasSlot->SetOffsets(FMargin(0.f, 0.f, 0.f, 0.f));
			// 添加到激活画布组
			ActiveOverlay.Push(WorkLayout);	
		}

		// 激活遮罩到最顶层弹窗
		ActiveMask(WorkLayout, PanelWidget->UINature.PanelLucencyType);

		// 添加弹窗到获取到的最顶层的布局控件
		UOverlaySlot* PanelSlot = WorkLayout->AddChildToOverlay(PanelWidget);
		PanelSlot->SetPadding(PrePadding);
		PanelSlot->SetHorizontalAlignment(PreHAlign);
		PanelSlot->SetVerticalAlignment(PreVAlign);
	}

	// 添加弹窗到栈
	PopPanelStack.Add(PanelWidget->GetObjectName(), PanelWidget);
	// 显示弹窗
	PanelWidget->PanelDisplay();
}

void UDDFrameWidget::TransferMask(UDDPanelWidget* PanelWidget)
{
	// ... 省略

	if (PanelWidget->UINature.LayoutType == ELayoutType::Canvas) {
		// ... 省略
	}
	else {
		UOverlay* WorkLayout = Cast<UOverlay>(PanelWidget->GetParent());

		int32 StartOrder = WorkLayout->GetChildIndex(PanelWidget);
		for (int i = StartOrder; i < WorkLayout->GetChildrenCount(); ++i) {
			UDDPanelWidget* TempPanelWidget = Cast<UDDPanelWidget>(WorkLayout->GetChildAt(i));
			if (!TempPanelWidget)
				continue;
			// 保存 UI 面板以及布局数据
			AbovePanelStack.Push(TempPanelWidget);
			FUINature TempUINature;
			UOverlaySlot* TempPanelSlot = Cast<UOverlaySlot>(TempPanelWidget->Slot);
			TempUINature.Offsets = TempPanelSlot->Padding;
			TempUINature.HAlign = TempPanelSlot->HorizontalAlignment;
			TempUINature.VAlign = TempPanelSlot->VerticalAlignment;
			AboveNatureStack.Push(TempUINature);
		}

		// 循环移除上层 UI 面板
		for (int i = 0; i < AbovePanelStack.Num(); ++i)
			AbovePanelStack[i]->RemoveFromParent();

		// 添加遮罩到新的父控件
		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 (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) {
			UOverlaySlot* PanelSlot = WorkLayout->AddChildToOverlay(AbovePanelStack[i]);
			PanelSlot->SetPadding(AboveNatureStack[i].Offsets);
			PanelSlot->SetHorizontalAlignment(AboveNatureStack[i].HAlign);
			PanelSlot->SetVerticalAlignment(AboveNatureStack[i].VAlign);
		}
	}
}

接下来我们测试一下弹窗隐藏和重新显示的逻辑是否正常运行。依旧是修改协程方法里的调用顺序。

RCGameUIFrame.cpp

// 修改协程方法如下
DDCoroTask* URCGameUIFrame::UIProcess()
{
	DDCORO_PARAM(URCGameUIFrame);

#include DDCORO_BEGIN()

	// 显示状态栏和小地图
	D->ShowUIPanel("StatePanel");

	D->ShowUIPanel("MiniMapPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);		// 缩短时间

	// 显示菜单栏
	D->ShowUIPanel("MenuPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	// 显示设置栏
	D->ShowUIPanel("OptionPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	// 隐藏菜单栏,这个操作会失败并输出 Debug 错误
	D->HideUIPanel("MenuPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	// 隐藏设置栏
	D->HideUIPanel("OptionPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	// 隐藏菜单栏,本次操作成功
	D->HideUIPanel("MenuPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);	

	// 显示菜单栏
	D->ShowUIPanel("MenuPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	// 显示设置栏
	D->ShowUIPanel("OptionPanel");

#include DDCORO_END()
}

运行游戏,可见界面上有状态栏和小地图,
3 秒后菜单弹窗伴随着遮罩出现,菜单弹窗可交互;
再 3 秒后设置弹窗出现,遮罩移到它的底下,菜单弹窗不可交互,设置弹窗可交互;
再 3 秒后试图隐藏菜单弹窗,但是它不是最靠前的弹窗,所以左上角输出 Debug 语句提示失败。
再 3 秒后设置弹窗隐藏,遮罩移到菜单弹窗的底下,并且菜单弹窗又恢复可交互;
再 3 秒菜单弹窗和遮罩隐藏;
再 3 秒,菜单弹窗和遮罩重新出现,菜单弹窗可交互;
最后再 3 秒,设置弹窗出现,遮罩移到它的底下,菜单弹窗不可交互,设置弹窗可交互;

在这里插入图片描述

说明我们写的遮罩管理器以及弹窗的隐藏和重新显示都没有问题。

59. UI 面板销毁

UI 框架已经实现了大半,我们接下来继续实现 UI 的销毁功能。对于销毁逻辑也需要根据面板类型来使用相应的方法。

DDFrameWidget.h

public:

	// 销毁 UI
	UFUNCTION()
	void ExitUIPanel(FName PanelName);

	// 处理 UI 面板销毁后的父控件(供反射系统调用)
	UFUNCTION()
	void ExitCallBack(ELayoutType LayoutType, UPanelWidget* InLayout);

protected:

	// 销毁 UI
	void ExitPanelDoNothing(UDDPanelWidget* PanelWidget);
	void ExitPanelHideOther(UDDPanelWidget* PanelWidget);
	void ExitPanelReverse(UDDPanelWidget* PanelWidget);

DDFrameWidget.cpp

void UDDFrameWidget::ExitUIPanel(FName PanelName)
{
	// 如果正在预加载但是没有加载完成(这种情况出现在执行第一次显示或提前加载后就马上执行销毁界面)
	if (!AllPanelGroup.Contains(PanelName) && LoadedPanelName.Contains(PanelName)) {
		DDH::Debug() << "Do Not Exit UI Panel when Loading Panel" << DDH::Endl();
		return;
	}
	// 如果这个 UI 面板没有加载到全部组
	if (!AllPanelGroup.Contains(PanelName))
		return;

	// 获取 UI 面板
	UDDPanelWidget* PanelWidget = *AllPanelGroup.Find(PanelName);
	// 是否在显示组或者弹窗栈内
	if (!ShowPanelGroup.Contains(PanelName) && !PopPanelStack.Contains(PanelName)) {
		AllPanelGroup.Remove(PanelName);
		LoadedPanelName.Remove(PanelName);
		// 运行 PanelExit 生命周期,具体的内存释放代码在该周期函数里面
		PanelWidget->PanelExit();
		// 直接返回
		return;
	}

	// 处理隐藏 UI 面板相关的流程
	switch (PanelWidget->UINature.PanelShowType) {
	case EPanelShowType::DoNothing:
		ExitPanelDoNothing(PanelWidget);
		break;
	case EPanelShowType::HideOther:
		ExitPanelHideOther(PanelWidget);
		break;
	case EPanelShowType::Reverse:
		ExitPanelReverse(PanelWidget);
		break;
	}
}

void UDDFrameWidget::ExitPanelDoNothing(UDDPanelWidget* PanelWidget)
{
	// 从显示组,全部组,加载名字组移除
	ShowPanelGroup.Remove(PanelWidget->GetObjectName());
	AllPanelGroup.Remove(PanelWidget->GetObjectName());
	LoadedPanelName.Remove(PanelWidget->GetObjectName());
	
	// 运行销毁生命周期
	PanelWidget->PanelExit();
}

void UDDFrameWidget::ExitPanelHideOther(UDDPanelWidget* PanelWidget)
{
	// 从显示组,全部组,加载名字组移除
	ShowPanelGroup.Remove(PanelWidget->GetObjectName());
	AllPanelGroup.Remove(PanelWidget->GetObjectName());
	LoadedPanelName.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->PanelExit();
}

void UDDFrameWidget::ExitPanelReverse(UDDPanelWidget* PanelWidget)
{
	// 获取弹窗栈到数组
	TArray<UDDPanelWidget*> PopStack;
	PopPanelStack.GenerateValueArray(PopStack);

	// 如果不是最上层的弹窗,直接返回
	if (PopStack[PopStack.Num() - 1] != PanelWidget) {
		DDH::Debug() << PanelWidget->GetObjectName() << " Is Not Last Panel In PopPanelStack" << DDH::Endl();
		return;
	}

	// 从栈,全部组,加载名字组中移除
	PopPanelStack.Remove(PanelWidget->GetObjectName());
	AllPanelGroup.Remove(PanelWidget->GetObjectName());
	LoadedPanelName.Remove(PanelWidget->GetObjectName());
	// 运行销毁生命周期函数
	PanelWidget->PanelExit();

	// 调整弹窗栈
	PopStack.Pop();
	if (PopStack.Num() > 0) {
		UDDPanelWidget* PrePanelWidget = PopStack[PopStack.Num() - 1];
		// 转移遮罩到新的最顶层的弹窗的下一层
		TransferMask(PrePanelWidget);
		// 恢复被冻结的最顶层的弹窗
		PrePanelWidget->PanelResume();
	}
	else
		RemoveMaskPanel();
}

void UDDFrameWidget::ExitCallBack(ELayoutType LayoutType, UPanelWidget* InLayout)
{
	if (LayoutType == ELayoutType::Canvas) {
		UCanvasPanel* WorkLayout = Cast<UCanvasPanel>(InLayout);
		if (WorkLayout->GetChildrenCount() == 0) {
			WorkLayout->RemoveFromParent();
			ActiveCanvas.Remove(WorkLayout);
			UnActiveCanvas.Push(WorkLayout);
		}
	}
	else {
		UOverlay* WorkLayout = Cast<UOverlay>(InLayout);
		if (WorkLayout->GetChildrenCount() == 0) {
			WorkLayout->RemoveFromParent();
			ActiveOverlay.Remove(WorkLayout);
			UnActiveOverlay.Push(WorkLayout);
		}
	}
}

来到 DDPanelWidget,编写销毁 UI 的具体逻辑。

因为需要考虑面板销毁后父控件是否还有子面板(如果没有就没必要存在了),所以我们利用反射系统声明一个方法来调用 DDFrameWidget 里的方法来移除没有内容的父控件。

DDPanelWidget.h

protected:

	// 销毁动画回调函数
	void RemoveCallBack();

protected:

	// UIFrame 管理器所在的模组 ID,约定在 HUD 下,数值是 1
	static int32 UIFrameModuleIndex;
	// UIFrame 管理器的对象名,约定是 UIFrame
	static FName UIFrameName;
	// 销毁回调函数名字
	static FName ExitCallBackName;

protected:

	DDOBJFUNC_TWO(ExitCallBack, ELayoutType, LayoutType, UPanelWidget*, WorkLayout);

DDPanelWidget.cpp

int32 UDDPanelWidget::UIFrameModuleIndex(1);

FName UDDPanelWidget::UIFrameName(TEXT("UIFrame"));

FName UDDPanelWidget::ExitCallBackName(TEXT("ExitCallBack"));

void UDDPanelWidget::PanelExit()
{
	// 如果 UI 面板正在显示
	if (GetVisibility() != ESlateVisibility::Hidden)
		InvokeDelay(PanelHiddenName, DisplayLeaveMovie(), this, &UDDPanelWidget::RemoveCallBack);
	else
		RemoveCallBack();
}

void UDDPanelWidget::RemoveCallBack()
{
	// 获取父控件
	UPanelWidget* WorkLayout = GetParent();
	// 这个判断条件会在下一集补充
	// 已经加载了 UI 面板,但是一直没有运行显示命令的情况下,WorkLayout 为空
	if (WorkLayout) {
		RemoveFromParent();
		// 告诉 UI 管理器处理父控件
		ExitCallBack(UIFrameModuleIndex, UIFrameName, ExitCallBackName, UINature.LayoutType, WorkLayout);
	}
	// 执行销毁
	DDDestroy();
}

接下来测试下销毁 UI 的逻辑是否正常运行,依旧是调整协程方法里的调用逻辑。

RCGameUIFrame.cpp

// 修改协程方法如下
DDCoroTask* URCGameUIFrame::UIProcess()
{
	DDCORO_PARAM(URCGameUIFrame);

#include DDCORO_BEGIN()

	D->ShowUIPanel("StatePanel");

	D->ShowUIPanel("MiniMapPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	D->ShowUIPanel("BigMapPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	D->ShowUIPanel("MenuPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	D->ShowUIPanel("OptionPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	DDH::Debug() << "ExitUIPanel MiniMapPanel" << DDH::Endl();

	D->ExitUIPanel("MiniMapPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	DDH::Debug() << "ExitUIPanel OptionPanel" << DDH::Endl();

	D->ExitUIPanel("OptionPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	DDH::Debug() << "ExitUIPanel BigMapPanel" << DDH::Endl();

	D->ExitUIPanel("BigMapPanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	DDH::Debug() << "ExitUIPanel StatePanel" << DDH::Endl();

	D->ExitUIPanel("StatePanel");

#include DDYIELD_READY()

	DDYIELD_RETURN_SECOND(3.f);

	DDH::Debug() << "ExitUIPanel MenuPanel" << DDH::Endl();

	D->ExitUIPanel("MenuPanel");

#include DDCORO_END()
}

运行游戏,首先可看到状态栏和小地图;接着是大地图出现(状态栏和小地图收起);菜单弹窗出现;设置弹窗出现;
随后销毁小地图、销毁设置弹窗、销毁大地图(状态栏出现)、销毁状态栏,最后销毁菜单弹窗。
说明销毁 UI 的功能也写好了。

在这里插入图片描述

60. 框架完成与总结

课程已经接近尾声,本集开头会修改一些疏漏的地方,笔者已经在先前的课程里标注了。

我们前面写了很多 UI 框架的方法,但是看起来都是管理类在操控面板,我们打算让面板自己也能执行这些操控方法,只要通过反射系统让管理类执行就可以实现了。

DDPanelWidget.h

protected:

	void ShowSelfPanel();

	void HideSelfPanel();

	void ExitSelfPanel();

	void AdvanceLoadPanel(FName PanelName);

	void ShowUIPanel(FName PanelName);

	void HideUIPanel(FName PanelName);

	void ExitUIPanel(FName PanelName);

protected:

	// 显示 UI 方法名
	static FName ShowUIPanelName;
	// 隐藏 UI 方法名
	static FName HideUIPanelName;
	// 销毁 UI 方法名
	static FName ExitUIPanelName;
	// 预加载方法名
	static FName AdvanceLoadPanelName;

protected:

	DDOBJFUNC_ONE(OperatorUIPanel, FName, PanelName);

DDPanelWidge.cpp

FName UDDPanelWidget::ShowUIPanelName(TEXT("ShowUIPanel"));

FName UDDPanelWidget::HideUIPanelName(TEXT("HideUIPanel"));

FName UDDPanelWidget::ExitUIPanelName(TEXT("ExitUIPanel"));

FName UDDPanelWidget::AdvanceLoadPanelName(TEXT("AdvanceLoadPanel"));

void UDDPanelWidget::ShowSelfPanel()
{
	ShowUIPanel(GetObjectName());
}

void UDDPanelWidget::HideSelfPanel()
{
	HideUIPanel(GetObjectName());
}

void UDDPanelWidget::ExitSelfPanel()
{
	ExitUIPanel(GetObjectName());
}

void UDDPanelWidget::AdvanceLoadPanel(FName PanelName)
{
	OperatorUIPanel(UIFrameModuleIndex, UIFrameName, AdvanceLoadPanelName, PanelName);
}

void UDDPanelWidget::ShowUIPanel(FName PanelName)
{
	OperatorUIPanel(UIFrameModuleIndex, UIFrameName, ShowUIPanelName, PanelName);
}

void UDDPanelWidget::HideUIPanel(FName PanelName)
{
	OperatorUIPanel(UIFrameModuleIndex, UIFrameName, HideUIPanelName, PanelName);
}

void UDDPanelWidget::ExitUIPanel(FName PanelName)
{
	OperatorUIPanel(UIFrameModuleIndex, UIFrameName, ExitUIPanelName, PanelName);
}

最后再改一些地方的错误。

DDTypes.h

// 执行 Execute 方法之前必须手动调用 IsBound() 方法判定是否有绑定函数
template<typename RetType, typename... VarTypes>
RetType DDCallHandle<RetType, VarTypes...>::Execute(VarTypes... Params)
{
	// 删除原来的 是否绑定 判断
	return MsgQuene->Execute<RetType, VarTypes...>(CallName, Params...);
}

DDDriver.cpp

#if WITH_EDITOR
void ADDDriver::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
	Super::PostEditChangeProperty(PropertyChangedEvent);

	if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(ADDDriver, ModuleType)) {
		Center->IterChangeModuleType(Center, ModuleType);
	}
}
#endif

// 下面这两个方法要放在预编译的 #if WITH_EDITOR 之外,笔者检查的时候已经是放在外面的了,应该是在前面的课程也有提到这个修改
void ADDDriver::ExecuteFunction(DDModuleAgreement Agreement, DDParam* Param)
{
	Center->AllotExecuteFunction(Agreement, Param);
}

void ADDDriver::ExecuteFunction(DDObjectAgreement Agreement, DDParam* Param)
{
	Center->AllotExecuteFunction(Agreement, Param);
}

最后我们来看一下梁迪老师的 DataDriven 框架实现了如下功能:

  1. 模块化树状结构(适合分模块多人合作开发)
  2. 更多生命周期函数
  3. 反射事件系统(零耦合, 调用方便)
  4. 注册事件系统(零耦合, 运行效率高, 适合大批量调用时使用)
  5. 协程系统(全面实现 Unity 协程的功能)
  6. 延时事件系统
  7. 多按键绑定系统
  8. 资源同异步加载系统
  9. UI 框架

至于坦克大战,应该是没有的了。不过能系统地学习梁迪老师的这个框架,相信读者也能收获到很多。笔者在此衷心感谢梁迪老师的优质课程 : )

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

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

相关文章

[Angular 基础] - 指令(directives)

[Angular 基础] - 指令(directives) 这里假设已经知道如何创建 Angular 组件以及数据绑定&#xff0c;不然可以参考前两篇笔记&#xff1a; [Angular 基础] - Angular 渲染过程 & 组件的创建 [Angular 基础] - 数据绑定(databinding) 就像中文翻译一样&#xff0c;dire…

Vue-Vue3 集成编辑器功能

1、安装依赖 编辑器插件需要安装 wangeditor/editor 和 wangeditor/editor-for-vue 两个插件 npm install wangeditor/editor --savevue3运行如下命令安装 npm install wangeditor/editor-for-vuenext --savevue2运行如下命令安装 npm install wangeditor/editor-for-vue -…

清平乐-春风丽日

今天&#xff0c;是2024年农历除夕日&#xff0c;远方家人已于昨夜风尘扑扑地倦鸟归巢&#xff0c;团聚过龙年&#xff0c;今晨酣睡未起。老龄笔者心情极佳&#xff0c;一夜好梦醒来&#xff0c;推窗仰头展望苍穹&#xff0c;喜上心头&#xff1a;啊&#xff01;接连几天的小雨…

containerd中文翻译系列(十八)containerd支持NRI

节点资源接口 NRI 是节点资源接口&#xff08;Node Resource Interface&#xff09;&#xff0c;它是一个通用框架&#xff0c;用于将扩展功能插入兼容 OCI 的容器运行时。它提供了插件跟踪容器状态并对其配置进行有限的更改改的基本机制。 NRI 本身与任何容器运行时的内部实…

MySQL篇----第十五篇

系列文章目录 文章目录 系列文章目录前言一、实践中如何优化 MySQL二、优化数据库的方法三、简单描述 MySQL 中,索引,主键,唯一索引,联合索引的区别,对数据库的性能有什么影响(从读写两方面)前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分…

Cilium CNI深度指南

Cilium是基于eBPF的功能强大的CNI插件&#xff0c;为云原生环境提供了强大的网络和安全支持。原文: Cilium CNI: A Comprehensive Deep Dive Guide for Networking and Security Enthusiasts! &#x1f313;简介 欢迎阅读为网络和安全爱好者提供的全面深入的指南&#xff01; 本…

Django(十)

1. Ajax请求 浏览器向网站发送请求时&#xff1a;URL 和 表单的形式提交。 GETPOST 特点&#xff1a;页面刷新。 除此之外&#xff0c;也可以基于Ajax向后台发送请求&#xff08;偷偷的发送请求&#xff09;。 依赖jQuery编写ajax代码 $.ajax({url:"发送的地址"…

《剑指 Offer》专项突破版 - 面试题 37 : 小行星碰撞(C++ 实现)

题目链接&#xff1a;LCR 037. 行星碰撞 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 输入一个表示小行星的数组&#xff0c;数组中每个数字的绝对值表示小行星的大小&#xff0c;数字的正负号表示小行星运动的方向&#xff0c;正号表示向右飞行&#xff0c;负…

探索设计模式的魅力:代理模式揭秘-软件世界的“幕后黑手”

设计模式专栏&#xff1a;http://t.csdnimg.cn/U54zu 目录 引言 一、魔法世界 1.1 定义与核心思想 1.2 静态代理 1.3 动态代理 1.4 虚拟代理 1.5 代理模式结构图 1.6 实例展示如何工作&#xff08;场景案例&#xff09; 不使用模式实现 有何问题 使用模式重构示例 二、…

HttpClient | 支持 HTTP 协议的客户端编程工具包

目录 1、简介 2、应用场景 3、导入 4、API 5、示例 5.1、GET请求 5.2、POST请求 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅长web应用开发、数据结构和算法&#xff0c;初…

Spring Boot 笔记 004 自动配置和自定义starter

003讲到了导入jar包中的方法&#xff0c;但其实是个半成品&#xff0c;别人写的jar包中的方法我要在自己的代码中去调用&#xff0c;非常的不方便。原则上写给别人用的jar包&#xff0c;人家要能直接用&#xff0c;而不用写注入的方法。 在springboot中会自动扫描imports文件中…

ThinkPad X201 经典小黑 折腾玩

前段时间&#xff0c;在折腾ThinkPad T430时&#xff0c;偶然看到了ThinkPad X200&#xff0c;一个12.1英寸的高端便携小本。 想当年&#xff0c;但那是总裁级别才能用的&#xff0c;应该是接近2万元&#xff0c;我们是一直用DELL的。 没想到的是&#xff0c;在海鲜市场上&am…

[office] excel如何计算毛重和皮重的时间间隔 excel计算毛重和皮重时间间隔方法 #笔记#学习方法

excel如何计算毛重和皮重的时间间隔 excel计算毛重和皮重时间间隔方法 在日常工作中经常会到用excel&#xff0c;有时需要计算毛重和皮重的时间间隔&#xff0c;具体的计算方式是什么&#xff0c;一起来了解一下吧 在日常工作中经常会到用excel&#xff0c;在整理编辑过磅数据…

贵金属交易包括哪些?香港有哪些贵金属交易平台?

随着金融市场的不断发展&#xff0c;贵金属交易作为一种投资方式&#xff0c;越来越受到投资者的关注。贵金属交易不仅具有投资价值&#xff0c;还能够为投资者提供规避风险和保值的工具。本文将介绍贵金属交易的种类和香港的贵金属交易平台。 一、贵金属交易的种类 贵金属交…

【开源】JAVA+Vue.js实现高校实验室管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实验管理模块2.4 实验设备模块2.5 实验订单模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示五、样例代码5.1 查询实验室设备5.2 实验放号5.3 实验预定 六、免责说明 一、摘…

个人IP塑造与短视频带货,人人都是吸金的网红博主

一、教程描述 网红带货&#xff0c;就是网络红人通过推荐和分享&#xff0c;间接销售产品的一种方式。网红带货并不是直接带货&#xff0c;而是需要打造自己&#xff0c;用时下热门的话讲叫塑造IP&#xff0c;一般通过旅行、工作、日常服装搭配等这些行为&#xff0c;输出自己…

2024-02-08 Unity 编辑器开发之编辑器拓展1 —— 自定义菜单栏与窗口

文章目录 1 特殊文件夹 Editor2 在 Unity 菜单栏中添加自定义页签3 在 Hierarchy 窗口中添加自定义页签4 在 Project 窗口中添加自定义页签5 在菜单栏的 Component 菜单添加脚本6 在 Inspector 为脚本右键添加菜单7 加入快捷键8 小结 1 特殊文件夹 Editor ​ Editor 文件夹是 …

数据在内存中的存储:深入了解与理解技巧

​ ✨✨ 欢迎大家来到贝蒂大讲堂✨✨ ​ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; ​ 所属专栏&#xff1a;C语言学习 ​ 贝蒂的主页&#xff1a;Betty‘s blog 引言 ​ 我们早就学完基本的数据类型&#xff0c;那这些数据类型…

PCIe学习笔记(1)Hot-Plug机制

文章目录 Hot-Plug InitHot Add FlowSurprise Remove FlowNPEM Flow Hot-Plug Init PCIe hot-plug是一种支持在不关机情况下从支持的插槽添加或删除设备的功能&#xff0c;PCIe架构定义了一些寄存器以支持原生热插拔。相关寄存器主要分布在Device Capabilities, Slot Capabili…

【Linux系统学习】2.Linux基础命令

Linux基础命令 Linux的目录结构 Linux命令入门 目录切换相关命令(cd/pwd) 相对路径、绝对路径和特殊路径符 创建目录命令(mkdir) 文件操作命令part1(touch、cat、more&#xff09; 文件操作命令part2(cp、mv、rm&#xff09; 查找命令(which、find&#xff09; grep、wc和管道符…