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


    UE4运用C++和框架开发坦克大战教程笔记(十九)(第58~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);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249

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

    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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    运行游戏,可见界面上有状态栏和小地图,
    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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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);
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    来到 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    接下来测试下销毁 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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    运行游戏,首先可看到状态栏和小地图;接着是大地图出现(状态栏和小地图收起);菜单弹窗出现;设置弹窗出现;
    随后销毁小地图、销毁设置弹窗、销毁大地图(状态栏出现)、销毁状态栏,最后销毁菜单弹窗。
    说明销毁 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

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

    DDTypes.h

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

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

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

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

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

  • 相关阅读:
    ThreadPoolTaskScheduler的使用以及动态修改cron表达式时的任务stop与start
    基于STM32单片机的温度报警器(数码管)(Proteus仿真+程序)
    Redis 内存优化神技,小内存保存大数据
    简单的 JSONParser
    GRE简单模拟 Generic Routing Encapsulation
    Opecv检测多个圆形(霍夫圆检测,轮廓面积筛选,C/C++)
    LeetCode 002:两数相加
    ATFX:转机未现,EURUSD空头趋势恐延续,但短线或有反弹
    Zookeeper(window)安装
    腾讯云4核8G云服务器租用价格选轻量还是CVM?性能如何?
  • 原文地址:https://blog.csdn.net/q446013924/article/details/135915523