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

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);}elseWorkLayout = 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);}elseWorkLayout = 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();}// 如果没有弹窗就移除遮罩elseRemoveMaskPanel();
}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);}// 寻找最顶层的 WorkLayoutUCanvasPanel* WorkLayout = NULL;// 判断最底层的布局控件是否是 Canvasif (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);}elseWorkLayout = 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;// 如果存在布局控件,试图把最后一个布局控件转换成 Overlayif (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);}elseWorkLayout = 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:// 销毁 UIUFUNCTION()void ExitUIPanel(FName PanelName);// 处理 UI 面板销毁后的父控件(供反射系统调用)UFUNCTION()void ExitCallBack(ELayoutType LayoutType, UPanelWidget* InLayout);protected:// 销毁 UIvoid 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();}elseRemoveMaskPanel();
}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 下,数值是 1static int32 UIFrameModuleIndex;// UIFrame 管理器的对象名,约定是 UIFramestatic 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);elseRemoveCallBack();
}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://xiahunao.cn/news/2776173.html

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

相关文章

K8S之运用亲和性设置Pod的调度约束

亲和性 Node节点亲和性硬亲和实践软亲和性实践 Pod节点亲和性和反亲和性pod亲和性硬亲和实践 pod反亲和性 Pod 的yaml文件里 spec 字段中包含一个 affinity 字段&#xff0c;使用一组亲和性调度规则&#xff0c;指定pod的调度约束。 kubectl explain pods.spec.affinity 配置…

MySQL优化器

优化器 MySQL存储引擎中存在了一个可插拔的优化器OPTIMIZER_TRACE&#xff0c;可以看到内部查询计划的TRACE信息&#xff0c;从而可以知道MySQL内部执行过程 查询优化器状态 show variables like optimizer_trace;Variable_name Valueoptimizer_trace enabledoff,one_lineoff…

[C++] opencv + qt 创建带滚动条的图像显示窗口代替imshow

在OpenCV中&#xff0c;imshow函数默认情况下是不支持滚动条的。如果想要显示滚动条&#xff0c;可以考虑使用其他库或方法来进行实现。 一种方法是使用Qt库&#xff0c;使用该库可以创建一个带有滚动条的窗口&#xff0c;并在其中显示图像。具体步骤如下&#xff1a; 1&…

【翻译】Processing安卓模式的安装使用及打包发布(内含中文版截图)

原文链接在下面的每一章的最前面。 原文有三篇&#xff0c;译者不知道贴哪篇了&#xff0c;这篇干脆标了原创。。 译者声明&#xff1a;本文原文来自于GNU协议支持下的项目&#xff0c;具备开源二改授权&#xff0c;可翻译后公开。 文章目录 Install&#xff08;安装&#xff0…

路由引入路由过滤排错

目录 排错网络拓扑图 排错需求 故障排错 故障一 故障二 故障三 排错网络拓扑图 排错需求 按照图示配置 IP 地址&#xff0c;总部和分支 A、分支 B 各自使用 loopback 口模拟业务网段公司业务流分为 A 流和 B 流&#xff0c;网段如图所示总部内部配置 OSPF 互通&#xff0…

Frostmourne (霜之哀伤)日志告警系统部署安装

简介 Frostmourne(霜之哀伤)是汽车之家经销商技术部监控系统的开源版本&#xff0c;用于帮助监控几乎所有数据库数据(包括Elasticsearch, Prometheus, SkyWalking, MySql 等等)。如果你已经建立起了日志系统&#xff0c; 指标体系&#xff0c;却苦恼于没有一个配套监控系统&am…

【芯片设计- RTL 数字逻辑设计入门 15 -- 函数实现数据大小端转换】

文章目录 函数实现数据大小端转换函数语法函数使用的规则Verilog and Testbench综合图VCS 仿真波形 函数实现数据大小端转换 在数字芯片设计中&#xff0c;经常把实现特定功能的模块编写成函数&#xff0c;在需要的时候再在主模块中调用&#xff0c;以提高代码的复用性和提高设…

「递归算法」:二叉树剪枝

一、题目 给你二叉树的根结点 root &#xff0c;此外树的每个结点的值要么是 0 &#xff0c;要么是 1 。 返回移除了所有不包含 1 的子树的原二叉树。 节点 node 的子树为 node 本身加上所有 node 的后代。 示例 1&#xff1a; 输入&#xff1a;root [1,null,0,0,1] 输出&…

编曲学习:旋律创作基础概念 和弦进行作曲 和弦外音使用 作曲技巧

旋律创作基础概念 和弦进行作曲 和弦外音使用 作曲技巧https://app8epdhy0u9502.pc.xiaoe-tech.com/live_pc/l_65be1ba7e4b064a83b92a3d7?course_id=course_2XLKtQnQx9GrQHac7OPmHD9tqbv文档https://app8epdhy0u9502.pc.xiaoe-tech.com/p/t_pc/course_pc_detail/camp_pro/cour…

Java实现用户画像活动推荐系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 兴趣标签模块2.3 活动档案模块2.4 活动报名模块2.5 活动留言模块 三、系统设计3.1 用例设计3.2 业务流程设计3.3 数据流程设计3.4 E-R图设计 四、系统展示五、核心代码5.1 查询兴趣标签5.2 查询活动推荐…

算法练习-二叉搜索树中的搜索(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;二叉树 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。且所在课程未提供测试平台&#xff0c;故实现代码主要为自行测试的那种&#xff0c;以下内容均为个人笔记&#xff0c;旨…

活字格V9 嵌入的html与活字格页面数据交互

不想看分析请直接跳到解决方案 项目场景&#xff1a; 活字格V9 嵌入的html与活字格页面的数据交互&#xff08;传值&#xff09;&#xff0c;嵌入的html用了WebSocket来控制硬件&#xff0c;获取的数据无法回传到活字格页面上&#xff0c;且嵌入的html无法使用活字格内置的js及…

第三百一十回

我们在上一章回中介绍了"再谈ListView中的分隔线"&#xff0c;本章回中将介绍showMenu的用法.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在第一百六十三回中介绍了showMenu相关的内容&#xff0c;它主要用来显示移动PopupMenu在页面中的位置…

Git、github与gitee码云

1.git核心是两个仓库&#xff1a;本地仓库和远程仓库 主要用于团队合作和代码版本控制&#xff08;个人现有版本代码出错可回溯上个提交版本的代码&#xff09; 远程仓库国际主流githut&#xff0c;但外网速度问题&#xff0c;国内可使用码云gitee github&#xff1a;https:…

【开源】JAVA+Vue.js实现在线课程教学系统

目录 一、摘要1.1 系统介绍1.2 项目录屏 二、研究内容2.1 课程类型管理模块2.2 课程管理模块2.3 课时管理模块2.4 课程交互模块2.5 系统基础模块 三、系统设计3.1 用例设计3.2 数据库设计 四、系统展示4.1 管理后台4.2 用户网页 五、样例代码5.1 新增课程类型5.2 网站登录5.3 课…

vue3+vite+ts 配置commit强制码提交规范配置 commitlint

配置 git 提交时的 commit 信息&#xff0c;统一提交 git 提交规范 安装命令: npm install -g commitizen npm i cz-customizable npm i commitlint/config-conventional commitlint/cli -D 文件配置 根路径创建文件 commitlint.config.js module.exports {// 继承的规…

Backtrader 文档学习- Plotting -Plotting on the same axis

Backtrader 文档学习- Plotting -Plotting on the same axis 1.概述 在同一轴上绘图&#xff0c;绘图是在同一空间上绘制原始数据和稍微(随机)修改的数据&#xff0c;但不是在同一轴上。 核心代码&#xff0c;data数据正负50点。 # The filter which changes the close pri…

java面试题:MySQL中的各种JOIN的区别

表关联是频率非常高的一种数据库操作&#xff0c;在MySQL中&#xff0c;这种JOIN操作有很多类型&#xff0c;包括内联接、左外连接、右外连接等等&#xff0c;而每种连接的含义都不一样&#xff0c;如果死记硬背&#xff0c;不仅很难记住&#xff0c;而且也容易搞混淆&#xff…

JAVA Web 学习(三)Web服务架构

五、软件架构模式——MVC MVC是一种 分层开发的模式 &#xff0c;其中&#xff1a;M-Model&#xff0c;业务模型&#xff0c;处理业务&#xff1b;V&#xff1a;View&#xff0c;视图&#xff0c;界面展示&#xff1b;C&#xff1a;Controller&#xff0c;控制器&#xff0c;处…

基于华为云欧拉操作系统(HCE OS)容器化部署传统应用(Redis+Postgresql+Git+SpringBoot+Nginx)

写在前面 博文内容为 华为云欧拉操作系统入门级开发者认证(HCCDA – Huawei Cloud EulerOS)实验笔记整理认证地址&#xff1a;https://edu.huaweicloud.com/certificationindex/developer/9bf91efb086a448ab4331a2f53a4d3a1博文内容涉及一个传统 Springboot 应用HCE部署&#x…