UE4 -- 实现用于网络连接的插件

news/2024/5/19 2:06:59

插件

UE中的插件就相当于一个模块,在引擎界面点击创建新的插件后,会在项目文件夹中生成插件的文件夹,在该文件夹内,只需要像游戏项目一样编写插件逻辑,最后在插件选择界面开启该插件即可
当新建插件后,UE会自动生成继承于IModuleInterface的类,说明该文件夹的内容为插件。
在InsideUE4中对于Subsystem的介绍可以了解到,Subsystem的生命周期分别与其父类相同。在对于实现网络连接的插件,由于其是需要在整个游戏运行时存在所以选择了UGameInstanceSubsystem。

Lan连接

同一局域网下的连接,只要该局域网下有一个主机充当了服务器,其余电脑就可以作为客户端连接。UE没有区分客户端与服务器,所以客户端和服务器的代码都写在一个文件中。如果是网络属性赋值和RPC,会对函数添加UFUNCTION(Server/Client)说明该函数是在服务器还是客户端运行。
对于Lan连接,一般只需要两个函数,一个对应服务器,一个对应客户端

服务器

作为服务器的主机,主要实现打开地图,开启监听,当有其他主机请求连接时,进行请求。
在UE中创建一个地图,利用AWorld里面提供的ServerTravel函数对地图添加?listen。然后可以绑定一个按键用于调用这个函数,当某个主机按下该按键,该主机执行这个函数,成为服务器并且切换地图

void AMPGameDemoCharacter::OpenLobby_Map()
{UWorld* World = GetWorld();if (World) {World->ServerTravel("/Game/ThirdPersonCPP/Maps/Lobby_Map?listen");}
}

客户端

客户端主要就是实现地图的切换,通过IP地址来获取服务器,进行地图切换。有两种方法实现,可以是在单机游戏中OpenLevel的方法

void AMPGameDemoCharacter::CallOpenLobby_Map(const FString& Addr)
{UGameplayStatics::OpenLevel(this, *Addr);
}

使用按键控制调用,在调用时传入表示IP地址的字符串即可
第二种方法利用APlayerController中的ClientServer,传入IP地址和传送方式

void AMPGameDemoCharacter::CallClientTravel(const FString& Addr)
{//需要获取客户端上的角色控制器 APlayerController* PlayerController = GetGameInstance()->GetFirstLocalPlayerController();if (PlayerController) {PlayerController->ClientTravel(Addr, ETravelType::TRAVEL_Absolute);}
}

利用Steam提供的接口连接

插件类UMultiplayerSessionSubsystem
基础成员变量

	//Session接口IOnlineSessionPtr SessionInterface;TSharedPtr<FOnlineSessionSettings> SessionSettings;TSharedPtr<FOnlineSessionSearch> SessionSearch;

SessionInterface为一个接口指针,其作用就是维护与steam平台的连接,使用该指针可以实现session的创建,加入离开等等功能。是整个插件的核心
SessionSettings设置了连接session的方式,该session的最大容量等等信息
SessionSearch设置了对session进行搜索时的参数,用于寻找session时的判断配置等,其中会存储寻找的结果

对项目的配置

按照官方教程进行设置即可
参考连接

创建OnlineSubsystem

OnlineSubsystem - Series of interfaces to support communicating with various web/platform layer services
OnlineSubsystem是UE准备好的一系列支持各种平台网络服务的类,可以直接使用该类来获取与steam服务的连接。
通过在构造函数中调用Get函数获得OnlineSubsystem的实例对象,当获得了实例化对象,就可以利用GetSessionInterface()来对SessionInterface进行赋值,从而开始准备维护一个session

	//获得onlinesubsystem的变量IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();if (Subsystem){//将会话接口实例化SessionInterface = Subsystem->GetSessionInterface();}

功能

该插件是为了提供一个网络连接功能,即需要一个主机依靠steam创建一个session,然后其他主机通过对该session的搜索进行加入或者离开操作。
所以该插件主要实现以下几个功能,以供其他类进行调用

	//通过传入参数方便对Session的设置,一个是能够连接的玩家,一个是等待客户端连接时需要匹配的键值对值void CreateSession(int32 NumPublicConnections, FString MatchType);//客户端寻找Session时调用,最大搜寻数void FindSessions(int32 MaxSearchResults);//客户端加入Session时调用传入正确的Session会话void JoinSession(const FOnlineSessionSearchResult& SessionResult);//销毁会话和开始会话void DestroySession();void StartSession();

同时还需要创建委托和回调函数来控制,以及DelegateHandles来控制Delegate当这些Delegate结束时。
image

OnlineSessionInterface委托

OnlineSessionInterface中提供了上述五种功能完成时的委托声明,为了使用需要定义对应的成员变量

	FOnCreateSessionCompleteDelegate CreateSessionCompleteDelegate;FDelegateHandle CreateSessionCompleteDelegateHandle;FOnFindSessionsCompleteDelegate FindSessionsCompleteDelegate;FDelegateHandle FindSessionsCompleteDelegateHandle;FOnJoinSessionCompleteDelegate JoinSessionCompleteDelegate;FDelegateHandle JoinSessionCompleteDelegateHandle;FOnDestroySessionCompleteDelegate DestroySessionCompleteDelegate;FDelegateHandle DestroySessionCompleteDelegateHandle;FOnStartSessionCompleteDelegate StartSessionCompleteDelegate;FDelegateHandle StartSessionCompleteDelegateHandle;

对于上面五个Delegate,还需要定义5个回调函数来进行控制,这5个函数只会在该类中调用,用于将Delegate List中的对应的Delegate清除掉

	void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);void OnFindSessionsComplete(bool bWasSuccessful);void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);void OnDestroySessionComplete(FName SessionName, bool bWasSuccessful);void OnStartSessionComplete(FName SessionName, bool bWasSuccessful);

然后就需要对Delegate进行绑定上述5个回调函数,可以在构造函数中实现,此时构造函数就变为了

UMultiplayerSessionSubsystem::UMultiplayerSessionSubsystem():CreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this,&UMultiplayerSessionSubsystem::OnCreateSessionComplete)),FindSessionsCompleteDelegate(FOnFindSessionsCompleteDelegate::CreateUObject(this,&UMultiplayerSessionSubsystem::OnFindSessionsComplete)),JoinSessionCompleteDelegate(FOnJoinSessionCompleteDelegate::CreateUObject(this,&UMultiplayerSessionSubsystem::OnJoinSessionComplete)),DestroySessionCompleteDelegate(FOnDestroySessionCompleteDelegate::CreateUObject(this,&UMultiplayerSessionSubsystem::OnDestroySessionComplete)),StartSessionCompleteDelegate(FOnStartSessionCompleteDelegate::CreateUObject(this,&UMultiplayerSessionSubsystem::OnStartSessionComplete))
{//获得onlinesubsystem的变量IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();if (Subsystem){//将会话接口实例化SessionInterface = Subsystem->GetSessionInterface();}
}

上面对与UMultiplayerSessionSubsystem类的功能已经基本完成了,接下来就需要对5个功能函数进行编写了。
首先来看看在其他类如何实现对UMultiplayerSessionSubsystem类的功能调用,下面以一个主界面两个按钮为例,一个按钮用于创建session,一个按钮用于加入session。

Menu类调用UMultiplayerSessionSubsystem类的功能

首先要在Menu类中声明一个UMultiplayerSessionSubsystem对象,当按钮按下后,将通过对该对象中函数的调用实现功能。
声明后就需要对这个对象赋值,由于UMultiplayerSessionSubsystem类是继承的UGameInstance,所以当引擎一启动,UMultiplayerSessionSubsystem就会被创建,可以通过获取GameInstance实例对象,然后从GameInstance存储的列表中获得UMultiplayerSessionSubsystem对象,所以在Menu类的构造函数中就有

	//实例化子系统变量UGameInstance* GameInstance = GetGameInstance();if (GameInstance){MultiplayerSessionSubsystem = GameInstance->GetSubsystem<UMultiplayerSessionSubsystem>();}

之后就是实现点击事件的响应,以创建session为例,当点击创建按钮后,将调用MultiplayerSessionSubsystem对象中的Create功能函数,在Create功能函数中实现Session的创建,然后如果创建成功后,将利用自定义委托(不是上面5个)进行广播,而该广播的回调函数是在Menu类中(为了分离)实现了地图的转换
image

MultiplayerSessionSubsystem类中的委托

基本上是十个委托,5个为OnlineSessionInterface提供,其可以使用DelegateHandle进行控制,一般用来表明5个功能是否成功的执行,对应的回调函数中,如果顺利执行即CompleteSuccessful那么就可以广播自定义的5个委托,这5个委托用于在Menu类中调用绑定回调函数,当得到Successful后可以执行进入地图或者离开地图等功能。

//自定义的5个委托
//绑定自己创建的动态多播用于响应菜单UI界面的响应回调
//委托名字,参数类型,参数形参
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMultiPlayerOnCreateSessionComplete, bool, bWasSuccessful);//动态多播的返回类型必须是UClass或者UStruct,并且与蓝图兼容//寻找委托,返回一个数组结果,FOnlineSessionSearchResult不是一个UClass,所以在蓝图中也无法调用
//如果想在蓝图中调用,那么可以自己创建一个继承的UClass,然后采用动态多播
DECLARE_MULTICAST_DELEGATE_TwoParams(FMultiPlayerOnFindSessionComplete, const TArray<FOnlineSessionSearchResult>& SessionResults, bool bWasSuccessful);//加入会话的委托传入加入的结果
DECLARE_MULTICAST_DELEGATE_OneParam(FMultiPlayerOnJoinSessionComplete, EOnJoinSessionCompleteResult::Type Result);DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMultiPlayerOnDestroySessionComplete, bool, bWasSuccessful);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMultiPlayerOnStartSessionComplete, bool, bWasSuccessful);

image

五个功能函数的实现

这些函数都将在Menu类响应事件后被调用

CreateSession

创建Session,即通过传入的参数新建一个Session不涉及主机在创建Session后的具体操作,具体操作应该与该类分离,在Menu类实现比如地图的切换等等。
参数: 该Session最大容许加入的主机数,该会话的表示(用于其他主机搜寻,字符串即可)

void CreateSession(int32 NumPublicConnections, FString MatchType);
  1. 检测SessionInterface是否有效,如果无效说明没有获取到Steam服务,则无法继续CreateSession
  2. 检测之前是否存在Session,如果存在则先断开再重新创建
    利用SessionInterface中的GetNamedSession,来判断是否已经有Session存在,UE会将已经存在的Session存放在系统中,由宏定义Name_GameSession保存REGISTER_NAME(287,GameSession),如果可以通过Name_GameSession获得一个Session说明已经存在一个Session需要先断开再创建。
    image
    可以看到在搜索之前先上锁,然后在Session列表中以O(n)的复杂度搜索匹配项,如果没有返回NULL
	auto ExistingSession = SessionInterface->GetNamedSession(NAME_GameSession);if(ExistingSession != nullptr){bCreateSessionOnDestroy = true;LastNumPublicConnections = NumPublicConnections;LastMatchType = MatchType;//使用自己创建的销毁会话函数DestroySession();}
  1. 绑定委托以及添加DelegateHandle
    利用SessionInterface中的AddOnCreateSessionCompleteDelegate_Handle函数将OnCreateSessionCompleteDelegate添加到SessionInterface的Delegate列表中,该函数会返回一个DelegateHandle,可以利用该Handle来控制该Delegate
CreateSessionCompleteDelegateHandle = SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegate);
  1. 配置Session参数
//创建的会话设置SessionSettings = MakeShareable(new FOnlineSessionSettings());//设置是否为LAN连接SessionSettings->bIsLANMatch = IOnlineSubsystem::Get()->GetSubsystemName() == "NULL" ? true : false;SessionSettings->NumPublicConnections = NumPublicConnections;SessionSettings->bAllowJoinInProgress = true;SessionSettings->bAllowJoinViaPresence = true;SessionSettings->bShouldAdvertise = true;SessionSettings->bUsesPresence = true;//设置一些键值对,使得我们可以区分其他的session连接SessionSettings->Set(FName("MatchType"), MatchType, EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);SessionSettings->BuildUniqueId = 1;SessionSettings->bUseLobbiesIfAvailable = true;
  1. CreateSession
    利用SessionInterface中的CreateSession函数
    image
    可以看到Session在这个函数被调用之后仍然不是完全创建的,只有在OnCreateSessionCompleteDelegate被广播后才能说完成创建,以Steam平台为例,
bool FOnlineSessionSteam::CreateSession(int32 HostingPlayerNum, FName SessionName, const FOnlineSessionSettings& NewSessionSettings)

需要传入主机即创建该Session的Player,以及Name_GameSession,和配置的Session信息
该函数会首先定义一个局部变量,来判断是否创建成功uint32 Result = ONLINE_FAIL;,通过之前配置的参数进行Session的创建,然后调用OnCreateSessionCompleteDelegate进行广播完成创建
image
在子编写函数中可以利用 GetWorld()->GetFirstLocalPlayerFromController();获取当前Player,然后判断上述函数的返回值即可

	const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();//当会话创建失败,删除这个Handleif (!SessionInterface->CreateSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, *SessionSettings)){SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle);//广播自定义委托,传入的值会被回调函数接受MultiPlayerOnCreateSessionComplete.Broadcast(false);}

MultiPlayerOnCreateSessionComplete委托将广播false导致Menu类中的回调函数不会成功执行。
6. 清理Delegate
当CreateSession执行成功后,需要从Session的Delegate列表中清除CreateSessionCompleteDelegate,上面是可以在没有创建成功后清除,为了能在成功后清除,就需要之前创建的对应的回调函数

CreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this,&UMultiplayerSessionSubsystem::OnCreateSessionComplete))

在回调函数OnCreateSessionComplete中利用ClearOnCreateSessionCompleteDelegate_Handle清除Delegate并且广播MultiPlayerOnCreateSessionComplete.Broadcast(bWasSuccessful)

FindSession

另一个主要的功能就是加入Session功能,要加入首先就要搜索到对应的Session,用于其他主机在点击按钮后搜索Session。
参数: 搜索的最大范围,用于确定回去搜索多少个Session,如果过少可能造成无法寻找到的情况
当FindSession成功后,利用MultiPlayerOnFindSessionComplete委托进行广播到Menu类中的回调函数,由于会传入一个带有结果的数据,所以在回调函数中对数组遍历,判断Session的标识是不是设置的标识,如果是调用JoinSession()函数实现加入
还是以Steam平台为例,在调用FindSession后,寻找到的结果会存储在SessionResults内,然后根据是否是局域网联机,进行不同的寻找
image
会通过寻找当前网络中存在的SteamSession任务,然后加入到列表中。
当得到结果数组后,在Menu类中的回调函数中,通过对数组的遍历,进行JoinSession操作

	for (auto Result : SessionResults){FString SettingsValue;Result.Session.SessionSettings.Get(FName("MatchType"), SettingsValue);if (SettingsValue == MatchType){if (MultiplayerSessionSubsystem){MultiplayerSessionSubsystem->JoinSession(Result);return;}}}

JoinSession

void UMultiplayerSessionSubsystem::JoinSession(const FOnlineSessionSearchResult& SessionResult)
{if (!SessionInterface.IsValid()){MultiPlayerOnJoinSessionComplete.Broadcast(EOnJoinSessionCompleteResult::UnknownError);return;}//调用会话接口加入会话,绑定委托,添加委托列表JoinSessionCompleteDelegateHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegate);const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController();//给JoinSession添加客户端信息,session信息,session的名字和服务器信息if (!SessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, SessionResult)){SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle);MultiPlayerOnJoinSessionComplete.Broadcast(EOnJoinSessionCompleteResult::UnknownError);}
}

在JoinSession函数中,会通过判断SessionSettings里面的bUserPresence(主机是否显示用户信息)来选择Join方式和SessionInfo
image
如果bUserPresence为false说明是Client,会在加入时进行信息验证。
当成功的JoinSession后,MultiPlayerOnJoinSessionComplete委托调用Menu类中的回调函数,回调函数主要实现的功能就是客户端上地图的转换等等功能实现,而通过在LAN模式下地图的切换需要知道地图的地址,那么在非LAN模式下,地图的地址存放在SessionInterface中,为了使得插件MultiplayerSessionSubsystem类和Menu类的分离,所以需要在回调函数中定义一个局部变量获取SessionInterface。

void UMenuWidget::OnJoinSession(EOnJoinSessionCompleteResult::Type Result)
{//为了使得菜单与插件相互独立,所以我们需要单独获取SessionInterface//获得onlinesubsystem的变量IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get();if (Subsystem){//将会话接口实例化IOnlineSessionPtr SessionInterface = Subsystem->GetSessionInterface();if (SessionInterface.IsValid()){FString Addr;SessionInterface->GetResolvedConnectString(NAME_GameSession, Addr);//客户端利用ClientTrave进行地图转换APlayerController* PlayerController = GetGameInstance()->GetFirstLocalPlayerController();if (PlayerController){PlayerController->ClientTravel(Addr, ETravelType::TRAVEL_Absolute);}}}

DestroySession

UE提供的DestroySession函数中,首先通过传入的SessionName寻找到需要Destroy的Session,然后会先停止当前正在进行的所有进程
image
其次通过判断bUsesPresence,来确定是主机还是客户端想要DestroySession
如果是主机,会直接将该Session状态修改为Destroy,然后关闭所有连接
image
如果是客户端,会先清空与Session的会话,然后从Server LogOff,最后Destroy
image
主要区别就是LogOff的类不一样
FOnlineAsyncTaskSteamLeaveLobby: Async task for leaving a single lobby
FOnlineAsyncTaskSteamLogoffServer:Async task for shutting down an advertised game erver

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

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

相关文章

OKR-Periods of Words

[POI2006] OKR-Periods of Words 题面翻译 对于一个仅含小写字母的字符串 \(a\),\(p\) 为 \(a\) 的前缀且 \(p\ne a\),那么我们称 \(p\) 为 \(a\) 的 proper 前缀。 规定字符串 \(Q\) 表示 \(a\) 的周期,当且仅当 \(Q\) 是 \(a\) 的 proper 前缀且 \(a\) 是 \(Q+Q\) 的前缀。…

Phone List

题目描述输入格式输出格式样例 样例输入 2 3 911 97625999 91125426 5 113 12340 123440 12345 98346 样例输出 NO YES 数据范围与提示这道题的三条判断是否存在前缀的标准:当在建树字符串已经到结尾时,如果该点有结束标记,那肯定是前缀(不是真前缀)当在建树字符串已经到结…

SSM教务管理系统设计与实现(附源码下载地址)

@目录01 项目背景02 使用技术03 运行环境04 功能分析05 数据库设计06 项目工程结构07 部分功能展示及源码7.1 登录页7.2 管理员端--首页7.3 管理员端--课程管理7.4 管理员端--学生管理7.5 教师端--首页7.6 教师端--个人信息7.7 学生端--已修课程7.8 学生端--公告管理08 运行教程…

AutoCAD C# 两不平行直线倒圆弧算法

参考的博客:https://www.cnblogs.com/JJBox/p/14300098.html 下面是计算示例主要计算代码:var peo = new PromptEntityOptions("选择直线1"){AllowNone = false,AllowObjectOnLockedLayer = false};peo.SetRejectMessage("请选择直线Line");peo.AddAllow…

挖矿流量分析之Stratum挖矿协议

目录前言区块链和挖矿相关概念挖矿木马挖矿协议StratumStratum工作过程 前言 之前做了一个关于“挖矿行为检测”的大创训练项目,在这里记录一下我关于挖矿检测相关内容的学习。 区块链和挖矿相关概念 区块链 首先需要了解一些关于区块链的内容。注意,区块链和挖矿是两个紧密相…

sublime设置默认打开侧边栏(失败)

描述 每次使用sublime打开某个目录,总是不显示侧边栏,还得手动打开。 过程 在设置里找了半天似乎没有这个选项,有点离谱,网上搜到的全是手动打开侧边栏。看来只能Ctrl+KB按得勤快些了。// Display the toggle sidebar button in the status bar"show_sidebar_button&q…

C++模板

C++模板 C++是一个面向对象编程的语言,提供了类的继承和组合机制,虽然在层次结构上很简单,但使用起来非常糟糕。C++使用关键字template,告诉编译器声明的类或者对象是一个模板。模板不是像继承和组合那样重用目标代码,而是重用源代码。容器不再包含名为 Object 的泛型基类…

Matlab安装教程(Linux)

解压安装包 在虚拟机中,文件直接通过拖拽文件的方式将安装包拉入虚拟机时,文件通常存放在/tmp/VMwareDnD中,因此需要将存放文件位置的文件转移到/home/<用户名>/<存放目录>中 参考命令如下: mv /tmp/VMwareDnD/<文件存放目录>/* /home/<用户名>/&l…