[UE5] Lyra改造計画⑤-4 OnlineSubsystemを使ってマッチメイキング

Post 2025年3月20日木曜日

C++ Epic Online Services GASでオンラインシリーズ Lyra改造 UE5 Unreal Engine オンラインゲーム

前回の続き。

なんかもう専門的すぎる話で一体誰に需要があるのか分からんですが、まあ世界のどこかの誰かにぶっ刺さればいいなくらいの気持ちでいきます。逆に専門的じゃないことはChatGPTに聞けばええんや。



お約束
この記事作成にあたって使用した主なUnreal Engine バージョンUE 5.3.2

本日のゴール

EOSでマッチメイキングする


そもそもマッチメイキングとは…?

マッチメイキングとはそもそもなんなのでしょう?ChatGPTに聞いたところ「プレイヤー同士を適切な条件で自動的にマッチングし、対戦や協力プレイを行えるようにする仕組みのこと」のようです。

種類がいくつかあるらしく、スキルレベルやランクを基準にしてマッチングさせる方式、カジュアルマッチでとにかくマッチングさせる方式、地域でマッチングさせる方式、役割を基準にしてマッチングさせる方式(役職が被らないように)

などです。このページではランクを基準にしてマッチングさせる方式について解説しますが、基本はどれも実現できると思います。


Lyraでのセッション検索の結果

そもそも、Lyraではセッションを作成する(いわゆる"ホスト"する)こともできれば、この作成されたセッションを検索することもできますし、その検索されたセッションに接続してゲームに参加すること(いわゆる"ジョイン")もできます。

Lyraでセッションを検索する手順は下記になります。



CreateOnlineSearchSessionRequestというノードを使います。
その返り値を、とりあえず変数化しておきます。

しかし、このノードはセッションを探すための申請書を作ったにすぎません。実際にセッションを探すノードは下記になります。

「Request」には先程作った変数を入れます。

それでは、セッションが検索完了したかどうかはどうやったらわかるのかというと、OnSearchFinishedというイベントディスパッチャー(というよりデリゲート?)が用意されています。


※検索完了というのは失敗も含むので、Succeededで場合分けが必要です。

しかし、このイベントディスパッチャーには、検索結果の返り値がありません。肝心な検索結果は、先程作成したSearch Requestの中に保存されます。
下記がその図で、そのSearch Requestの中の「Results」というマップ変数に格納されることになります。
※もちろんこのイベントディスパッチャーのあとに繋がないと結果は保存されていないので注意


このResultsからは、For Each Loopでそれぞれの結果を取り出すことができます。それぞれの結果に対して、Get String Settingノードで特定の情報を引き出すことが可能です。例えばセッションを作成したプレイヤーのレベルを格納すればランクマッチに使えそうですね。※情報はStringで出てくるので、後でintに変換してください。

Get String Settingノードには「Key」をいれることができます。これでなんの情報なのかを指定することができるわけです。


Keyは「OnlineSessionName.h」の中で記述されています。今回はCUSTOMを使うことにしました。

しかしこれで完了ではありません。実際には、セッションを作るときにこの情報を格納しないといけませんね。
なのでちょいとC++を変更します。

CommonSessionSubsystem.cpp(赤字部分が変更箇所)
  1. void UCommonSessionSubsystem::CreateOnlineSessionInternalOSSv1(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request, int32 PlayerLevel)
  2. {
  3.     const FName SessionName(NAME_GameSession);
  4.     const int32 MaxPlayers = Request->GetMaxPlayers();
  5.     const bool bIsPresence = Request->bUseLobbies; // Using lobbies implies presence
  6.     IOnlineSubsystem* const OnlineSub = Online::GetSubsystem(GetWorld());
  7.     check(OnlineSub);
  8.     IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
  9.     check(Sessions);
  10.     FUniqueNetIdPtr UserId;
  11.     if (LocalPlayer)
  12.     {
  13.         UserId = LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
  14.     }
  15.     else if (bIsDedicatedServer)
  16.     {
  17.         UserId = OnlineSub->GetIdentityInterface()->GetUniquePlayerId(0);
  18.     }
  19.     //@TODO: You can get here on some platforms while trying to do a LAN session, does that require a valid user id?
  20.     if (ensure(UserId.IsValid()))
  21.     {
  22.         HostSettings = MakeShareable(new FCommonSession_OnlineSessionSettings(Request->OnlineMode == ECommonSessionOnlineMode::LAN, bIsPresence, MaxPlayers));
  23.         HostSettings->bUseLobbiesIfAvailable = Request->bUseLobbies;
  24.         HostSettings->bShouldAdvertise = true;
  25.         HostSettings->Set(SETTING_GAMEMODE, Request->ModeNameForAdvertisement, EOnlineDataAdvertisementType::ViaOnlineService);
  26.         HostSettings->Set(SETTING_MAPNAME, Request->GetMapName(), EOnlineDataAdvertisementType::ViaOnlineService);
  27.         HostSettings->Set(SETTING_MATCHING_TIMEOUT, 120.0f, EOnlineDataAdvertisementType::ViaOnlineService);
  28.         HostSettings->Set(SETTING_SESSION_TEMPLATE_NAME, FString(TEXT("GameSession")), EOnlineDataAdvertisementType::ViaOnlineService);//変更。DontAdvertiseから。
  29.         HostSettings->Set(SETTING_ONLINESUBSYSTEM_VERSION, true, EOnlineDataAdvertisementType::ViaOnlineService);
  30.         HostSettings->Set(SETTING_CUSTOM, FString::FromInt(PlayerLevel), EOnlineDataAdvertisementType::ViaOnlineService);//追加
  31.         FSessionSettings& UserSettings = HostSettings->MemberSettings.Add(UserId.ToSharedRef(), FSessionSettings());
  32.         UserSettings.Add(SETTING_GAMEMODE, FOnlineSessionSetting(FString("GameSession"), EOnlineDataAdvertisementType::ViaOnlineService));
  33.         Sessions->CreateSession(*UserId, SessionName, *HostSettings);
  34.         CheckVacantSlot();
  35.     }
  36.     else
  37.     {
  38.         OnCreateSessionComplete(SessionName, false);
  39.     }
  40. }

これでHostSessionしたときにPlayerLevel (int32の変数)が呼び出され、CUSTOMに入れられます。

HostSessionを呼び出すところも修正していきます。

CommonSessionSubsystem.cpp(赤字部分が変更箇所)

  1. void UCommonSessionSubsystem::CreateOnlineSessionInternal(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request, int32 PlayerLevel)
  2. {
  3.     CreateSessionResult = FOnlineResultInformation();
  4.     PendingTravelURL = Request->ConstructTravelURL();
  5. #if COMMONUSER_OSSV1
  6.     CreateOnlineSessionInternalOSSv1(LocalPlayer, Request, PlayerLevel);
  7. #else
  8.     CreateOnlineSessionInternalOSSv2(LocalPlayer, Request);
  9. #endif
  10. }


その②

  1. void UCommonSessionSubsystem::HostSession(APlayerController* HostingPlayer, UCommonSession_HostSessionRequest* Request, int32 PlayerLevel)
  2. {
  3.     if (Request == nullptr)
  4.     {
  5. ---(中略)---
  6.     else
  7.     {
  8.         CreateOnlineSessionInternal(LocalPlayer, Request, PlayerLevel);
  9.     }
  10. }


CommonSessionSubsystem.h の方(赤字部分が変更箇所)

  1.     UFUNCTION(BlueprintCallable, Category=Session)
  2.     virtual void HostSession(APlayerController* HostingPlayer, UCommonSession_HostSessionRequest* Request, int32 PlayerLevel);

  1. void CreateOnlineSessionInternal(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request, int32 PlayerLevel);

  1. void CreateOnlineSessionInternalOSSv1(ULocalPlayer* LocalPlayer, UCommonSession_HostSessionRequest* Request, int32 PlayerLevel);


これであとはブループリントでHostSessionをしたときに、プレイヤーのレベルを入れてあげればOKです。


ここで格納したPlayerLevelの情報が、それぞれJoinしたプレイヤーに伝わるわけです。なのでレベル差が多すぎるときにはそのセッションには参加しない、みたいなことができるようになります。

また、CUSTOMSEARCHINT1という、INT格納できる専用の変数が用意されているんですが、これはうまく使えなくて半年浪費しました。使い方をご存知の方がいれば教えて下さい…。私は使うの諦めてCUSTOMの方を使っています。