此博客为学习油管DruidMech大佬的广域网多人射击游戏的笔记
附上项目GitHub源码地址https://github.com/DruidMech/MultiplayerCourseMenuSystem
新建项目
首先使用引擎(5.0及以上)创建一个基于第三人称模板的.cpp
项目,我这里将其命名为MenuSystem
.
Subsystem插件配置
一、在编辑器-插件中打开Oline Subsystem Steam
插件,然后重启项目。
二、在项目的Build.cs
下找到一个PublicDependencyModuleNames
,把插件的配置项OnlineSubsystemSteam
、OnlineSubsystem
添加到其中然后编译
1 PublicDependencyModuleNames.AddRange(new string [] { "Core" , "CoreUObject" , "Engine" , "InputCore" , "HeadMountedDisplay" , "EnhancedInput" ,"OnlineSubsystemSteam" ,"OnlineSubsystem" });
三、在项目/config/DefaultEngine.ini
中添加下面代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [/Script/Engine.GameEngine] +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") [OnlineSubsystem] DefaultPlatformService=Steam [OnlineSubsystemSteam] bEnabled=true SteamDevAppId=480 ; If using Sessions ; bInitServerOnClient=true [/Script/OnlineSubsystemSteam.SteamNetDriver] NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection"
注意:
四、删除缓存文件saved
、Intermediate
、Binaries
后然右键.object
文件重新构建生成项目(GenerateProject
),完成配置。
访问在线子系统
在Character
类中添加引用
一、修改头文件Character.h
为其添加管理在线游戏会话的智能指针声明,在类底部写一个新的public
关键字用于区分系统源码与我们书写的代码
添加如下代码:
1 2 public : TSharedPtr<class IOnlineSession , ESPMode::ThreadSafe> OnlineSessionInterface;
修改解释:
TSharedPtr
:Unreal Engine 的智能指针 ,类似标准库的 std::shared_ptr
,但针对 UE 的内存管理优化。
<class IOnlineSession, ESPMode::ThreadSafe>
:
IOnlineSession
: 指向的接口类型(UE 在线会话系统的核心接口)。
ESPMode::ThreadSafe
: 指定指针的线程安全模式(此处为线程安全版本)。
二、修改Character.cpp
文件的头文件引用和构造函数
添加头文件:
1 2 #include "OnlineSubsystem.h" #include "Interfaces/OnlineSessionInterface.h"
找到析构函数在最后面添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get (); if (OnlineSubsystem){ OnlineSessionInterface = OnlineSubsystem->GetSessionInterface (); if (GEngine) { GEngine->AddOnScreenDebugMessage ( -1 , 15.f , FColor::Blue, FString::Printf (TEXT ("Found subsystem %s" ), *OnlineSubsystem->GetSubsystemName ().ToString ()) ); } }
CTRL+Shift+B
快捷键编译内容检查是否有错误
注意⚠️ 如果编译时出现无法热重载文件的报错,解决方法:
关闭虚幻编辑器,在文件目录下删除缓存文件saved
、Intermediate
、Binaries
后然右键.object
文件重新构建生成项目(GenerateProject
),打开项目重新编译一般就解决了。
打包项目测试
注意⚠️ 在测试之前首先需要打开steam,挂在后台就可以。
上面修改的代码编译完成后在之间虚幻编辑器中运行是无法获取子系统的实例的。
将项目打包到项目/Build/
目录下,再运行可发现,右下角弹出了Steam服务调用的提示,而游戏窗口中的Logger也显示在线子系统返回的服务名称为Steam,证明访问子系统成功。
创建与加入会话实现
创建等待关卡
新建一个地图保存在ThirdPerson/Maps
目录下,命名为Lobby
.
在项目打包设置中将该地图添加到PackageMapsList
对Character.h
进行修改
在导包处添加
1 #include "Interfaces/OnlineSessionInterface.h"
删除之前自己在public
处写的:
1 TSharedPtr<class IOnlineSession , ESPMode::ThreadSafe> OnlineSessionInterface;
修改为:
1 IOnlineSessionPtr OnlineSessionInterface;
在下面添加暴露给蓝图的函数以及用于同步事件的委托:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 protected : UFUNCTION (BlueprintCallable) void CreateGameSession () ; UFUNCTION (BlueprintCallable) void JoinGameSession () ; void OnCreateSessionComplete (FName SessionName, bool bWasSuccessful) ; void OnFindSessionsComplete (bool bWasSuccessful) ; void OnJoinSessionComplete (FName SessionName, EOnJoinSessionCompleteResult::Type Result) ; private : FOnCreateSessionCompleteDelegate CreateSessionCompleteDelegate; FOnFindSessionsCompleteDelegate FindSessionsCompleteDelegate; TSharedPtr<FOnlineSessionSearch> SessionSearch; FOnJoinSessionCompleteDelegate JoinSessionCompleteDelegate;
对Character.cpp
进行修改
首先在cpp文件最上面导包的部分用
1 #include "OnlineSessionSettings.h"
替换原来的
1 #include "Interfaces/OnlineSessionInterface.h"
然后将AMenuSystemCharacter类
头的部分修改为:
1 2 3 4 5 6 7 AMenuSystemCharacter::AMenuSystemCharacter(): CreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateSessionComplete)), FindSessionsCompleteDelegate(FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnFindSessionsComplete)), JoinSessionCompleteDelegate(FOnJoinSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnJoinSessionComplete)) { // 构造函数主体... }
这是一种虚幻引擎中委托绑定 的高级用法
成员初始化列表绑定 :在对象构造阶段直接绑定委托
CreateUObject
方法 :虚幻提供的安全绑定工具
ThisClass
用法 :避免硬编码类名,增强可维护性
其流程如下图所示:
graph TD
A[角色创建] --> B[委托绑定]
B --> C[会话操作]
C --> D{角色销毁}
D -->|自动解绑| E[安全回收内存]
在.cpp
文件中实现.h
文件中定义过的函数与回调函数:
实现CreateSession
功能:
CreateGameSession
函数实现:
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 void AMenuSystemCharacter::CreateGameSession () { if (!OnlineSessionInterface.IsValid ()) { UE_LOG (LogTemp, Warning, TEXT ("OnlineSessionInterface is invalid!" )); return ; } auto ExistingSession = OnlineSessionInterface->GetNamedSession (NAME_GameSession); if (ExistingSession != nullptr ) { OnlineSessionInterface->DestroySession (NAME_GameSession); UE_LOG (LogTemp, Log, TEXT ("Existing session destroyed" )); } OnlineSessionInterface->AddOnCreateSessionCompleteDelegate_Handle (CreateSessionCompleteDelegate); TSharedPtr<FOnlineSessionSettings> SessionSettings = MakeShareable (new FOnlineSessionSettings ()); SessionSettings->bIsLANMatch = false ; SessionSettings->NumPublicConnections = 4 ; SessionSettings->bAllowJoinInProgress = true ; SessionSettings->bAllowJoinViaPresence = true ; SessionSettings->bShouldAdvertise = true ; SessionSettings->bUsesPresence = true ; SessionSettings->Set ( FName ("MatchType" ), FString ("FreeForAll" ), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing ); if (const ULocalPlayer* LocalPlayer = GetWorld ()->GetFirstLocalPlayerFromController ()) { OnlineSessionInterface->CreateSession ( *LocalPlayer->GetPreferredUniqueNetId (), NAME_GameSession, *SessionSettings ); UE_LOG (LogTemp, Log, TEXT ("Session creation requested" )); } }
回调OnCreateSessionComplete
函数实现:
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 void AMenuSystemCharacter::OnCreateSessionComplete (FName SessionName, bool bWasSuccessful) { if (bWasSuccessful) { if (GEngine) { GEngine->AddOnScreenDebugMessage ( -1 , 15.f , FColor::Blue, FString::Printf (TEXT ("Session created: %s" ), *SessionName.ToString ()) ); } if (UWorld* World = GetWorld ()) { World->ServerTravel ("/Game/ThirdPerson/Maps/Lobby?listen" ); } } else { if (GEngine) { GEngine->AddOnScreenDebugMessage ( -1 , 15.f , FColor::Red, TEXT ("Failed to create session!" ) ); } } }
实现JoinSession
功能:
JoinGameSession
函数:
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 void AMenuSystemCharacter::JoinGameSession () { if (!OnlineSessionInterface.IsValid ()) { UE_LOG (LogTemp, Warning, TEXT ("OnlineSessionInterface is invalid!" )); return ; } OnlineSessionInterface->AddOnFindSessionsCompleteDelegate_Handle (FindSessionsCompleteDelegate); SessionSearch = MakeShareable (new FOnlineSessionSearch ()); SessionSearch->MaxSearchResults = 100 ; SessionSearch->bIsLanQuery = false ; SessionSearch->QuerySettings.Set ( SEARCH_PRESENCE, true , EOnlineComparisonOp::Equals ); if (const ULocalPlayer* LocalPlayer = GetWorld ()->GetFirstLocalPlayerFromController ()) { OnlineSessionInterface->FindSessions ( *LocalPlayer->GetPreferredUniqueNetId (), SessionSearch.ToSharedRef () ); UE_LOG (LogTemp, Log, TEXT ("Session search started" )); } }
回调函数OnFindSessionsComplet
:
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 void AMenuSystemCharacter::OnFindSessionsComplete (bool bWasSuccessful) { if (!OnlineSessionInterface.IsValid () || !SessionSearch.IsValid ()) { return ; } for (const FOnlineSessionSearchResult& Result : SessionSearch->SearchResults) { FString MatchType; Result.Session.SessionSettings.Get ("MatchType" , MatchType); if (MatchType == "FreeForAll" ) { OnlineSessionInterface->AddOnJoinSessionCompleteDelegate_Handle (JoinSessionCompleteDelegate); if (const ULocalPlayer* LocalPlayer = GetWorld ()->GetFirstLocalPlayerFromController ()) { OnlineSessionInterface->JoinSession ( *LocalPlayer->GetPreferredUniqueNetId (), NAME_GameSession, Result ); break ; } } } }
回调函数OnJoinSessionComplete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void AMenuSystemCharacter::OnJoinSessionComplete (FName SessionName, EOnJoinSessionCompleteResult::Type Result) { if (Result != EOnJoinSessionCompleteResult::Success) { UE_LOG (LogTemp, Warning, TEXT ("Join session failed with code: %d" ), Result); return ; } FString ConnectString; if (OnlineSessionInterface->GetResolvedConnectString (NAME_GameSession, ConnectString)) { if (APlayerController* PlayerController = GetGameInstance ()->GetFirstLocalPlayerController ()) { PlayerController->ClientTravel (ConnectString, TRAVEL_Absolute); } } }
几个函数的调用逻辑:
flowchart TD
subgraph 构造函数初始化
A[AMenuSystemCharacter 构造函数] --> B[绑定CreateSessionCompleteDelegate]
A --> C[绑定FindSessionsCompleteDelegate]
A --> D[绑定JoinSessionCompleteDelegate]
end
subgraph 会话创建流程
B --> E[CreateGameSession]
E -->|调用| F[OnlineSessionInterface->CreateSession]
F -->|异步完成| G[OnCreateSessionComplete]
G -->|成功| H["ServerTravel到大厅"]
G -->|失败| I[显示错误]
end
subgraph 会话搜索流程
C --> J[JoinGameSession]
J -->|调用| K[OnlineSessionInterface->FindSessions]
K -->|异步完成| L[OnFindSessionsComplete]
L -->|筛选匹配| M["MatchType=='FreeForAll'"]
M -->|符合条件| N[调用JoinSession]
end
subgraph 会话加入流程
N --> O[OnlineSessionInterface->JoinSession]
O -->|异步完成| P[OnJoinSessionComplete]
P -->|成功| Q["ClientTravel连接"]
P -->|失败| R[显示错误]
end
style A fill:#f9f,stroke:#333
style E fill:#6f9,stroke:#333
style J fill:#6f9,stroke:#333
style N fill:#6f9,stroke:#333
异步调用流程图:
sequenceDiagram
participant Character as AMenuSystemCharacter
participant Online as 在线子系统
participant Platform as 平台服务
Character->>Online: CreateSession/FindSessions/JoinSession
Online->>Platform: 调用平台API
Platform-->>Online: 返回结果
Online->>Character: 触发对应Delegate
在角色蓝图中调用.cpp
文件中写好的函数:
打包测试:
注意 ⚠️ 如果报了Fail to create a session
的错误
去config/DefaultEngine.ini
中添加一句bInitServerOnClient=true
重新保存打包测试
注意 ⚠️ 如果报了Fail to Find a session
的错误
确认网络没有问题,在CreateGameSession
函数中SessionSettings
部分添加一句
1 SessionSettings->bUseLobbiesIfAvailable = true ;
注意 ⚠️ 如果报成功创建Session,但是一直find不到,检查一下两台主机的Steam下载地区是否相同
创建插件
new
一个Blank
模板的Plugin
,将其命名为MultiplayerSessions
,在内容浏览界面打开Show Plugin Content
进入VS界面ReloadAll
重新加载全部,查看插件模块,Ctrl+Shift+B
编译
编译完成后在MultiplayerSession.uplugin
和MultiplayerSessions.Build.cs
中添加开发插件需要的插件和模组:
在MultiplayerSession.uplugin
添加在线子系统插件,并设置为Enabled
为true
1 2 3 4 5 6 7 8 9 10 "Plugins" : [ { "Name" : "OnlineSubsystem" , "Enabled" : true } , { "Name" : "OnlineSubsystemSteam" , "Enabled" : true } ]
中MultiplayerSessions.Build.cs
中添加编译需要的ModuleName
1 2 3 4 5 6 7 8 9 PublicDependencyModuleNames.AddRange( new string [] { "Core" , "OnlineSubsystem" , "OnlineSubsystemSteam" , } );
重新编译即可
子系统类
创建子系统类
在C++Classes\MenuSystem\
路径下创建一个继承自UGameInstanceSubsystem
的cpp
类,创建在MutiplayerSession
模块中这里我将他命名为MultiplayerSessionsSubsystem
,注意这里多加了个s,当初找了好久这个问题.
为新创建的类添加变量和实现构造函数:
.h
文件中,在类定义中添加构造函数和会话接口指针声明:
1 2 3 4 5 6 7 public : UMultiplayerSessionsSubsystem (); protected : private : IOnlineSessionPtr SessionInterface;
.cpp
文件中实现构造函数:
1 2 3 4 5 6 7 8 9 10 #include "OnlineSubsystem.h" UMultiplayerSessionsSubsystem::UMultiplayerSessionsSubsystem () { IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get (); if (Subsystem) { SessionInterface = Subsystem->GetSessionInterface (); } }
删除缓存文件(包括项目下的和Plugin
中的),重新生成vsproject
这里有一个容易触发的抽象错误,虚幻Class中的GENERATED_BODY()
报红.
这个问题是由于VS和虚幻的兼容性引起的,解决方法就是把头文件处的无用的缩进和空行都删掉.
委托与回调函数声明
先将需求的功能在类中进行声明:
.h
文件中public
部分添加功能函数:
1 2 3 4 5 void CreateSession (int32 NumPublicConnections, FString MatchType) ;void FindSessions (int32 MaxSearchResults) ;void JoinSession (const FOnlineSessionSearchResult& SessionResult) ;void DestroySession () ;void StartSession () ;
.h
文件中private
部分添加如下委托和委托句柄:
委托句柄的作用 :通过保存句柄,确保在合适的时机(如对象销毁时)清理委托,避免内存泄漏或无效回调。
直接保存 FDelegateHandle
比手动管理委托更安全,尤其是涉及异步操作时。
虚幻引擎的委托系统允许动态绑定/解绑函数到事件。
1 2 3 4 5 6 7 8 9 10 FOnCreateSessionCompleteDelegate CreateSessionCompleteDelegate; FDelegateHandle CreateSessionCompleteDelegateHandle; FOnFindSessionsCompleteDelegate FindSessionsCompleteDelegate; FDelegateHandle FindSessionsCompleteDelegateHandle; FOnJoinSessionCompleteDelegate JoinSessionCompleteDelegate; FDelegateHandle JoinSessionCompleteDelegateHandle; FOnDestroySessionCompleteDelegate DestroySessionCompleteDelegate; FDelegateHandle DestroySessionCompleteDelegateHandle; FOnStartSessionCompleteDelegate StartSessionCompleteDelegate; FDelegateHandle StartSessionCompleteDelegateHandle;
.h
文件中protected
部分添加上面委托的的回调函数:
1 2 3 4 5 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) ;
.cpp
文件中使用之前说过的委托绑定的高级用法:
上面定义的功能函数和回调后续再实现.
1 2 3 4 5 6 7 8 9 UMultiplayerSessionsSubsystem::UMultiplayerSessionsSubsystem (): CreateSessionCompleteDelegate (FOnCreateSessionCompleteDelegate::CreateUObject (this , &ThisClass::OnCreateSessionComplete)), FindSessionsCompleteDelegate (FOnFindSessionsCompleteDelegate::CreateUObject (this , &ThisClass::OnFindSessionsComplete)), JoinSessionCompleteDelegate (FOnJoinSessionCompleteDelegate::CreateUObject (this , &ThisClass::OnJoinSessionComplete)), DestroySessionCompleteDelegate (FOnDestroySessionCompleteDelegate::CreateUObject (this , &ThisClass::OnDestroySessionComplete)), StartSessionCompleteDelegate (FOnStartSessionCompleteDelegate::CreateUObject (this , &ThisClass::OnStartSessionComplete)) { }
菜单类
创建菜单
在C++Class\Public
文件夹下创建一个基于UUserWidget
的类,这里我将它命名为Menu
在Build.cs
中添加用户控件需要的模块:
1 2 3 4 5 6 7 8 9 10 11 12 PublicDependencyModuleNames.AddRange( new string [] { "Core" , "OnlineSubsystem" , "OnlineSubsystemSteam" , "UMG" , "Slate" , "SlateCore" } );
为Menu添加预设:
Menu.h
文件:
1 2 3 4 5 6 7 8 class MULTIPLAYERSESSIONS_API UMenu : public UUserWidget{ GENERATED_BODY () public : UFUNCTION (BlueprintCallable) void MenuSetup () ; };
Menu.cpp
文件,Setup
函数用于将控件添加到视口以及设置交互形式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void UMenu::MenuSetup () { AddToViewport (); SetVisibility (ESlateVisibility::Visible); bIsFocusable = true ; UWorld* World = GetWorld (); if (World) { APlayerController* PlayerController = World->GetFirstPlayerController (); if (PlayerController) { FInputModeUIOnly InputModeData; InputModeData.SetWidgetToFocus (TakeWidget ()); InputModeData.SetLockMouseToViewportBehavior (EMouseLockMode::DoNotLock); PlayerController->SetInputMode (InputModeData); PlayerController->SetShowMouseCursor (true ); } } }
基于Menu.cpp
类创建用户控件,命名为WBP_Menu
,创建两个Button
,并设置他的父类为Menu
将用户控件添加到视口便于测试:
添加基本交互
为Menu.h
添加声明
public
部分添加暴露给蓝图的创建函数,参数为房间人数
和比赛类型
:
1 2 UFUNCTION (BlueprintCallable)void MenuSetup (int32 NumberOfPublicConnections = 4 , FString TypeOfMatch = FString(TEXT("FreeForAll" ))) ;
protected
部分添加虚函数重载.
1 2 virtual bool Initialize () override ;virtual void NativeDestruct () override ;
Initialize()
是一个初始化函数,通常用于:
执行对象创建后的必要设置(如分配资源、加载数据、绑定委托等)。
返回 bool
表示初始化是否成功(true
=成功,false
=失败,可能触发错误处理)。
在虚幻引擎中,许多核心类(如 UObject
、AGameMode
、UGameInstance
)或其派生类会通过 Initialize()
NativeDestruct()
是 UUserWidget
(用户控件)提供的一个 关键生命周期虚函数 (析构函数),用于 处理控件的销毁逻辑 。
调用时机 :
当 UUserWidget
被 显式移除(RemoveFromParent
) 或 其所属的 UWorld
销毁时 (如关卡切换、游戏退出),引擎会自动调用此函数。
类比 :类似于 Actor 的 EndPlay()
或 C++ 对象的析构函数。
核心用途 :
释放资源 :清理绑定的动态委托(如按钮点击事件)、断开外部引用、销毁子控件等。
防止内存泄漏 :确保所有手动绑定的回调(如 UWorld
事件)被正确解绑。
private
部分声明UButton
变量并将其绑定到Widget
,声明点击事件,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 UPROPERTY (meta = (BindWidget))class UButton * HostButton;UPROPERTY (meta = (BindWidget))UButton* JoinButton; UFUNCTION ()void HostButtonClicked () ;UFUNCTION ()void JoinButtonClicked () ;void MenuTearDown () ;class UMultiplayerSessionsSubsystem * MultiplayerSessionsSubsystem;int32 NumPublicConnections{4 }; FString MatchType{ TEXT ("FreeForAll" ) };
注意 ⚠️:
C++文件中使用了BindWidget
后,用户控件的相应名称必须与变量相同,否则可能引起引擎崩溃.
在Menu.cpp
中实现初始化函数为按钮绑定事件函数,绑定事件,以及去除UI
的逻辑:
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 bool UMenu::Initialize () { if (!Super::Initialize ()) { return false ; } if (HostButton) { HostButton->OnClicked.AddDynamic (this , &ThisClass::HostButtonClicked); } if (JoinButton) { JoinButton->OnClicked.AddDynamic (this , &ThisClass::JoinButtonClicked); } return true ; } void UMenu::NativeDestruct () { MenuTearDown (); Super::NativeDestruct (); } void UMenu::MenuTearDown () { RemoveFromParent (); UWorld* World = GetWorld (); if (World) { APlayerController* PlayerController = World->GetFirstPlayerController (); if (PlayerController) { FInputModeGameOnly InputModeData; PlayerController->SetInputMode (InputModeData); PlayerController->SetShowMouseCursor (false ); } } }
添加需要的头文件:
1 2 3 #include "Menu.h" #include "Components/Button.h" #include "MultiplayerSessionsSubsystem.h"
子系统与UI交互
这部分的代码量比较大,但是基本是和之前的Character部分写的类似,画几张流程图梳理一下:
初始化阶段 :
sequenceDiagram
participant Menu as UMenu
participant Subsystem as UMultiplayerSessionsSubsystem
participant Online as OnlineSubsystem
Menu->>Subsystem: MenuSetup() → 初始化参数
Subsystem->>Online: 获取IOnlineSession接口
Menu->>Subsystem: 绑定委托(MultiplayerOnXXXComplete)
创建会话(Host) :
sequenceDiagram
participant Player as 玩家点击
participant Menu as UMenu
participant Subsystem as UMultiplayerSessionsSubsystem
participant Online as OnlineSessionInterface
Player->>Menu: 点击HostButton → HostButtonClicked()
Menu->>Subsystem: CreateSession(NumConnections, MatchType)
Subsystem->>Online: 销毁旧会话(如有)
Subsystem->>Online: 注册CreateSessionComplete委托
Subsystem->>Online: 配置SessionSettings(LAN/Online, MatchType等)
Online-->>Subsystem: OnCreateSessionComplete(成功/失败)
Subsystem->>Menu: 广播MultiplayerOnCreateSessionComplete
Menu->>World: ServerTravel("Lobby?listen") → 跳转地图
加入会话(Join) :
sequenceDiagram
participant Player as 玩家点击
participant Menu as UMenu
participant Subsystem as UMultiplayerSessionsSubsystem
participant Online as OnlineSessionInterface
Player->>Menu: 点击JoinButton → JoinButtonClicked()
Menu->>Subsystem: FindSessions(MaxSearchResults)
Subsystem->>Online: 注册FindSessionsComplete委托
Subsystem->>Online: 设置SearchQuery(过滤MatchType)
Online-->>Subsystem: OnFindSessionsComplete(结果列表)
Subsystem->>Menu: 广播MultiplayerOnFindSessionsComplete
Menu->>Subsystem: JoinSession(选择的结果)
Subsystem->>Online: 注册JoinSessionComplete委托
Online-->>Subsystem: OnJoinSessionComplete(结果)
Subsystem->>Menu: 广播MultiplayerOnJoinSessionComplete
Menu->>Player: ClientTravel(服务器地址) → 加入游戏
销毁逻辑与按钮禁用
UMultiplayerSessionsSubsystem
中添加几个变量用于存储销毁和按钮的变量:
1 2 3 4 bool bCreateSessionOnDestroy{ false };int32 LastNumPublicConnections; FString LastMatchType;
在find失败/join失败的时候启用按钮,其余情况禁用.
退出菜单
在之前写的WBP_Menu
中添加一个退出按钮,由于此处逻辑过于简单了,直接写在蓝图中即可: