本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 —— 《P13 加入游戏会话(Joining The Session)》 的学习笔记,该系列教学视频为 Udemy 课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么。
本节课将在《P11 设置加入游戏会话(Setup for Joining Sessions)》 代码的基础上,创建一个新关卡 “Lobby
”,以便玩家在加入已创建的会话后可以前往该关卡等待其他玩家加入;我们还将在会话设置中指定匹配类型(Match type),确保为游戏会话设置正确的匹配类型;当游戏会话被查找到时,首先检查匹配类型,然后自动获取游戏会话创建源 IP 地址,以便我们加入游戏会话后可以前往关卡 “Lobby
”。
Content\ThirdPerson\Maps
” 目录下。MenuSystemcharacter.cpp
” 中回调函数 “OnCreateSessionComplete()
”:如果游戏会话创建成功,进入关卡 “Lobby”。void AMenuSystemCharacter::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
if (bWasSuccessful) { // 如果游戏会话创建成功
if (GEngine) {
GEngine->AddOnScreenDebugMessage( // 添加调试信息到屏幕上
-1, // 使用 -1 不会覆盖前面的调试信息
15.f, // 调试信息的显示时间
FColor::Red, // 字体颜色
FString::Printf(TEXT("Create session: %s!"), *SessionName.ToString()) // 打印游戏会话的名称
);
}
/* P13 加入游戏会话(Joining The Sessions)*/
UWorld* World = GetWorld();
if (World) {
// Uworld->ServerTravel:https://docs.unrealengine.com/5.0/en-US/API/Runtime/Engine/Engine/UWorld/ServerTravel/
World->ServerTravel(FString("/Game/ThirdPerson/Maps/Lobby?listen")); // 作为监听服务器打开 Lobby 关卡
}
/* P13 加入游戏会话(Joining The Sessions)*/
}
else { // 如果游戏会话创建失败
...
}
}
在函数 CreateGameSession()
的会话设置部分添加类型匹配设置。
void AMenuSystemCharacter::CreateGameSession() // 当按下数字键 1 时调用
{
...
// 会话设置成员变量参阅及含义:https://docs.unrealengine.com/5.3/en-US/API/Plugins/OnlineSubsystem/FOnlineSessionSettings/
SessionSettings->bIsLANMatch = false; // 会话设置:不创建 LAN 连接
SessionSettings->NumPublicConnections = 4; // 会话设置:设置最大公共连接数为 4
SessionSettings->bAllowJoinInProgress = true; // 会话设置:在会话运行时允许其他玩家加入
SessionSettings->bAllowJoinViaPresence = true; // 会话设置:Steam 使用 Presence 搜索会话所在地区,确保连接正常工作
SessionSettings->bShouldAdvertise = true; // 会话设置:允许 Steam 发布会话
SessionSettings->bUsesPresence = true; // 会话设置:允许显示用户 Presence 信息
SessionSettings->bUseLobbiesIfAvailable = true; // (视频中未提及)会话设置:优先选择 Lobby API(Steam 支持 Lobby API)
/* P13 加入游戏会话(Joining The Sessions)*/
// void FOnlineSessionSettings::Set(FName Key, const FString& Value, EOnlineDataAdvertisementType::Type InType);
SessionSettings->Set(FName("MatchType"), FString("FreeForAll"), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);
/* P13 加入游戏会话(Joining The Sessions)*/
...
}
在回调函数 “OnFindSessionsComplete()
” 中加入对游戏会话搜索结果进行匹配类型检查的代码。
void AMenuSystemCharacter::OnFindSessionsComplete(bool bWasSuccessful)
{
for (auto Result : SessionSearch->SearchResults) { // 遍历游戏会话搜索结果
FString Id = Result.GetSessionIdStr();
FString User = Result.Session.OwningUserName;
/* P13 加入游戏会话(Joining The Sessions)*/
FString MatchType; // 保存游戏会话匹配类型
Result.Session.SessionSettings.Get(FName("MatchType"), MatchType); // 获取游戏会话匹配类型保存在 MatchType 中
/* P13 加入游戏会话(Joining The Sessions)*/
if (GEngine) {
GEngine->AddOnScreenDebugMessage( // 添加调试信息到屏幕上
-1, // 使用 -1 不会覆盖前面的调试信息
15.f, // 调试信息的显示时间
FColor::Cyan, // 字体颜色:蓝绿色
FString::Printf(TEXT("Id: %s, User: %s!"), *Id, *User) // 打印消息
);
}
/* P13 加入游戏会话(Joining The Sessions)*/
if (MatchType == FString("FreeForAll")) {
if (GEngine) {
GEngine->AddOnScreenDebugMessage( // 添加调试信息到屏幕上
-1, // 使用 -1 不会覆盖前面的调试信息
15.f, // 调试信息的显示时间
FColor::Cyan, // 字体颜色:蓝绿色
FString::Printf(TEXT("Joining Match Type: %s!"), *MatchType) // 打印游戏会话匹配类型
);
}
}
/* P13 加入游戏会话(Joining The Sessions)*/
}
}
添加代码到 “MenuSystemcharacter.h
” 的类 “AMenuSystemCharacter
” 中,定义委托 JoinSessionsCompleteDelegate
、以及委托的回调函数 OnJoinSessionComplete()
:
...
UCLASS(config=Game)
class AMenuSystemCharacter : public ACharacter
{
GENERATED_BODY()
...
public:
// 会话接口智能指针
// IOnlineSessionPtr OnlineSessionInterface; // 添加头文件 "Interfaces/OnlineSessionInterface.h" 后使用,更具可读性
TSharedPtr<class IOnlineSession, ESPMode::ThreadSafe> OnlineSessionInterface; // 使用 TSharedPtr 智能指针包装器进行声明
protected:
UFUNCTION(BlueprintCallable)
void CreateGameSession(); // 创建游戏会话
UFUNCTION(BlueprintCallable)
void JoinGameSession(); // 加入游戏会话
void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful); // 委托 CreateSessionCompleteDelegate 的回调函数
void OnFindSessionsComplete(bool bWasSuccessful); // 委托 FindSessionCompleteDelegate 的回调函数
/* P13 加入游戏会话(Joining The Sessions)*/
void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result); // 委托 JoinSessionCompleteDelegate 的回调函数
/* P13 加入游戏会话(Joining The Sessions)*/
private:
// 类 FOnCreateSessionCompleteDelegate 在 UE 5.0 和 5.1 版本的头文件 "Interfaces/OnlineSessionInterface.h" 中声明
// 而 5.2 和 5.3 版本的头文件 "Interfaces/OnlineSessionDelegates.h" 中声明
FOnCreateSessionCompleteDelegate CreateSessionCompleteDelegate; // 会话创建完成委托
FOnFindSessionsCompleteDelegate FindSessionsCompleteDelegate; // 会话查找完成委托
TSharedPtr<FOnlineSessionSearch> SessionSearch; // 会话查找智能指针
/* P13 加入游戏会话(Joining The Sessions)*/
FOnJoinSessionCompleteDelegate JoinSessionCompleteDelegate; // 会话加入完成委托
/* P13 加入游戏会话(Joining The Sessions)*/
};
在 “MenuSystemcharacter.cpp
” 构造函数 “AMenuSystemCharacter::AMenuSystemCharacter()
” 中为委托 “JoinSessionCompleteDelegate
” 绑定回调函数 “OnJoinSessionComplete()
” ,并在回调函数 “OnFindSessionsComplete()
” 中添加 “JoinSessionCompleteDelegate
” 到委托列表。
...
AMenuSystemCharacter::AMenuSystemCharacter() : // 为委托绑定回调函数
CreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateSessionComplete)),
FindSessionsCompleteDelegate(FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnFindSessionsComplete)),
/* P13 加入游戏会话(Joining The Sessions)*/
JoinSessionCompleteDelegate(FOnJoinSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnJoinSessionComplete))
/* P13 加入游戏会话(Joining The Sessions)*/
{
...
}
void AMenuSystemCharacter::OnFindSessionsComplete(bool bWasSuccessful)
{
/* P13 加入游戏会话(Joining The Sessions)*/
if (!OnlineSessionInterface.IsValid()) {
return;
}
/* P13 加入游戏会话(Joining The Sessions)*/
for (auto Result : SessionSearch->SearchResults) { // 遍历游戏会话搜索结果
FString Id = Result.GetSessionIdStr();
FString User = Result.Session.OwningUserName;
/* P13 加入游戏会话(Joining The Sessions)*/
FString MatchType; // 保存游戏会话匹配类型
Result.Session.SessionSettings.Get(FName("MatchType"), MatchType); // 获取游戏会话匹配类型保存在 MatchType 中
/* P13 加入游戏会话(Joining The Sessions)*/
if (GEngine) {
GEngine->AddOnScreenDebugMessage( // 添加调试信息到屏幕上
-1, // 使用 -1 不会覆盖前面的调试信息
15.f, // 调试信息的显示时间
FColor::Cyan, // 字体颜色:蓝绿色
FString::Printf(TEXT("Id: %s, User: %s!"), *Id, *User) // 打印游戏会话匹配类型
);
}
/* P13 加入游戏会话(Joining The Sessions)*/
if (MatchType == FString("FreeForAll")) {
if (GEngine) {
GEngine->AddOnScreenDebugMessage( // 添加调试信息到屏幕上
-1, // 使用 -1 不会覆盖前面的调试信息
15.f, // 调试信息的显示时间
FColor::Cyan, // 字体颜色:蓝绿色
FString::Printf(TEXT("Joining Match Type: %s!"), *MatchType) // 打印消息
);
}
OnlineSessionInterface->AddOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegate); // 添加委托到会话接口的委托列表
const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController(); //获取本地玩家指针
// 第一个输入参数类型为 const FUniqueNetId &SearchingPlayerId
// 第二个输入参数类型为 Fname SessionName
// 第三个输入参数类型为 const FOnlineSessionSearchResult &DesiredSession,这里写 Result 即可
OnlineSessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, Result); // 调用在线接口函数加入会话
}
/* P13 加入游戏会话(Joining The Sessions)*/
}
}
...
. 在回调函数 “OnJoinSessionComplete()
” 中获取游戏会话创建源 IP 地址并打印在屏幕上,然后前往关卡 “Lobby”。保存代码并进行编译。
void AMenuSystemCharacter::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
/* P13 加入游戏会话(Joining The Sessions)*/
if (!OnlineSessionInterface.IsValid()) {
return;
}
FString Address; // 保存游戏会话创建源地址
if (OnlineSessionInterface->GetResolvedConnectString(NAME_GameSession, Address)) {
if (GEngine) {
GEngine->AddOnScreenDebugMessage( // 添加调试信息到屏幕上
-1, // 使用 -1 不会覆盖前面的调试信息
15.f, // 调试信息的显示时间
FColor::Yellow, // 字体颜色:黄色
FString::Printf(TEXT("Connect string: %s!"), *Address) // 打印游戏会话创建源 IP 地址
);
}
}
APlayerController* PlayerController = GetGameInstance()->GetFirstLocalPlayerController(); // 获取玩家控制器
if (PlayerController) {
PlayerController->ClientTravel(Address, ETravelType::TRAVEL_Absolute); // 客户端传送至关卡 “Lobby”
}
/* P13 加入游戏会话(Joining The Sessions)*/
}
将项目打包之后发送到另一台设备上。在设备 1 上运行游戏(保证 Steam 已经运行),按下数字键 “1”,屏幕左上角红色字体显示会话的名称 “Game Session
” ,并且当前关卡跳转至 “Lobby
”,说明设备 1 创建会话成功。
在设备 2 上运行游戏(保证 Steam 已经运行且登录的账户与设备1 上登录的账号不同),按下数字键 “2”,屏幕左上角蓝绿色字体显示设备 1 上登录到 Steam 的 ID 和用户名以及设备 1 创建的游戏会话匹配类型 “FreeForAll
”,黄色字体显示设备 1 的 IP 地址,当前关卡跳转至 “Lobby
”,并且可以看到有两个玩家存在,说明设备 2 找到并加入了设备 1 创建的会话中。
本节课创建了一个 Basic 类型的新关卡 “Lobby”,使得玩家在加入已创建的会话后可以前往该关卡等待其他玩家的加入。接着,在会话设置 “SessionSettings
” 中添加了匹配类型指定和检查的代码,确保为游戏会话设置正确的匹配类型。然后,定义委托 “JoinSessionCompleteDelegate
” ,为它绑定回调函数 “OnJoinSessionComplete()
”,在回调函数 “OnFindSessionsComplete()
” 中添加 “JoinSessionCompleteDelegate
” 到在线接口委托列表,通过在线接口函数 “GetResolvedConnectString()
” 获取游戏会话创建源 IP 地址,使用函数 “ClientTravel()
”进行客户端传送至关卡 “Lobby”。最后在两台设备上登录两个不同的 Steam 账号,以进行创建会话和加入会话测试。