メインコンテンツまでスキップ

サーバーでのマッチメイキングに OSS を使用する - 専用サーバーでのクイックマッチ - (Unreal Engine モジュール)

Last updated on February 4, 2026

注釈:本資料はAI技術を用いて翻訳されています。

専用サーバーを使用したマッチメイキングのフロー

以下の図を使用して、専用サーバー(DS)を使用したマッチメイキングの仕組みを理解してください:

ゲームクライアントのオンラインセッションをセットアップする

マッチメイキングは、ゲームセッションのマッチを見つけるプロセスです。ゲームセッションが見つかると、プレイヤーはゲームセッションに参加し、ゲームサーバーに移動します。必要に応じて、セッション入門モジュールを参照して、ゲームセッションの仕組みに関する基本知識を思い出してください。

このチュートリアルでは、MatchmakingDSOnlineSession_Starter という名前のオンラインセッションクラスを使用します。このオンラインセッションは、ゲームクライアントがマッチメイキングを実行するために使用されます。このクラスはリソースセクションで利用可能で、以下のファイルで構成されています:

  • ヘッダーファイル: /Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSOnlineSession_Starter.h
  • CPP ファイル: /Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSOnlineSession_Starter.cpp

このクラスには、チュートリアルに従うために使用できる事前定義された属性と関数がいくつかあります。提供されている内容を見てみましょう。

  • セッション入門モジュールと同様に、このクラスはセッションインターフェースを使用して、マッチメイキングを含むセッション関連の関数を実行します。

    FOnlineSessionV2AccelBytePtr UAccelByteWarsOnlineSessionBase::GetABSessionInt()
    {
    return StaticCastSharedPtr<FOnlineSessionV2AccelByte>(GetSessionInt());
    }
  • ヘッダーファイルには、前のチュートリアルで作成したマッチプール ID を定義する以下のマップがあります。ご覧のとおり、ID は Admin Portal で設定したものとまったく同じです。正しいマッチプールにマッチメイキングをリクエストできるように、名前は同じである必要があります。

    private:
    // ...
    const TMap<TPair<EGameModeType, EGameStyle>, FString> MatchPoolIds = {
    {{EGameModeType::FFA, EGameStyle::Zen}, "unreal-elimination-ds-ams"},
    {{EGameModeType::TDM, EGameStyle::Zen}, "unreal-teamdeathmatch-ds-ams"},
    {{EGameModeType::FFA, EGameStyle::Frenzy}, "unreal-frenzy-elimination-ds-ams"},
    {{EGameModeType::TDM, EGameStyle::Frenzy}, "unreal-frenzy-teamdeathmatch-ds-ams"}
    };
  • ヘッダーファイルには、マッチプール ID をゲームがサポートするゲームモードに変換する以下のマップもあります。ゲームサーバーは、このマップを使用して、ゲームモードに基づいてゲームプレイを構成します。

    public:
    // ...
    const TMap<FString, FString> TargetGameModeMap = {
    {"unreal-elimination-ds-ams", "ELIMINATION-DS"},
    {"unreal-teamdeathmatch-ds-ams", "TEAMDEATHMATCH-DS"},
    {"unreal-frenzy-elimination-ds-ams", "FRENZY-ELIMINATION-DS"},
    {"unreal-frenzy-teamdeathmatch-ds-ams", "FRENZY-TEAMDEATHMATCH-DS"}
    };
  • CPP ファイルには、プレイヤー情報をクエリするための関数がいくつかあります。このチュートリアルでは、これらの関数を直接使用しません。これらの関数は、ゲームサーバー側、特に UAccelByteWarsServerSubsystemBase::AuthenticatePlayer_OnRefreshSessionComplete() で呼び出されます。クエリされた情報はゲームステートに保存され、マッチロビーウィジェットがプレイヤー情報を表示するために使用されます。

    void UMatchmakingDSOnlineSession_Starter::DSQueryUserInfo(
    const TArray<FUniqueNetIdRef>& UserIds,
    const FOnDSQueryUsersInfoComplete& OnComplete)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("Called"))

    AccelByte::FServerApiClientPtr ServerApiClient = UTutorialModuleOnlineUtility::GetServerApiClient(this);
    if (!ServerApiClient)
    {
    UE_LOG_MATCHMAKINGDS(Log, TEXT("Cannot query user info. Server API Client is invalid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, OnComplete]()
    {
    OnDSQueryUserInfoComplete({}, OnComplete);
    }));
    return;
    }

    const TArray<const FUserDataResponse*> UserInfo;
    if (DSRetrieveUserInfoCache(UserIds, UserInfo))
    {
    UE_LOG_MATCHMAKINGDS(Log, TEXT("Cache found"))
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, OnComplete, UserInfo]()
    {
    OnComplete.ExecuteIfBound(true, UserInfo);
    }));
    }
    else
    {
    // Gather user IDs.
    FListUserDataRequest Request;
    for (const FUniqueNetIdRef& UserId : UserIds)
    {
    const FUniqueNetIdAccelByteUserPtr AbUniqueNetId = FUniqueNetIdAccelByteUser::TryCast(*UserId);
    const FString AbUserId = AbUniqueNetId->GetAccelByteId();
    if (!AbUserId.IsEmpty())
    {
    Request.UserIds.Add(AbUserId);
    }
    }

    ServerApiClient->ServerUser.ListUserByUserId(
    Request,
    THandler<FListUserDataResponse>::CreateWeakLambda(this, [OnComplete, this](FListUserDataResponse UserInfo)
    {
    OnDSQueryUserInfoComplete(UserInfo, OnComplete);
    }),
    FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32, const FString&)
    {
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, OnComplete]()
    {
    OnDSQueryUserInfoComplete({}, OnComplete);
    }));
    })
    );
    }
    }

マッチメイキングを開始する

このセクションでは、マッチメイキングを開始する関数を実装します。

  1. MatchmakingDSOnlineSession_Starter ヘッダーファイルを開き、マッチメイキングを開始する関数を宣言します。

    public:
    // ...
    virtual void StartMatchmaking(
    const APlayerController* PC,
    const FName& SessionName,
    const EGameModeNetworkType NetworkType,
    const EGameModeType GameModeType, const EGameStyle GameStyle) override;
  2. 同じファイルで、マッチメイキング開始プロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnStartMatchmakingComplete(
    FName SessionName,
    const FOnlineError& ErrorDetails,
    const FSessionMatchmakingResults& Results) override;
  3. マッチメイキング開始プロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、後でいくつかのイベントをバインドできます。特に、ユーザーインターフェース(UI)をマッチメイキング実装に接続するときに使用します。

    private:
    // ...
    FOnMatchmakingResponse OnStartMatchmakingCompleteDelegates;
    public:
    // ...

    virtual FOnMatchmakingResponse* GetOnStartMatchmakingCompleteDelegates() override
    {
    return &OnStartMatchmakingCompleteDelegates;
    }
  4. マッチメイキングを開始するには、残っているゲームセッションから退出する必要があります。それを処理するために、以下の関数を宣言します:

    private:
    // ...
    void OnLeaveSessionForReMatchmakingComplete(
    FName SessionName,
    bool bSucceeded,
    const int32 LocalUserNum,
    const EGameModeType GameModeType, const EGameStyle GameStyle);
    FDelegateHandle OnLeaveSessionForReMatchmakingCompleteDelegateHandle;
  5. 上記で宣言した関数を定義します。MatchmakingDSOnlineSession_Starter CPP ファイルを開き、StartMatchmaking() 関数から始めます。この関数では、どのようなマッチメイキングを行うかを定義するマッチメイキングハンドルを構築します。Admin Portal で構成したマッチプールに基づいてマッチメイキングを開始するように、マッチプールをマッチメイキングハンドルに設定します。マッチメイキングを開始する前に、プレイヤーがすでにゲームセッションに参加しているかどうかを確認します。参加している場合は、まずゲームセッションから退出してから、マッチメイキングを再試行します。これは OnLeaveSessionForReMatchmakingComplete() 関数で処理されます。マッチメイキングが正常に開始されると、OnStartMatchmakingComplete() 関数を呼び出します。

    void UMatchmakingDSOnlineSession_Starter::StartMatchmaking(
    const APlayerController* PC,
    const FName& SessionName,
    const EGameModeNetworkType NetworkType,
    const EGameModeType GameModeType, const EGameStyle GameStyle)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    // Abort if the session interface is invalid.
    if (!ensure(GetSessionInt()))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Session Interface is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
    }));
    return;
    }

    // If the player is already in a session, then leave session first.
    if (GetSession(SessionName))
    {
    UE_LOG_MATCHMAKINGDS(Log, TEXT("Already in session. Leaving session first."))
    if (OnLeaveSessionForReMatchmakingCompleteDelegateHandle.IsValid())
    {
    GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForReMatchmakingCompleteDelegateHandle);
    OnLeaveSessionForReMatchmakingCompleteDelegateHandle.Reset();
    }

    OnLeaveSessionForReMatchmakingCompleteDelegateHandle = GetOnLeaveSessionCompleteDelegates()->AddUObject(
    this,
    &ThisClass::OnLeaveSessionForReMatchmakingComplete,
    GetLocalUserNumFromPlayerController(PC),
    GameModeType,
    GameStyle);
    LeaveSession(SessionName);
    return;
    }

    const FUniqueNetIdPtr PlayerNetId = GetLocalPlayerUniqueNetId(PC);
    if (!ensure(PlayerNetId.IsValid()))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Player UniqueNetId is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
    }));
    return;
    }

    // Get match pool ID based on game mode type
    FString MatchPoolId = MatchPoolIds[{GameModeType, GameStyle}];
    const FString GameModeCode = TargetGameModeMap[MatchPoolId];


    // Override match pool ID if applicable.
    if (!UTutorialModuleOnlineUtility::GetMatchPoolDSOverride().IsEmpty())
    {
    MatchPoolId = UTutorialModuleOnlineUtility::GetMatchPoolDSOverride();
    }

    // Set up matchmaking search handle, it will be used to store session search results.
    TSharedRef<FOnlineSessionSearch> MatchmakingSearchHandle = MakeShared<FOnlineSessionSearch>();
    MatchmakingSearchHandle->QuerySettings.Set(SETTING_SESSION_MATCHPOOL, MatchPoolId, EOnlineComparisonOp::Equals);
    MatchmakingSearchHandle->QuerySettings.Set(GAMESETUP_GameModeCode, GameModeCode, EOnlineComparisonOp::Equals);

    // Check for DS version override.
    const FString OverriddenDSVersion = UTutorialModuleOnlineUtility::GetDedicatedServerVersionOverride();
    if (!OverriddenDSVersion.IsEmpty())
    {
    MatchmakingSearchHandle->QuerySettings.Set(SETTING_GAMESESSION_CLIENTVERSION, OverriddenDSVersion, EOnlineComparisonOp::Equals);
    }

    // Set local server name for matchmaking request if any.
    // This is useful if you want to try matchmaking using local dedicated server.
    FString ServerName;
    FParse::Value(FCommandLine::Get(), TEXT("-ServerName="), ServerName);
    if (!ServerName.IsEmpty())
    {
    UE_LOG_MATCHMAKINGDS(Log, TEXT("Requesting local server with name: %s"), *ServerName)
    MatchmakingSearchHandle->QuerySettings.Set(SETTING_GAMESESSION_SERVERNAME, ServerName, EOnlineComparisonOp::Equals);
    }

    #pragma region "Region Preferences"
    // Include region preferences into matchmaking setting.
    if (const UTutorialModuleDataAsset* ModuleDataAsset = UTutorialModuleUtility::GetTutorialModuleDataAsset(
    FPrimaryAssetId{ "TutorialModule:REGIONPREFERENCES" },
    this,
    true))
    {
    const UAccelByteWarsGameInstance* GameInstance = StaticCast<UAccelByteWarsGameInstance*>(GetGameInstance());
    ensure(GameInstance);

    // Get enabled regions
    TArray<FString> EnabledRegions = {};
    if (ModuleDataAsset->IsStarterModeActive())
    {
    // Starter mode is active, use the starter subsystem
    URegionPreferencesSubsystem_Starter* RegionPreferencesSubsystem = GameInstance->GetSubsystem<URegionPreferencesSubsystem_Starter>();
    if(RegionPreferencesSubsystem != nullptr)
    {
    EnabledRegions = RegionPreferencesSubsystem->GetEnabledRegion();
    }
    }
    else
    {
    // Starter mode is not active, use the non starter subsystem.
    URegionPreferencesSubsystem* RegionPreferencesSubsystem = GameInstance->GetSubsystem<URegionPreferencesSubsystem>();
    if(RegionPreferencesSubsystem != nullptr)
    {
    EnabledRegions = RegionPreferencesSubsystem->GetEnabledRegion();
    }
    }

    // Set enabled regions
    if(!EnabledRegions.IsEmpty())
    {
    FOnlineSearchSettingsAccelByte::Set(MatchmakingSearchHandle->QuerySettings, SETTING_GAMESESSION_REQUESTEDREGIONS, EnabledRegions, EOnlineComparisonOp::In);
    }
    }
    #pragma endregion

    if (!GetSessionInt()->StartMatchmaking(
    USER_ID_TO_MATCHMAKING_USER_ARRAY(PlayerNetId.ToSharedRef()),
    SessionName,
    FOnlineSessionSettings(),
    MatchmakingSearchHandle,
    FOnStartMatchmakingComplete::CreateUObject(this, &ThisClass::OnStartMatchmakingComplete)))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Failed executing"))
    // Failed to start matchmaking.
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
    }));
    }
    }
    備考

    リージョンプリファレンス部分は、マッチメイキング機能のみを実装する場合は必要ありません。この機能を独自のゲームに統合するためのサンプルとして使用している場合は、この部分を削除しても構いません。

  6. OnStartMatchmakingComplete() 関数を定義します。この関数は、作成したデリゲートをトリガーして、開始されたマッチメイキングが成功したかどうかを通知します。

    void UMatchmakingDSOnlineSession_Starter::OnStartMatchmakingComplete(
    FName SessionName,
    const FOnlineError& ErrorDetails,
    const FSessionMatchmakingResults& Results)
    {
    UE_LOG_MATCHMAKINGDS(
    Log,
    TEXT("succeeded: %s | error: (%s) %s"),
    *FString(ErrorDetails.bSucceeded ? "TRUE": "FALSE"),
    *ErrorDetails.ErrorCode, *ErrorDetails.ErrorMessage.ToString())

    OnStartMatchmakingCompleteDelegates.Broadcast(SessionName, ErrorDetails.bSucceeded);
    }
  7. OnLeaveSessionForReMatchmakingComplete() 関数を定義します。この関数は、ゲームセッションから退出した後に呼び出されます。その後、マッチメイキングを再試行することが有効かどうかを確認します。

    void UMatchmakingDSOnlineSession_Starter::OnLeaveSessionForReMatchmakingComplete(
    FName SessionName,
    bool bSucceeded,
    const int32 LocalUserNum,
    const EGameModeType GameModeType, const EGameStyle GameStyle)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForReMatchmakingCompleteDelegateHandle);

    if (bSucceeded)
    {
    // Retry matchmaking.
    const APlayerController* PC = GetPlayerControllerByLocalUserNum(LocalUserNum);
    if (!ensure(PC))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("PlayerController is null."));
    OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
    return;
    }

    StartMatchmaking(PC, SessionName, EGameModeNetworkType::DS, GameModeType, GameStyle);
    }
    else
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Is not a game session."));
    OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
    }
    }
  8. マッチメイキングが完了したときの処理が必要です。MatchmakingDSOnlineSession_Starter ヘッダーファイルを開き、以下の関数を宣言します:

    protected:
    // ...
    virtual void OnMatchmakingComplete(FName SessionName, bool bSucceeded) override;
  9. マッチメイキングプロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、後でいくつかのイベントをバインドできます。特に、UI をマッチメイキング実装に接続するときに使用します。

    private:
    // ...
    FOnMatchmakingResponse OnMatchmakingCompleteDelegates;
    public:
    // ...
    virtual FOnMatchmakingResponse* GetOnMatchmakingCompleteDelegates() override
    {
    return &OnMatchmakingCompleteDelegates;
    }
  10. Open the MatchmakingDSOnlineSession_Starter CPP file and define the OnMatchmakingComplete() function. This function will check if a game session was found after the matchmaking completes. If found, it joins the game session. It also invokes the delegate you created earlier to inform the game that the matchmaking process has completed.

    void UMatchmakingDSOnlineSession_Starter::OnMatchmakingComplete(FName SessionName, bool bSucceeded)
    {
    UE_LOG_MATCHMAKINGDS(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))

    const TSharedPtr<FOnlineSessionSearchAccelByte> CurrentMatchmakingSearchHandle = GetABSessionInt()->GetCurrentMatchmakingSearchHandle();
    if (!bSucceeded ||
    !ensure(CurrentMatchmakingSearchHandle.IsValid()) /*This might happen when matchmaking finishes right as it’s about to be canceled.*/ ||
    !ensure(CurrentMatchmakingSearchHandle->SearchResults.IsValidIndex(0)) ||
    !ensure(CurrentMatchmakingSearchHandle->GetSearchingPlayerId().IsValid()))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("There is no match result returned."));
    OnMatchmakingCompleteDelegates.Broadcast(SessionName, false);
    return;
    }

    OnMatchmakingCompleteDelegates.Broadcast(SessionName, bSucceeded);
    }
  11. You need to bind the function above so it will be called when the matchmaking process is completes. You can do this by adding the highlighted code below to the RegisterOnlineDelegates() function, which is the first function to be called when the online session initializes.

    void UMatchmakingDSOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    GetSessionInt()->OnMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnMatchmakingComplete);
    // ...
    }
  12. When the online session is deinitialized, you need to unbind to stop listening to the event. You can do this by adding the highlighted code below in the predefined ClearOnlineDelegates() function, which is the first function to be called when the online session is deinitialized.

    void UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    GetSessionInt()->OnMatchmakingCompleteDelegates.RemoveAll(this);
    // ...
    }

マッチメイキングをキャンセルする

このセクションでは、マッチメイキングをキャンセルする関数を実装します。

  1. MatchmakingDSOnlineSession_Starter ヘッダーファイルを開き、マッチメイキングをキャンセルする関数を宣言します。

    public:
    // ...
    virtual void CancelMatchmaking(APlayerController* PC, const FName& SessionName) override;
  2. 同じファイルで、マッチメイキングキャンセルプロセスが完了したときに呼び出されるコールバック関数を宣言します。

    protected:
    // ...
    virtual void OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded) override;
  3. マッチメイキングキャンセルプロセスが完了したときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、後でいくつかのイベントをバインドできます。特に、UI をマッチメイキング実装に接続するときに使用します。

    private:
    // ...
    FOnMatchmakingResponse OnCancelMatchmakingCompleteDelegates;
    public:
    // ...
    virtual FOnMatchmakingResponse* GetOnCancelMatchmakingCompleteDelegates() override
    {
    return &OnCancelMatchmakingCompleteDelegates;
    }
  4. MatchmakingDSOnlineSession_Starter CPP ファイルを開き、CancelMatchmaking() 関数を定義します。この関数は、アクションを実行する前に、マッチメイキングをキャンセルすることが有効かどうかを確認します。プロセスが完了すると、OnCancelMatchmakingComplete() 関数を呼び出します。

    void UMatchmakingDSOnlineSession_Starter::CancelMatchmaking(APlayerController* PC, const FName& SessionName)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    // Abort if the session interface is invalid.
    if (!ensure(GetABSessionInt()))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Session Interface is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnCancelMatchmakingComplete(SessionName, false);
    }));
    return;
    }

    if (!(GetABSessionInt()->GetCurrentMatchmakingSearchHandle().IsValid() &&
    GetABSessionInt()->GetCurrentMatchmakingSearchHandle()->GetSearchingPlayerId().IsValid()))
    {
    // This can happen if the cancel was called just as match was found.
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Searching player ID is not valid."));
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnCancelMatchmakingComplete(SessionName, false);
    }));
    return;
    }

    if (!GetSessionInt()->CancelMatchmaking(
    *GetABSessionInt()->GetCurrentMatchmakingSearchHandle()->GetSearchingPlayerId(),
    GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Failed executing"))
    // Failed to start matchmaking.
    ExecuteNextTick(FTimerDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnCancelMatchmakingComplete(SessionName, false);
    }));
    }
    }
  5. OnCancelMatchmakingComplete() 関数を定義します。この関数は、作成したデリゲートをトリガーして、開始されたマッチメイキングが成功したかどうかを通知します。

    void UMatchmakingDSOnlineSession_Starter::OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded)
    {
    UE_LOG_MATCHMAKINGDS(Log, TEXT("succeeded: %s"), *FString(bSucceeded ? "TRUE": "FALSE"))

    OnCancelMatchmakingCompleteDelegates.Broadcast(SessionName, bSucceeded);
    }
  6. 上記の関数をバインドして、マッチメイキングキャンセルプロセスが完了したときに呼び出されるようにします。これは、オンラインセッションが初期化されるときに最初に呼び出される RegisterOnlineDelegates() 関数に、以下のコードを追加することで実行できます。

    void UMatchmakingDSOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    GetSessionInt()->OnCancelMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnCancelMatchmakingComplete);
    // ...
    }
  7. オンラインセッションが非初期化されるときは、イベントのリスニングを停止するためにバインドを解除する必要があります。これは、オンラインセッションが非初期化されるときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に、以下のコードを追加することで実行できます。

    void UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    GetSessionInt()->OnCancelMatchmakingCompleteDelegates.RemoveAll(this);
    // ...
    }

専用サーバーを受信したときの処理

マッチメイキングが終了し、ゲームクライアントがゲームセッションに参加すると、ゲームクライアントは移動先の専用サーバーを受信するのを待ちます。このセクションでは、それが発生したときの処理方法を学習します。

  1. ゲームクライアントを専用サーバーに移動させる関数を作成します。MatchmakingDSOnlineSession_Starter ヘッダーファイルを開き、以下の関数を宣言します:

    public:
    // ...
    virtual bool TravelToSession(const FName SessionName) override;
  2. MatchmakingDSOnlineSession_Starter CPP ファイルを開き、上記の関数を定義します。この関数は、ゲームクライアントを専用サーバーに移動させる前にいくつかの検証を実行します。有効な場合は、サーバーアドレスを使用して専用サーバーに移動します。

    bool UMatchmakingDSOnlineSession_Starter::TravelToSession(const FName SessionName)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    if (GetSessionType(SessionName) != EAccelByteV2SessionType::GameSession)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Not a game session"));
    return false;
    }

    // Get session info
    const FNamedOnlineSession* Session = GetSession(SessionName);
    if (!Session)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("The session is invalid"));
    return false;
    }

    const TSharedPtr<FOnlineSessionInfo> SessionInfo = Session->SessionInfo;
    if (!SessionInfo.IsValid())
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("The session info is invalid"));
    return false;
    }

    const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
    if (!AbSessionInfo.IsValid())
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("The session info is not FOnlineSessionInfoAccelByteV2"));
    return false;
    }

    // get player controller of the local owner of the user
    APlayerController* PlayerController = GetPlayerControllerByUniqueNetId(Session->LocalOwnerId);

    // if nullptr, treat as failed
    if (!PlayerController)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Can't find player controller with the session's local owner's unique ID"));
    return false;
    }

    AAccelByteWarsPlayerController* AbPlayerController = Cast<AAccelByteWarsPlayerController>(PlayerController);
    if (!AbPlayerController)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Player controller is not (derived from) AAccelByteWarsPlayerController"));
    return false;
    }

    // Make sure this is not a P2P session
    if (GetABSessionInt()->IsPlayerP2PHost(GetLocalPlayerUniqueNetId(PlayerController).ToSharedRef().Get(), SessionName))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("The session is a P2P session"));
    return false;
    }

    FString ServerAddress = "";
    GetABSessionInt()->GetResolvedConnectString(SessionName, ServerAddress);

    if (ServerAddress.IsEmpty())
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Can't find session's server address"));
    return false;
    }

    if (!bIsInSessionServer)
    {
    AbPlayerController->DelayedClientTravel(ServerAddress, TRAVEL_Absolute);
    bIsInSessionServer = true;
    }
    else
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Already in session's server"));
    }

    return true;
    }
  3. ゲームクライアントがサーバー更新を受信したときにリスニングする関数を作成します。MatchmakingDSOnlineSession_Starter ヘッダーファイルを開き、以下の関数を宣言します:

    protected:
    // ...
    virtual void OnSessionServerUpdateReceived(FName SessionName) override;
  4. 専用サーバーが受信されたときに呼び出されるデリゲートを宣言します。このデリゲートを使用して、後でいくつかのイベントをバインドできます。特に、UI をマッチメイキング実装に接続するときに使用します。

    private:
    // ...
    FOnServerSessionUpdateReceived OnSessionServerUpdateReceivedDelegates;
    public:
    // ...
    virtual FOnServerSessionUpdateReceived* GetOnSessionServerUpdateReceivedDelegates() override
    {
    return &OnSessionServerUpdateReceivedDelegates;
    }
  5. MatchmakingDSOnlineSession_Starter CPP ファイルを開き、OnSessionServerUpdateReceived() 関数を定義します。この関数は、専用サーバーに移動することが有効かどうかを確認します。また、先ほど作成したデリゲートを呼び出して、サーバー更新が受信されたことを通知します。

    void UMatchmakingDSOnlineSession_Starter::OnSessionServerUpdateReceived(FName SessionName)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    if (bLeavingSession)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("called but leave session is currently running. Canceling attempt to travel to server"))
    OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, FOnlineError(true), false);
    return;
    }

    const bool bHasClientTravelTriggered = TravelToSession(SessionName);
    OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, FOnlineError(true), bHasClientTravelTriggered);
    }
  6. 場合によっては、バックエンドがゲームクライアントに専用サーバーを提供できないことがあります。それが発生したときに処理する関数を作成するために、MatchmakingDSOnlineSession_Starter ヘッダーファイルを開き、以下の関数を宣言します:

    protected:
    // ...
    virtual void OnSessionServerErrorReceived(FName SessionName, const FString& Message) override;
  7. MatchmakingDSOnlineSession_Starter CPP ファイルを開き、上記の関数を定義します。この関数は、先ほど作成したセッションサーバー更新デリゲートを呼び出しますが、失敗としてマークします。

    void UMatchmakingDSOnlineSession_Starter::OnSessionServerErrorReceived(FName SessionName, const FString& Message)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    FOnlineError Error;
    Error.bSucceeded = false;
    Error.ErrorMessage = FText::FromString(Message);

    OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, Error, false);
    }
  8. 上記の関数をバインドして、サーバー更新が受信されたときに呼び出されるようにします。これは、オンラインセッションが初期化されるときに最初に呼び出される RegisterOnlineDelegates() 関数に、以下のコードを追加することで実行できます。

    void UMatchmakingDSOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    GetABSessionInt()->OnSessionServerUpdateDelegates.AddUObject(this, &ThisClass::OnSessionServerUpdateReceived);
    GetABSessionInt()->OnSessionServerErrorDelegates.AddUObject(this, &ThisClass::OnSessionServerErrorReceived);
    // ...
    }
  9. オンラインセッションが非初期化されるときは、イベントのリスニングを停止するためにバインドを解除する必要があります。これは、オンラインセッションが非初期化されるときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に、以下のコードを追加することで実行できます。

    void UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    GetABSessionInt()->OnSessionServerUpdateDelegates.RemoveAll(this);
    GetABSessionInt()->OnSessionServerErrorDelegates.RemoveAll(this);
    // ...
    }

バックフィルを処理する

バックフィルは、まだ満員でないゲームサーバーにプレイヤーが参加できるようにするフローです。このセクションでは、バックフィルを処理する関数を実装します。

注記

このチュートリアルでは、バックフィル提案が受信されたときに、常に提案を受け入れます。提案を拒否するには、GetABSessionInt()->RejectBackfillProposal() 関数を使用できます。提案を受け入れる場合と拒否する場合のフローは似ています。

  1. MatchmakingDSOnlineSession_Starter ヘッダーファイルを開き、以下の関数を宣言します:

    protected:
    // ...
    virtual void OnBackfillProposalReceived(FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal) override;
  2. バックフィル受け入れプロセスが完了したときに呼び出されるデリゲートを宣言します。

    private:
    // ...
    FOnMatchmakingAcceptBackfillProposalComplete OnAcceptBackfillProposalCompleteDelegates;
    public:
    // ...
    virtual FOnMatchmakingAcceptBackfillProposalComplete* GetOnAcceptBackfillProposalCompleteDelegates() override
    {
    return &OnAcceptBackfillProposalCompleteDelegates;
    }
  3. MatchmakingDSOnlineSession_Starter CPP ファイルを開き、OnBackfillProposalReceived() 関数を定義します。この関数は、受信したバックフィルを受け入れて、プレイヤーがまだ満員でないゲームサーバーに参加できるようにします。また、先ほど作成したデリゲートを呼び出して、バックフィル受け入れプロセスが完了したことをゲームに通知します。

    void UMatchmakingDSOnlineSession_Starter::OnBackfillProposalReceived(
    FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    // Abort if the session interface is invalid.
    if (!ensure(GetABSessionInt().IsValid()))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Session Interface is not valid."));
    return;
    }

    // Accept backfill proposal.
    GetABSessionInt()->AcceptBackfillProposal(
    GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession),
    Proposal,
    false,
    FOnAcceptBackfillProposalComplete::CreateWeakLambda(this, [this](bool bSucceeded)
    {
    UE_LOG_MATCHMAKINGDS(Log, TEXT("succeeded: %s To accept backfill."), *FString(bSucceeded ? "TRUE": "FALSE"));
    OnAcceptBackfillProposalCompleteDelegates.Broadcast(bSucceeded);
    }));
    }
  4. 上記の関数をバインドして、バックフィル提案が受信されたときに呼び出されるようにします。これは、オンラインセッションが初期化されるときに最初に呼び出される RegisterOnlineDelegates() 関数に、以下のコードを追加することで実行できます。

    void UMatchmakingDSOnlineSession_Starter::RegisterOnlineDelegates()
    {
    // ...
    GetABSessionInt()->OnBackfillProposalReceivedDelegates.AddUObject(this, &ThisClass::OnBackfillProposalReceived);
    // ...
    }
  5. オンラインセッションが非初期化されるときは、イベントのリスニングを停止するためにバインドを解除する必要があります。これは、オンラインセッションが非初期化されるときに最初に呼び出される事前定義された ClearOnlineDelegates() 関数に、以下のコードを追加することで実行できます。

    void UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
    {
    // ...
    GetABSessionInt()->OnBackfillProposalReceivedDelegates.RemoveAll(this);
    // ...
    }

専用サーバーのオンラインサブシステムをセットアップする

このチュートリアルでは、MatchmakingDSServerSubsystem_Starter という名前のゲームインスタンスサブシステムクラスを使用します。このクラスは、専用サーバーがマッチメイキングによって生成されたゲームセッションを処理するために使用されます。このクラスはリソースセクションで利用可能で、以下のファイルで構成されています:

  • ヘッダーファイル: /Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSServerSubsystem_Starter.h
  • CPP ファイル: /Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSServerSubsystem_Starter.cpp

専用サーバーがセッションを受信したときの処理

マッチメイキングが終了すると、バックエンドは専用サーバーを作成して要求し、ゲームセッションを割り当てます。このセクションでは、サーバーが受信したセッションを処理できるように関数を実装します。

  1. MatchmakingDSServerSubsystem_Starter ヘッダーファイルを開き、以下の関数を作成します:

    protected:
    virtual void OnServerSessionReceived(FName SessionName) override;
  2. MatchmakingDSServerSubsystem_Starter CPP ファイルを開き、上記の関数を定義します。専用サーバーがバックエンドからゲームセッションを受信すると、この関数はゲームセッションによって提供される情報に基づいて正しいゲームモードを割り当てます。これにより、サーバーは接続されたプレイヤーに正しいゲームプレイを提供できます。この実装のほとんどは Byte Wars に固有のものですが、この関数の主なポイントは、強調表示された行で行われるバックエンドのセッション情報からデータを取得することです。これを、セッション情報から特定のデータを取得する方法のリファレンスとして使用できます。

    void UMatchmakingDSServerSubsystem_Starter::OnServerSessionReceived(FName SessionName)
    {
    Super::OnServerSessionReceived(SessionName);
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    #pragma region "Assign game mode based on SessionTemplateName from backend"
    // Get GameMode
    const UWorld* World = GetWorld();
    if (!World)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("World is invalid"));
    return;
    }

    AGameStateBase* GameState = World->GetGameState();
    if (!GameState)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Game State is invalid"));
    return;
    }

    AAccelByteWarsGameState* AbGameState = Cast<AAccelByteWarsGameState>(GameState);
    if (!AbGameState)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Game State is not derived from AAccelByteWarsGameState"));
    return;
    }

    // Get game session
    if (MatchmakingOnlineSession->GetSessionType(SessionName) != EAccelByteV2SessionType::GameSession)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Is not a game session"));
    return;
    }

    const FNamedOnlineSession* Session = MatchmakingOnlineSession->GetSession(SessionName);
    if (!Session)
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("The session is invalid"));
    return;
    }

    FString RequestedGameModeCode = TEXT(""), SessionTemplateName = TEXT("");
    Session->SessionSettings.Get(GAMESETUP_GameModeCode, RequestedGameModeCode);
    Session->SessionSettings.Get(SETTING_SESSION_MATCHPOOL, SessionTemplateName);
    if (!RequestedGameModeCode.IsEmpty())
    {
    AbGameState->AssignGameMode(RequestedGameModeCode);
    }
    else if (!SessionTemplateName.IsEmpty())
    {
    AbGameState->AssignGameMode(MatchmakingOnlineSession->TargetGameModeMap[SessionTemplateName]);
    }
    #pragma endregion

    // Query all currently registered users' info
    AuthenticatePlayer_OnRefreshSessionComplete(true);
    }
  3. 上記の関数をバインドして、専用サーバーがバックエンドからゲームセッションを受信したときに呼び出されるようにする必要があります。これは、サブシステムが初期化されるときに最初に呼び出される Initialize() 関数に、以下のコードを追加することで実行できます。

    void UMatchmakingDSServerSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    Super::Initialize(Collection);

    UOnlineSession* BaseOnlineSession = GetWorld()->GetGameInstance()->GetOnlineSession();
    if (!ensure(BaseOnlineSession))
    {
    return;
    }
    MatchmakingOnlineSession = Cast<UMatchmakingDSOnlineSession_Starter>(BaseOnlineSession);

    GetABSessionInt()->OnServerReceivedSessionDelegates.AddUObject(this, &ThisClass::OnServerSessionReceived);
    }
  4. サブシステムが非初期化されるときに関数のバインドを解除します。これは、Deinitialize() 関数に以下のコードを追加することで実行できます。

    void UMatchmakingDSServerSubsystem_Starter::Deinitialize()
    {
    Super::Deinitialize();

    GetABSessionInt()->OnServerReceivedSessionDelegates.RemoveAll(this);
    }
  5. プロジェクトを再度ビルドし、エラーがないことを確認してください。

サブシステム内の追加機能

前のセクションでは、サーバーが FNamedOnlineSession* Session = MatchmakingOnlineSession->GetSession(SessionName) を介してセッション情報を取得する方法を学習しました。このセクションでは、それを拡張し、セッションに関して役立つ可能性のある追加情報を提供します。

チーム割り当て

AGS マッチメイキングは、指定されたマッチルールセットに基づいてプレイヤーをチームに振り分けることもできます。このため、サーバーはチーム情報を取得する方法が必要です。これを行うコードは次のとおりです:

// ...
const FNamedOnlineSession* NamedOnlineSession = GameSessionOnlineSession->GetSession(
GameSessionOnlineSession->GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession));
if (!NamedOnlineSession)
{
return;
}

const TSharedPtr<FOnlineSessionInfo> SessionInfo = NamedOnlineSession->SessionInfo;
if (!SessionInfo.IsValid())
{
return;
}

const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
if (!AbSessionInfo.IsValid())
{
return;
}
TArray<FAccelByteModelsV2GameSessionTeam> Teams = AbSessionInfo->GetTeamAssignments();

さらに、FAccelByteModelsV2GameSessionTeam::UserIDs を使用して、ユーザーがセッションの一部であるかどうかを確認できます。ユーザー ID は文字列として保存されていることに注意してください。このコードを使用して、FUniqueNetIdRepl から AB ユーザー ID を取得できます:

#include "OnlineSubsystemAccelByteTypes.h"
const FUniqueNetIdAccelByteUserPtr AbUniqueNetId = FUniqueNetIdAccelByteUser::TryCast(*UniqueNetIdRepl);
const FString AbUserId = AbUniqueNetId->GetAccelByteId();

リソース