《UE5_C++多人TPS完整教程》学习笔记24 ——《P25 完善菜单子系统(Polishing The Menu Subsystem)》


本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 —— 《P25 完善菜单子系统(Polishing The Menu Subsystem)》 的学习笔记,该系列教学视频为 Udemy 课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。


文章目录

  • P25 完善菜单子系统
  • 25.1 保证创建会话随时可用
  • 25.2 添加退出游戏按钮
  • 25.3 添加按钮禁用功能
  • 25.4 Summary


P25 完善菜单子系统

本节课是该系列课程关于制作多人游戏插件部分的最后一节课,我们将继续完善菜单子系统,保证创建会话功能一直可用,为菜单添加一个退出游戏按钮,然后实现按钮被点击一次之后就禁用的功能,以防短时间多次重复创建或查找会话,并考虑在一些情况下重新启用按钮。
在这里插入图片描述


25.1 保证创建会话随时可用

  1. 我们在进行测试的时候可能会发现一个问题:首先在设备 1 创建会话。
    在这里插入图片描述
    在这里插入图片描述
    在设备 2 加入会话,
    在这里插入图片描述
    在这里插入图片描述
    此时在设备 1 上退出游戏,原先会话被销毁,设备 2 上的玩家被踢出,回到菜单界面。
    在这里插入图片描述
    设备 2 在回到菜单界面后 立刻 点击 “Host” 按钮创建会话,可以发现屏幕左上角出现创建会话失败消息。但过了几秒钟后再次创建会话,可又以发现创建会话成功了。
    在这里插入图片描述
    在这里插入图片描述

  2. 这是因为在 “MultiplayerSessionsSubsystem.cpp” 的 “CreateSession()” 中,我们在创建会话前检查了先前存在会话,如果存在我们将调用 “SessionInterface->DestroySession()” 销毁先前的会话,但信息同步至原来创建会话的设备上需要时间,所以当执行到 “SessionInterface->CreateSession()” 函数时,会话可能还没有被销毁,这种情况下在线接口无法创建会话。

    void UMultiplayerSessionsSubsystem::CreateSession(int32 NumpublicConnections, FString MatchType)
    {
    	// 检查会话接口是否有效
    	if (!SessionInterface.IsValid()) {
    		return;
    	}
    
    	// 检查是否先前存在会话
    	auto ExistingSession = SessionInterface->GetNamedSession(NAME_GameSession);
    	if (ExistingSession != nullptr) {								// 如果先前存在会话
    		SessionInterface->DestroySession(NAME_GameSession);			// 销毁会话
    	}
    
    	...
    	
    	if (!SessionInterface->CreateSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, *LastSessionSettings)) {
    		// 如果创建会话失败,将委托移出委托列表
    		SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle);
    
    		// 广播会话创建失败消息到自定义的子系统委托
    		SubsystemOnCreateSessionCompleteDelegate.Broadcast(false);
    	}
    }
    

    解决这个问题的方法就是利用先前定义的有关销毁会话的子系统委托,实现与其绑定的回调函数,在保证会话销毁完成之后再去执行 “SessionInterface->CreateSession()” 函数

  3. 在 “MultiplayerSessionsSubsystem.h” 中定义布尔变量 “bCreateSessionOnDestroy()”,用来标识上次创建的会话是否需要被销毁,定义变量 “LastNumPublicConnections” 和 “LastMatchType” 分别保存上次会话公共连接数和匹配类型。

    UCLASS()
    class MULTIPLAYERSESSIONS_API UMultiplayerSessionsSubsystem : public UGameInstanceSubsystem
    {
    	GENERATED_BODY()
    
    	...
    
    private:
    	
    	...
    
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	bool bCreateSessionOnDestroy{ false };		// 上次调用 CreateSession() 时先前会话是否存在且需要被销毁
    	int32 LastNumPublicConnections;				// 上次会话的公共连接数
    	FString LastMatchType;						// 上次会话的匹配类型
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    };
    
  4. 在 “MultiplayerSessionsSubsystem.cpp” 中修改 “CreateSession()” 函数检查先前会话是否存在的代码,如果检查到先前存在代码,设置 “bCreateSessionOnDestroy” 为 “true”,表示先前创建的会话(上次会话)需要被销毁,保存公共连接数和匹配类型,执行 “DestroySession()” 函数销毁会话,最后可以加上 “return;” 防止后面的会话设置和广播会话创建消息的代码被执行。

    void UMultiplayerSessionsSubsystem::CreateSession(int32 NumpublicConnections, FString MatchType)
    {
    	// 检查会话接口是否有效
    	if (!SessionInterface.IsValid()) {
    		return;
    	}
    
    	// 检查是否先前存在会话
    	auto ExistingSession = SessionInterface->GetNamedSession(NAME_GameSession);
    	if (ExistingSession != nullptr) {						// 如果先前存在会话
    		
    		/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    		bCreateSessionOnDestroy = true;						// 本次调用 CreateSession() 需要销毁先前会话
    		LastNumPublicConnections = NumpublicConnections;	// 保存上次会话公共连接数
    		LastMatchType = MatchType;							// 保存上次会话匹配类型
    
    		DestroySession();	// 销毁会话
    		return;				// 防止执行后面的代码(未提及)
    		/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	}
    	
    	...
    	
    }
    
  5. 在 “MultiplayerSessionsSubsystem.cpp” 中实现 “DestroySession()” 函数以及回调函数 “OnDestroySessionComplete()”,在保证会话销毁完成之后再去执行 “SessionInterface->CreateSession()” 函数。

    ...
    
    void UMultiplayerSessionsSubsystem::DestroySession()
    {
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	// 检查会话接口是否有效
    	if (!SessionInterface.IsValid()) {	// 如果会话接口无效
    		SubsystemOnDestroySessionCompleteDelegate.Broadcast(false);	// 广播会话销毁失败消息到自定义的子系统委托
    		return;
    	}
    
    	// 保存委托句柄,以便此后移出委托列表
    	DestroySessionCompleteDelegateHandle = SessionInterface->AddOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegate);	// 添加委托到会话接口的委托列表
    	
    	// 销毁会话
    	if (!SessionInterface->DestroySession(NAME_GameSession)) {
    		// 如果销毁会话失败,将委托移出委托列表
    		SessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegateHandle);
    		
    		// 广播会话销毁失败消息到自定义的子系统委托
    		SubsystemOnDestroySessionCompleteDelegate.Broadcast(false);	
    	}
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    }
    
    ...
    
    void UMultiplayerSessionsSubsystem::OnDestroySessionComplete(FName SessionName, bool bWasSuccessful)
    {
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	// 如果销毁会话成功,将委托移出委托列表
    	if (SessionInterface) {
    		SessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegateHandle);
    	}
    
    	// 如果上次调用 CreateSession() 时先前的会话需要被销毁且该会话已经销毁成功
    	if (bCreateSessionOnDestroy && bWasSuccessful) {
    		bCreateSessionOnDestroy = false;						// 恢复初始值
    		CreateSession(LastNumPublicConnections, LastMatchType);	// 创建新会话
    	}
    
    	// 广播销毁会话的结果到自定义的子系统委托
    	SubsystemOnDestroySessionCompleteDelegate.Broadcast(bWasSuccessful);
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    }
    
    ...
    
  6. 重新进行测试,可以看到在设备 2 在被设备 1 踢出后能立刻点击 “Host” 按钮,出现创建会话失败提示消息,因为先前的会话没有被销毁,在销毁先前的会话后出现创建会话成功的提示消息。
    在这里插入图片描述


25.2 添加退出游戏按钮

  1. 在虚幻引擎的内容浏览器中双击双击 “WBP_Menu” 图标,进入用户控件设计器窗口。确定在左下 “层级” 面板中已经选中 “画布画板” 后,在 “控制板” 面板中将 “通用” 选项卡下的 “按钮” 组件拖拽到设计器中,添加按钮 “QuitButton”;接着在右侧 “细节” 面板中设置 “瞄点” 在 “画布画板” 的右上方,设置 “QuitButton” 的 “位置 X” 为 -450、“位置 Y” 为 -100、“尺寸 X” 为 350、“尺寸 Y” 为 100。
    在这里插入图片描述

  2. 在 “控制板” 面板中将 “通用” 中的 “文本”(Text)组件拖拽到 “QuitButton” 上,为按钮添加文本。
    在这里插入图片描述

  3. 这里我们简单地使用蓝图编写点击 “QuitButton” 按钮后退出游戏事件。在左下 “层级”(Components)面板中选择 “QuitButton” 组件, 然后在 “细节” 面板中单击 “点击时”(On Clicked)后面的 “+” 按钮,此时 “事件图表” 面板会出现一个 “点击时 (QuitButton)” 事件节点。
    在这里插入图片描述

  4. 从 “点击时 (QuitButton)” 事件节点的输出引脚处拖拽出一条线连接 “退出游戏” 节点。
    在这里插入图片描述

  5. 进行测试,运行游戏,点击 “Quit” 按钮后可以退出游戏。
    在这里插入图片描述


25.3 添加按钮禁用功能

  1. 在 “Menu.cpp” 的 “HostButtonClicked()” 和 “JoinButtonClicked()” 函数添加按钮禁用的代码,这样按钮只能被点击一次,而不能被点击多次,以避免重复创建和查找会话。

    void UMenu::HostButtonClicked()	// 回调函数:响应鼠标单击 HostButton 事件
    {
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	HostButton->SetIsEnabled(false);	// 按钮被第一次点击后禁用
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    
    	if (MultiplayerSessionsSubsystem) {
    		MultiplayerSessionsSubsystem->CreateSession(NumPublicConnections, MatchType);	// 创建游戏会话
    	}
    }
    
    void UMenu::JoinButtonClicked()	// 回调函数:响应鼠标单击 HostButton 事件
    {
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	JoinButton->SetIsEnabled(false);	// 按钮被第一次点击后禁用
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    
    	if (MultiplayerSessionsSubsystem) {
    		MultiplayerSessionsSubsystem->FindSessions(10000);
    	}
    }
    
  2. 在 “Menu.cpp” 的 “OnCreateSession” 函数中添加如果会话创建失败重新启用 “HostButton” 按钮的代码

    void UMenu::OnCreateSession(bool bWasSuccessful)
    {
    	if (bWasSuccessful) {
    		
    		...
    	
    	}
    	else {
    		if (GEngine) {
    			GEngine->AddOnScreenDebugMessage(	// 添加调试信息到屏幕上
    				-1,				// 使用 -1 不会覆盖前面的调试信息
    				15.f,			// 调试信息的显示时间
    				FColor::Yellow,	// 字体颜色:黄色
    				FString::Printf(TEXT("Failed to create session!"))	// 打印会话创建成功消息
    			);
    		}
    
    		/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    		HostButton->SetIsEnabled(true);	// 如果会话创建失败,则重新启用 HostButton 按钮
    		/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	}
    }
    
  3. 在 “Menu.cpp” 的 “OnFindSessions()” 函数中添加如果搜索会话失败或者搜索结果为 0 则重新启用 “JoinButton” 按钮的代码,然后在“OnJoinSessions()” 函数中添加如果搜索会话成功但加入会话失败则重新启用 “JoinButton” 按钮的代码。

    void UMenu::OnFindSessions(const TArray<FOnlineSessionSearchResult>& SearchResults, bool bWasSuccessful)
    {
    	if (MultiplayerSessionsSubsystem == nullptr) {
    		return;
    	}
    	
    	// 遍历搜索结果并加入第一个匹配类型相同的会话(以后可以进行改进)
    	for (auto Result : SearchResults) {
    		FString SettingsValue;	// 保存会话匹配类型
    		Result.Session.SessionSettings.Get(FName("MatchType"), SettingsValue);	// 获取会话匹配类型
    		if (SettingsValue == MatchType) {						// 如果匹配类型相同
    			MultiplayerSessionsSubsystem->JoinSession(Result);	// 调用子系统的加入会话函数
    			return;
    		}
    	}
    
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	// 如果搜索会话失败或者搜索结果为 0
    	if (!bWasSuccessful || SearchResults.Num() == 0) {
    		JoinButton->SetIsEnabled(true);	// 重新启用 JoinButton 按钮
    	}
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    }
    
    void UMenu::OnJoinSession(EOnJoinSessionCompleteResult::Type Result)
    {
    	// 加入会话,并传送至关卡 “Lobby”
    	IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();						// 获取当前的在线子系统指针
    	if (OnlineSubsystem) {																// 如果当前在线子系统有效
    		IOnlineSessionPtr SessionInterface = OnlineSubsystem->GetSessionInterface();	// 获取会话接口智能指针
    		if (SessionInterface.IsValid()) {	// 如果获取会话接口成功
    			FString Address;				// 保存会话创建源地址
    			SessionInterface->GetResolvedConnectString(NAME_GameSession, Address);		// 获取会话创建源地址
    
    			APlayerController* PlayerController = GetGameInstance()->GetFirstLocalPlayerController();	// 获取玩家控制器
    			if (PlayerController) {
    				PlayerController->ClientTravel(Address, ETravelType::TRAVEL_Absolute);	// 客户端传送至关卡 “Lobby”
    			}
    		}
    	}
    
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    	// 如果搜索会话成功但加入会话失败
    	if (Result != EOnJoinSessionCompleteResult::Success) {
    		JoinButton->SetIsEnabled(true);	// 重新启用 JoinButton 按钮
    	}
    	/* P25 完善菜单子系统(Polishing The Menu Subsystem)*/
    }
    
  4. 进行测试。运行游戏后,点击 “Host” 按钮,可以看到在会话创建成功之前,按钮的颜色会一直保持较暗的亮度。
    在这里插入图片描述


25.4 Summary

本节课对我们的菜单类进行了完善:修复了创建会话前检查上次会话是否存在的代码,解决了加入会话的设备被会话创建者踢出后无法立即创建会话的问题,保证创建会话功能一直可用;接着为菜单添加一个退出游戏按钮,并使用蓝图编程实现退出游戏逻辑;然后实现按钮被点击一次之后就禁用的功能,以防短时间多次重复地创建或查找会话,并在会话创建失败后重新启用创建会话按钮,在搜索会话失败或搜索结果为 0 或加入会话失败后重新启用加入会话按钮。
在这里插入图片描述
至此,该系列课程关于多人游戏插件制作这部分已经结束,我们也已经拥有了一个健壮(Robust)的多人游戏插件,将被用于之后的游戏项目当中。


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

200万上下文窗口创飞Gemini 1.5!微软来砸谷歌场子了

谷歌刚刷新大模型上下文窗口长度记录&#xff0c;发布支持100万token的Gemini 1.5&#xff0c;微软就来砸场子了。 推出大模型上下文窗口拉长新方法——LongRoPE&#xff0c;一口气将上下文拉至2048k token&#xff0c;也就是200多万&#xff01; 并且1000步微调内&#xff0c…

深入浅出:探究过完备字典矩阵

在数学和信号处理的世界里&#xff0c;我们总是在寻找表达数据的最佳方式。在这篇博文中&#xff0c;我们将探讨一种特殊的矩阵——过完备字典矩阵&#xff0c;这是线性代数和信号处理中一个非常有趣且实用的概念。 什么是过完备字典矩阵&#xff1f; 首先&#xff0c;我们先…

<网络安全>《49 网络攻防专业课<第十四课 - 华为防火墙的使用(2)>

6 防火墙的防范技术 6.1 ARP攻击防范 攻击介绍 攻击者通过发送大量伪造的ARP请求、应答报文攻击网络设备&#xff0c;主要有ARP缓冲区溢出攻击和ARP拒绝服务攻击两种。 ARP Flood攻击&#xff08;ARP扫描攻击&#xff09;&#xff1a;攻击者利用工具扫描本网段或者跨网段主机时…

MT8791迅鲲900T联发科5G安卓核心板规格参数_MTK平台方案定制

MT8791安卓核心板是一款搭载了旗舰级配置的中端手机芯片。该核心板采用了八核CPU架构设计&#xff0c;但是升级了旗舰级的Arm Cortex-A78核心&#xff0c;两个大核主频最高可达2.4GHz。配备了Arm Mali-G68 GPU&#xff0c;通过Mali-G88的先进技术&#xff0c;图形处理性能大幅提…

网络原理-UDP/TCP协议

协议 在网络通信中,协议是非常重要的一个概念,在下面,我将从不同层次对协议进行分析. 应用层 IT职业者与程序打交道最多的一层,调用系统提供的API写出的代码都是属于应用层的. 应用层中有很多现成的协议,但是更多的,我们需要根据实际情况来进行制作自定义协议. 自定义协议…

Vue3 (unplugin-auto-import自动导入的使用)

安装 参考链接 npm i -D unplugin-auto-importvite.config.ts里面配置 import AutoImport from unplugin-auto-import/viteAutoImport({imports:[ vue,vue-router]})重新运行项目会生成一个auto-imports.d.ts的文件 /* eslint-disable */ /* prettier-ignore */ // ts-nochec…

C++之类作用域

目录 1、全局作用域 2、类作用域 2.1、设计模式之Pimpl 2.2、单例模式的自动释放 2.2.0、检测内存泄漏的工具valgrind 2.2.1、可以使用友元形式进行设计 2.2.2、内部类加静态数据成员形式 2.2.3、atexit方式进行 2.2.4、pthread_once形式 作用域可以分为类作用域、类名…

高并发系统实战课个人总结(极客时间)

高并发系统实战课 场景 读多写少 我会以占比最高的“读多写少”系统带你入门&#xff0c;梳理和改造用户中心项目。这类系统的优化工作会聚焦于如何通过缓存分担数据库查询压力&#xff0c;所以我们的学习重点就是做好缓存&#xff0c;包括但不限于数据梳理、做数据缓存、加缓…

有趣的CSS - 文字加载动画效果

大家好&#xff0c;我是 Just&#xff0c;这里是「设计师工作日常」&#xff0c;今天分享的是用 css 实现多字符模拟加载动画效果。 《有趣的css》系列最新实例通过公众号「设计师工作日常」发布。 目录 整体效果核心代码html 代码css 部分代码 完整代码如下html 页面css 样式页…

【Python笔记-设计模式】享元模式

一、说明 享元模式是一种结构型设计模式&#xff0c;它摒弃了在每个对象中保存所有数据的方式&#xff0c;通过共享多个对象所共有的相同状态&#xff0c;让你能在有限的内存容量中载入更多对象。 (一) 解决问题 旨在减少大量相似对象创建时的内存开销 (二) 使用场景 大量…

这是我见过最全的权限系统设计方案!

日常工作中权限的问题时时刻刻伴随着我们&#xff0c;程序员新入职一家公司需要找人开通各种权限&#xff0c;比如网络连接的权限、编码下载提交的权限、监控平台登录的权限、运营平台查数据的权限等等。 在很多时候我们会觉得这么多繁杂的申请给工作带来不便&#xff0c;并且…

[更新]ARCGIS之土地耕地占补平衡、进出平衡系统报备坐标txt格式批量导出工具(定制开发版)

序言 之前开发的耕地占补平衡报备格式&#xff0c;现在之前的基础上集成了耕地进出平衡报备格式导出。 之前版本软件详见&#xff1a;软件介绍 一、软件简介 本软件是基于arcgis二次开发的工具&#xff08;插件&#xff09;&#xff0c;需要授权后才能使用&#xff1b; 本软件…

125 Linux C++ 系统编程4 Linux 静态库制作,动态库制作,静态库和动态库对比。静态库运行时找不到库的bug fix

一 静态库 和动态库 对比 静态库的原理&#xff1a;假设我们有一个 静态库&#xff0c;大小为500M&#xff0c;这个静态库实现了一些打牌的逻辑算法&#xff0c;提供了一堆API&#xff0c;让开发者 可以轻松的实现 54张扑克牌的随机发牌&#xff0c;指定发牌等功能。 我们写了…

Nuxt3实现多语言与事件总线(EventBus)

我的nuxt版本为 3.10.0 一、多语言实现 这里我使用得是 nuxtjs/i18n 这个库 安装 npm install -D nuxtjs/i18nnext根目录创建 i18n.config.ts 文件 export default {legacy: false,locale: zh, // 默认值messages: {zh: {home: 首页,useKill: 使用技巧,helpCenter: 帮助中…

H桥逆变控制方式(单极性倍频)

单极性倍频图像 内部做了载波取反&#xff1a;正相载波和负相载波 最后都和调制载波一起比较 正相载波&#xff1a;Q7导通为高电平&#xff0c;Q15导通为低电平 负相载波&#xff1a;Q16导通为高电平&#xff0c;Q8导通为低电平 导通次序为&#xff1a;Q7Q16——Q7Q8——Q7Q…

Mamba 作者谈 LLM 未来架构

文章目录 前言 1、为什么注意力机制有效&#xff1f; 2、注意力计算量呈平方级增长 3、Striped Hyena 是个什么模型&#xff1f; 4、什么是 Mamba? 5、Mamba 硬件优化 6、2024年架构预测 7、对 AI 更多的预测 本片文章来自【机器之心】对Mamba作者进行采访所进行的编译整理。 …

Sora 对未来视频创作伦理的挑战和思考

Sora 对未来视频创作伦理的挑战和思考 随着人工智能技术的飞速发展&#xff0c;AI视频模型Sora的出现为视频创作带来了革命性的变革。然而&#xff0c;在技术进步的同时&#xff0c;也带来了一些伦理问题值得我们深思。 1. 真实性和虚假信息: Sora能够生成逼真的视频画面&…

初识51单片机

##江科大51单片机学习 什么是单片机&#xff1f;&#xff1f;&#xff1f; 单片机&#xff0c;英文名&#xff0c;Micro Controller Unit&#xff0c;简称MCU&#xff08;tips&#xff1a;有人会简称它为CPU&#xff0c;但不是如此&#xff0c;CPU其实被集成在MCU中&#xff…

人工智能 — 数字图像

目录 一、图像1、像素2、图像分辨率3、RGB 模型4、灰度5、通道6、对比度7、RGB 转化为 Gray8、RGB 值转化为浮点数9、二值化10、常用视觉库11、频率12、幅值 二、图像的取样与量化1、数字图像2、取样3、量化 三、上采样与下采样1、上采样&#xff08;upsampling&#xff09;2、…

测试计划、测试方案、测试策略、测试用例的区别

一 测试计划 测试计划是指描述了要进行的测试活动的范围、方法、资源和进度的文档。它主要包括测试项、被测特性、各阶段的测试任务、时间进度安排&#xff0c;谁执行任务和风险控制等&#xff0c;可以包括测试策略。 二 测试方案 测试方案是指描述需要测试的特性、测试的方…