UE4运用C++和框架开发坦克大战教程笔记(十九)(第58~60集)完结
- [58. 弹窗显示与隐藏](#58. 弹窗显示与隐藏)
- [59. UI 面板销毁](#59. UI 面板销毁)
- [60. 框架完成与总结](#60. 框架完成与总结)
58. 弹窗显示与隐藏
这节课我们先来补全 TransferMask()
里对于 Overlay 布局类型面板的遮罩转移逻辑,大体上与 Canvas 布局类型的差不多。
接下来就是编写弹窗的隐藏和重新显示的逻辑。
在写重新显示弹窗的逻辑时我们发现 DoEnterUIPanel()
有一段代码可以复用,但是发现了一处逻辑上的错误,所以要调整一下代码。
DDFrameWidget.cpp
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
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
cpp
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
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
cpp
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
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
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
cpp
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
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
cpp
// 执行 Execute 方法之前必须手动调用 IsBound() 方法判定是否有绑定函数
template<typename RetType, typename... VarTypes>
RetType DDCallHandle<RetType, VarTypes...>::Execute(VarTypes... Params)
{
// 删除原来的 是否绑定 判断
return MsgQuene->Execute<RetType, VarTypes...>(CallName, Params...);
}
DDDriver.cpp
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 框架实现了如下功能:
- 模块化树状结构(适合分模块多人合作开发)
- 更多生命周期函数
- 反射事件系统(零耦合, 调用方便)
- 注册事件系统(零耦合, 运行效率高, 适合大批量调用时使用)
- 协程系统(全面实现 Unity 协程的功能)
- 延时事件系统
- 多按键绑定系统
- 资源同异步加载系统
- UI 框架
至于坦克大战,应该是没有的了。不过能系统地学习梁迪老师的这个框架,相信读者也能收获到很多。笔者在此衷心感谢梁迪老师的优质课程 : )