当前位置: 首页 > news >正文

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 框架

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

相关文章:

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

UE4运用C和框架开发坦克大战教程笔记&#xff08;十九&#xff09;&#xff08;第58~60集&#xff09;完结 58. 弹窗显示与隐藏59. UI 面板销毁60. 框架完成与总结 58. 弹窗显示与隐藏 这节课我们先来补全 TransferMask() 里对于 Overlay 布局类型面板的遮罩转移逻辑&#xff…...

ModuleNotFoundError: No module named ‘_ctypes‘报错解决方案

1、须命令安装libbffi-devel软件包&#xff1a; yum install libffi-devel -y2、安装完后再重装python3&#xff0c;无须卸载 找到之前的python3安装包&#xff0c;如果安装包删除了通过 history | grep python命令找到最初安装时的包下载的命令下载&#xff0c;保证版本一样&…...

【服务器数据恢复】服务器RAID模块硬件损坏的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某品牌服务器中有一组由数块SAS硬盘组建的RAID5磁盘阵列&#xff0c;服务器操作系统是WINDOWS SERVER&#xff0c;服务器中存放企业数据&#xff0c;无数据库文件。 服务器出故障之前出现过几次意外断电的情况&#xff0c;服务器断电…...

spring boot3x登录开发-上(整合jwt)

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途。 目录 前置条件 jwt简介 导依赖 编写jwt工具类 1.配置项直接嵌入代码&#xff0c;通过类名.静态方法使用 2.配置项写到…...

git 克隆拉取代码出现私钥权限问题。

问题反馈&#xff1a; rootdd:~/android/boost-1.74-for-android-r20b# git clone https://github.com/liulilittle/boost-1.74-for-android-r20b.git Cloning into boost-1.74-for-android-r20b... WARNING: UNPROTECTED PRIVATE KEY FILE! Permissions 0777 for /root/…...

【5G NR】【一文读懂系列】移动通讯中使用的信道编解码技术-卷积码原理

目录 一、引言 二、卷积编码的发展历史 2.1 卷积码的起源 2.2 主要发展阶段 2.3 重要里程碑 三、卷积编码的基本概念 3.1 基本定义 3.2 编码器框图 3.3 编码多项式 3.4 网格图(Trellis)描述 四、MATLAB示例 一、引言 卷积编码&#xff0c;作为数字通信领域中的一项…...

揭开Markdown的秘籍:标题|文字样式|列表

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;Markdown指南、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️Markdown 标题二. ⛳️Markdown 文字样式2.1 &#x1f514;斜体2.2 &…...

移动最小二乘法

移动最小二乘法&#xff08;Moving Least Square&#xff0c;MLS&#xff09;主要应用于曲线与曲面拟合&#xff0c;该方法基于紧支撑加权函数&#xff08;即函数值只在有限大小的封闭域中定义大于零&#xff0c;而在域外则定义为零&#xff09;和多项式基函数&#xff0c;通过…...

【LeetCode】37. 解数独(困难)——代码随想录算法训练营Day30

题目链接&#xff1a;37. 解数独 题目描述 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&…...

VUE学习——属性绑定

属性绑定&#xff0c;就是给html添加id、class这样类似的操作。 <template><div v-bind:id"dynamicId"><div v-bind:class"dynamicClass">Test</div></div> </template><script>export default{data(){return{…...

vue3 之 通用组件统一注册全局

components/index.js // 把components中的所组件都进行全局化注册 // 通过插件的方式 import ImageView from ./ImageView/index.vue import Sku from ./XtxSku/index.vue export const componentPlugin {install (app) {// app.component(组件名字&#xff0c;组件配置对象)…...

[Java][算法 双指针]Day 02---LeetCode 热题 100---04~07

LeetCode 热题 100---04~07 第一题&#xff1a;移动零 思路 找到每一个为0的元素 然后移到数组的最后 但是需要注意的是 要在给定的数组原地进行修改 并且其他非零元素的相对顺序不能改变 我们采用双指针法 定义两个指针i和j i和j一开始分别都在0索引位置 然后判断j所…...

【问题解决】如何将一个服务器的docker迁移到另一个服务器

要将Docker容器从一台机器迁移到另一台机器&#xff0c;可以按照以下步骤操作&#xff1a; 在机器A上提交容器为镜像&#xff1a; 使用docker commit命令将运行中的容器保存为新的镜像。这里需要容器的ID或名称&#xff0c;以及你想要命名的目标镜像名。 docker commit [容器…...

C++单例模式详解

目录 0. 前言 1. 懒汉式单例模式 1.1 最简单的单例模式 1.2 防止内存泄漏 1.2.1 智能指针的方法 1.2.2 静态嵌套的方法 1.3 保证线程安全 1.4 C11版本的优雅解决方案 2. 饿汉式单例模式 0. 前言 起因是在程序中重复声明了一个单例模式的变量&#xff0c;后来程序怎么调…...

LLM应用开发与落地:流式响应

一、背景 最近智能客服产品给到一个游戏客户那边&#xff0c;客户那边的客服负责人体验后认为我们产品回答的准确率是还是比较高的。同时&#xff0c;他反馈了几个需要改进的地方&#xff0c;其中一个就是机器人回复慢。机器人回复慢有很多原因&#xff0c;也有优化方式&#…...

神经网络 | 基于 CNN 模型实现土壤湿度预测

Hi&#xff0c;大家好&#xff0c;我是半亩花海。在现代农业和环境监测中&#xff0c;了解土壤湿度的变化对于作物生长和水资源管理至关重要。通过深度学习技术&#xff0c;特别是卷积神经网络&#xff0c;我们可以利用过去的土壤湿度数据来预测未来的湿度趋势。本文将使用 Pad…...

江科大STM32 终

目录 SPI协议10.1 SPI简介W25Q64简介10.3 SPI软件读写W25Q6410.4 SPI硬件外设读写W25Q64 BKP备份寄存器、PER电源控制器、RTC实时时钟11.0 Unix时间戳代码示例&#xff1a;读写备份寄存器BKP11.2 RTC实时时钟 十二、PWR电源控制12.1 PWR简介代码示例&#xff1a;修改主频12.3 串…...

《MySQL 简易速速上手小册》第10章:未来趋势和进阶资源(2024 最新版)

文章目录 10.1 MySQL 在云计算和容器化中的应用10.1.1 基础知识10.1.2 重点案例&#xff1a;使用 Python 部署 MySQL 到 Kubernetes10.1.3 拓展案例 1&#xff1a;在 AWS RDS 上部署 MySQL 实例10.1.4 拓展案例 2&#xff1a;使用 Docker 部署 MySQL 10.2 MySQL 和 NoSQL 的整合…...

Stable Diffusion 模型下载:GhostMix(幽灵混合)

文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 GhostMix 是绝对让你惊艳的模型&#xff0c;也是自己认为现在最强的2.5D模型。我认为模型的更新应该是基于现有的画面整体不大变的前提下&#xff0c;提高模型的成…...

django解决Table ‘xx‘ already exists的方法

1&#xff0c;首先看已存在的这个库表结构是什么样的&#xff0c;先让对应的model.py恢复到和他一样的字段 2&#xff0c;删除对应app下的migrations目录里面除__init__.py文件的其他所有文件 3&#xff0c;回到manage.py所在目录执行python manage.py makemigrations 4&#x…...

qt学习:arm摄像头+c调用v412框架驱动+qt调用v412框架驱动 显示摄像头画面

目录 跟内核进行数据通信的函数 编程步骤 c代码 头文件 打开摄像头文件 /dev/videox 获取当前主机上&#xff08;开发板&#xff09;摄像头列表信息 设置当前摄像头的画面格式 比如说 设置 采集图像的宽度为640 高度 480 在内核空间中&#xff0c;申请一个缓冲区队列…...

Linux 36.2@Jetson Orin Nano基础环境构建

Linux 36.2Jetson Orin Nano基础环境构建 1. 源由2. 步骤2.1 安装NVIDIA Jetson Linux 36.2系统2.2 必备软件安装2.3 基本远程环境2.3.1 远程ssh登录2.3.2 samba局域网2.3.3 VNC远程登录 2.4 开发环境安装 3. 总结 1. 源由 现在流行什么&#xff0c;也跟风来么一个一篇。当然&…...

牛客网SQL264:查询每个日期新用户的次日留存率

官网链接&#xff1a; 牛客每个人最近的登录日期(五)_牛客题霸_牛客网牛客每天有很多人登录&#xff0c;请你统计一下牛客每个日期新用户的次日留存率。 有一个登录(login。题目来自【牛客题霸】https://www.nowcoder.com/practice/ea0c56cd700344b590182aad03cc61b8?tpId82 …...

echarts 曲线图自定义提示框

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>曲线图</title><!-- 引入 ECharts 库 -->…...

幻兽帕鲁服务器怎么搭建?Palworld多人联机教程

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…...

DAY39: 动态规划不同路径问题62

Leetcode: 62 不同路径 机器人从(0 , 0) 位置出发&#xff0c;到(m - 1, n - 1)终点。 基本思路 1、确定dp数组&#xff08;dp table&#xff09;以及下标的含义 dp[i][j] &#xff1a;表示从&#xff08;0 &#xff0c;0&#xff09;出发&#xff0c;到(i, j) 有dp[i][j]条…...

idea开发工具的简单使用与常见问题

1、配置git 选择左上角目录file->setting 打开&#xff0c;Version Control 目录下Git&#xff0c;选择git安装目录下的git.exe文件&#xff1b; 点击test&#xff0c;出现git版本&#xff0c;则表示git识别成功&#xff0c;点击右下角确认即可生效。 2、配置node.js 选…...

使用 WMI 查询安全软件信息

在这篇文章中&#xff0c;我们将详细介绍如何使用 Windows Management Instrumentation (WMI) API 来查询当前计算机上安装的安全软件的基本信息。我们将分析代码的各个部分&#xff0c;并解释每个步骤所涉及的技术和原理。 一、什么是 WMI&#xff1f; WMI 是 Windows Manag…...

创建TextMeshPro字体文件

相比于Unity的Text组件&#xff0c;TextMesh Pro提供了更强大的文本格式和布局控制&#xff0c;更高级的文本渲染技术&#xff0c;更灵活的文本样式和纹理支持&#xff0c;更好的性能以及更易于使用的优点。但unity自带TextMeshPro字体不支持中文。这里使用普通字体文件生成Tex…...

信创ARM架构QT应用开发环境搭建

Linux ARM架构QT应用开发环境搭建 前言交叉工具链Ubuntu上安装 32 位 ARM 交叉工具链Ubuntu上安装 64 位 ARM 交叉工具链 交叉编译 QT 库下载 QT 源码交叉编译 QT 源码 Qt Creator交叉编译配置配置 Qt Creator Kits创建一个测试项目 小结 前言 有没有碰到过这种情况&#xff1…...

使用SPM_batch进行批量跑脚本(matlab.m)

软件&#xff1a;spm8matlab2023bwin11 数据格式&#xff1a; F:\ASL\HC\CBF\HC_caishaoqing\CBF.nii F:\ASL\HC\CBF\HC_caishaoqing\T1.nii F:\ASL\HC\CBF\HC_wangdonga\CBF.nii F:\ASL\HC\CBF\HC_wangdonga\T1.nii clear spmdirD:\AnalysisApps\spm8; datadirF:\ASL\HC\CBF…...

力扣0124——二叉树的最大路径和

二叉树的最大路径和 难度&#xff1a;困难 题目描述 二叉树中的 路径 被定义为一条节点序列&#xff0c;序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点&#xff0c;且不一定经过根节点。 路径和 是路径中各节点…...

c# 字符串帮助类

public class StringHelper { #region 全角半角互相转换 /// <summary> /// 转全角的函数(SBC case) /// </summary> /// <param name"str">任意字符串</param> /// <returns>全…...

LabVIEW双光子荧光显微成像系统开发

双光子显微成像是一种高级荧光显微技术&#xff0c;广泛用于生物学和医学研究&#xff0c;尤其是用于活体组织的深层成像。在双光子成像过程中&#xff0c;振镜&#xff08;Galvo镜&#xff09;扮演了非常关键的角色&#xff0c;它负责精确控制激光束在样本上的扫描路径。以下是…...

Prim模板

通过代码探索Prim算法&#xff1a;最小生成树之旅 在计算机科学领域&#xff0c;图算法占据了至关重要的位置&#xff0c;尤其是在设计高效的网络&#xff08;无论是社交网络、计算机网络还是交通网&#xff09;时。在这些算法中&#xff0c;寻找最小生成树&#xff08;MST&am…...

CSS之盒子模型

盒子模型 01-选择器 结构伪类选择器 基本使用 作用&#xff1a;根据元素的结构关系查找元素。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IE…...

Linux系统安装(CentOS Vmware)

学习环境安装 VMware安装 VMware下载&安装 访问官网&#xff1a;https://www.vmware.com 在此处可以选择语言 点击China&#xff08;简体中文&#xff09; 点击产品&#xff0c;点击Workstation Pro 下滑&#xff0c;点击下载试用版 下滑找到Workstation 17 Pro for Wi…...

STM32 硬件随机数发生器(RNG)

STM32 硬件随机数发生器 文章目录 STM32 硬件随机数发生器前言第1章 随机数发生器简介1.1 RNG主要特性1.2.RNG应用 第2章 RNG原理框图第3章 RNG相关寄存器3.1 RNG 控制寄存器 (RNG_CR)3.2 RNG 状态寄存器 (RNG_SR)3.3 RNG 数据寄存器 (RNG_DR) 第3章 RNG代码部分第4章 STM32F1 …...

Window环境下使用go编译grpc最新教程

网上的grpc教程都或多或少有些老或者有些问题&#xff0c;导致最后执行生成文件时会报很多错。这里给出个人实践出可执行的编译命令与碰到的报错与解决方法。&#xff08;ps:本文代码按照煎鱼的教程编写&#xff1a;4.2 gRPC Client and Server - 跟煎鱼学 Go (gitbook.io)&…...

STM32——FLASH(1)简单介绍、分类、读写流程及注意事项

文章目录 FLASH的特点Nor flash和nand flashflash的读写flash 的存储单位 flash的读写过程 FLASH的特点 可擦写数据可修改可重写访问速度<ROM Nor flash和nand flash Nor flash 1、与SDRAM相似&#xff0c;用户可以直接运行装载到NORFLASH里面的代码&#xff0c;减少SRAM…...

MySQL的DML语言

DML&#xff1a;Data Manipulation Language&#xff08;数据操作语言&#xff09; DML语言用来对数据库中表的数据记录进行增、删、改操作。 一、添加数据命令 注意&#xff1a; 插入数据时&#xff0c;指定的字段顺序需要与值的顺序是一一对应的。 字符串和日期型数据应该包…...

Vivado-IP核

Vivado-IP核 主程序 timescale 1ns / 1ps ////module ip_clk_wiz(input sys_clk,input sys_rst_n,output clk_out1,output clk_out2,output clk_out3,output clk_out4,output locked);clk_wiz_0 instance_name(// Clock out ports.clk_out1(clk_out1), // output clk_out…...

品牌如何营造生活感氛围?媒介盒子分享

「生活感」简而言之是指人们对生活的感受和意义&#xff0c;它往往没有充斥在各种重要的场合和事件中&#xff0c;而是更隐藏在细碎平凡的生活场景中。在营销越来越同质化的当下&#xff0c;品牌应该如何打破常规模式&#xff0c;洞察消费情绪&#xff0c;找到更能打动消费者心…...

Java 学习和实践笔记(2)

今天的学习进度&#xff1a; 注册并下载安装好了Java 8&#xff0c;之后进行以下配置。 1&#xff09;path 是一个常见的环境变量&#xff0c;它告诉系统除了在当前的目标下妹寻找此程序外&#xff0c;还可以到path指定的目录下找。这句话是什么意思呢&#xff1f;以下举报例…...

Python:批量url链接保存为PDF

我的数据是先把url链接获取到存入excel中&#xff0c;后续对excel做的处理&#xff0c;各位也可以直接在程序中做处理&#xff0c;下面就是针对excel中的链接做批量处理 excel内容格式如下&#xff08;涉及具体数据做了隐藏&#xff09; 标题文件链接文件日期网页标题1http://…...

【LeetCode每日一题】525连续数组 303区域和检索(前缀和的基本概念和3个简单案例)

前缀和 // 构造prefix let prefix [0] arr.forEach(num > {prefix.push(prefix.at(-1) num); })如果想要计算某个区间 i 到 j 这个子数组的和时&#xff0c;可以根据 prefix[j1] - prefix[i] 获得。 例题1&#xff1a;303.区域和检索 - 数组不可变 给定一个整数数组 num…...

形态学算法应用之连通分量提取的python实现——图像处理

原理 连通分量提取是图像处理和计算机视觉中的一项基本任务&#xff0c;旨在识别图像中所有连通区域&#xff0c;并将它们作为独立对象处理。在二值图像中&#xff0c;连通分量通常指的是所有连接在一起的前景像素集合。这里的“连接”可以根据四连通或八连通的邻接关系来定义…...

Kafka系列之:Kafka集群同时设置基于时间和日志大小两种方式保存Topic的数据

Kafka系列之:Kafka集群同时设置基于时间和日志大小两种方式保存Topic的数据 一、基于日志大小二、基于时间大小三、参数设置四、设置命令一、基于日志大小 "log.retention.bytes"是Apache Kafka中的一项配置参数,用于指定每个日志段文件的最大大小。当日志段文件的…...

pytest+allure批量执行测试用例

在 Pytest 中,可以使用装饰器 `@pytest.fixture` 来定义用例级别的前置和后置操作。下面是一个示例代码,演示了如何使用 Pytest 的前置和后置操作: ```python import pytest @pytest.fixture(scope="function") def setup_function(): print("Setup fu…...

SpringBoot和SpringMVC

目录 一、springboot项目 &#xff08;1&#xff09;创建springboot项目 &#xff08;2&#xff09;目录介绍 &#xff08;3&#xff09;项目启动 &#xff08;4&#xff09;运行一个程序 &#xff08;5&#xff09;通过其他方式创建和运行springboot项目 二、SpringMVC…...