《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)*/// 如果搜索会话失败或者搜索结果为 0if (!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)的多人游戏插件,将被用于之后的游戏项目当中。


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

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

相关文章

TongWEB(东方通),部署WEB前后端项目步骤

我的系统: 银河麒麟桌面系统V10(SP1)(兆芯) 环境需要搭建好,什么redis,数据库等 1.准备项目前端war包 (我后端项目本就是war部署,jar转war自行百度一下吧) 进入前端打包好的dist文件夹,创建一个文件夹 WEB-INF ,再在 WEB-INF 里创建一个 web.xml 文件,文件内容: <web-…

Linux——简单的Shell程序

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、Shell程序思路二、Shell代码展示 一、Shell程序思路 用下图的时间轴来表示事件的发生次序…

经典Go知识点总结

开篇推荐 来来来,老铁们,男人女人都需要的技术活 拿去不谢:远程调试,发布网站到公网演示,远程访问内网服务,游戏联机 推荐链接 1.无论sync.Mutex还是其衍生品都会提示不能复制,但是能够编译运行 加锁后复制变量&#xff0c;会将锁的状态也复制&#xff0c;所以 mu1 其实是已…

【JVM】Java中SPI机制

打破双亲委派模型中提到SPI和JDBC相关内容&#xff0c;那么是如何打破双亲委派模型呢?本文进行一个讲解&#xff0c;在开始讲解之前&#xff0c;我们需要先了解Java中的SPI机制 是什么 SPI 全称Service Provider Interface&#xff0c;是 Java 提供的一套用来被第三方实现或…

python jupyter notebook打开页面方便使用

如果没安装jupyter, 请安装&#xff1a; pip install jupyter notebook 运行jupyter notebook jupyter notebook

“政务服务+AI交互数字人”,重新定义政务服务体验

随着AIGC发展&#xff0c;各地方政务部门纷纷通过AI交互数字人技术&#xff0c;提升企业和群众的办事效率、满意度&#xff0c;以数字人有效推动政务服务数字化、智能化发展。 *图片源于网络 如高新区将数字人海蓝作为政务服务大使&#xff0c;让数字人化身AI交互数字人可以面…

k8s-heml联动harbor 18

将打包的heml包上传到harbor仓库进行管理 创建一个公开的项目来接收传送的heml包 安装helm-push插件&#xff1a; helm plugin install https://github.com/chartmuseum/helm-push &#xff08;在线安装&#xff0c;要求网速要快或者提供科学上网&#xff09; 离线安装&…

Ansible 简介及部署 基础模块学习 ansible部署rsync 及时监控远程同步

Ansible介绍&#xff1a; Ansible 是一个配置管理系统&#xff0c;当下最流行的批量自动化运维工具之一&#xff0c;它是一款开源的自动化工具&#xff0c;基于Python开发的配置管理和应用部署的工具。 Ansible 是基于模块工作的&#xff0c;它只是提供了一种运行框架&#xff…

5G网络建设 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 现需要在某城市进行5G网络建设&#xff0c;已经选取N个地点设置5G基站&#xff0c;编号固定为1到N&#xff0c; 接下来需要各个基站之间使用光纤进行连接以确保基…

Stable Diffusion 绘画入门教程(webui)-ControlNet(IP2P)

上篇文章介绍了深度Depth&#xff0c;这篇文章介绍下IP2P&#xff08;InstructP2P&#xff09;, 通俗理解就是图生图&#xff0c;给原有图加一些效果,比如下图&#xff0c;左边为原图&#xff0c;右边为增加了效果的图&#xff1a; 文章目录 一、选大模型二、写提示词三、基础参…

计算机网络:思科实验【1-访问WEB服务器】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;Cisco Packet Tracer实验 本文对应的实验报告源文件请关注微信公众号程序员刘同学&#xff0c;回复思科获取下载链接。 实验目的实验环境实验内容熟悉仿真软件访问WEB服务器 实验体会总结…

Python实战:xlsx文件的读写

Python实战&#xff1a;xlsx文件的读写 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您的订阅和支持~ &#…

中断系统(详解与使用)

讲解 简介 中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。 假设一个人在家看电视,这时候突然门铃响了,这个人此时就要停止看电视去开门,然后关上门后继续回来…

PyTorch:transforms.Normalize()函数详解

PyTorch&#xff1a;transforms.Normalize()函数详解 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程 &#x1f448; 希望得到您的订阅和…

考研西电(833),考什么?计算机组成原理第一章要点

目录 1.1 计算机的发展历史&#xff08;必须要了解的知识点&#xff09;1.1.1 发展历史1.1.2 摩尔定律★★ 1.2 计算机的基本组成1.2.1 硬件系统1.2.2 软件系统1.2.3 指令集系结构1.2.4 高级语言程序的执行过程 1.3 计算机的层次概念1.3.1 计算机系统的层次结构1.3.2 计算机体系…

React18源码: reconcliler启动过程

Reconcliler启动过程 Reconcliler启动过程实际就是React的启动过程位于react-dom包&#xff0c;衔接reconciler运作流程中的输入步骤.在调用入口函数之前&#xff0c;reactElement(<App/>) 和 DOM对象 div#root 之间没有关联&#xff0c;用图片表示如下&#xff1a; 在启…

TiDB 社区智慧合集丨TiDB 相关 SQL 脚本大全

非常感谢各位 TiDBer 在之前 【TiDBer 唠嗑茶话会 48】非正式 TiDB 相关 SQL 脚本征集大赛&#xff01;( https://asktug.com/t/topic/996635 )里提供的各种常用脚本。 在这篇文章中&#xff0c;我们整理了社区同学提供的一系列 TiDB 相关 SQL 脚本&#xff0c;希望能为大家在…

C++基础知识(六:继承)

首先我们应该知道C的三大特性就是封装、继承和多态。 此篇文章将详细的讲解继承的作用和使用方法。 继承 一个类&#xff0c;继承另一个已有的类&#xff0c;创建的过程 父类(基类)派生出子类(派生类)的过程 继承提高了代码的复用性 【1】继承的格式 class 类名:父类名 {}; 【…

机器视觉选型:如何选择一个合适光源控制器

在机器视觉系统中&#xff0c;选择合适的光源及其控制器对于确保高质量图像捕获和处理至关重要。本文会提供一些建议&#xff0c;以便于引导您了解如何基于应用需求选择最合适的光源和光源控制器。 1. 理解光源的功率需求 不同类型的光源具有不同的功率需求&#xff0c;这直接…

汽水分离器——矿用分离过滤装置

去找一个奋发向上能带动你的人&#xff0c;去找一个像太阳一样的人&#xff0c;帮你晒晒全部不值一提的迷茫! 一、结构&#xff1a; 气水分离器又称气水分离过滤器&#xff0c;主要由&#xff1a;进口、筒体、滤芯连接件、滤芯、密封圈、阀门连接件、出气管、排水口、压力表等…