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

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

Last updated on May 30, 2024

Matchmaking with dedicated server flow

Take some time to understand how matchmaking with a dedicated server (DS) works using the following diagram:

Set up game client online session

Matchmaking is the process of finding a match for a game session. Once the game session is found, the player will join the game session and travel to the game server. If necessary, remind yourself of the basic knowledge of how game sessions work by referring back to the Introduction to Session module.

In this tutorial, you will be working with an online session class named MatchmakingDSOnlineSession_Starter. This online session is used by the game client to perform matchmaking. This class is available in the Resources section and consists of the following files:

  • Header file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSOnlineSession_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSOnlineSession_Starter.cpp

This class has several predefined attributes and functions you can use to follow the tutorial. Take a look at what it provides.

  • Similar to the Introduction to Session module, this class uses session interfaces to perform session-related functions, including matchmaking.

    IOnlineSessionPtr UOnlineSessionClient::GetSessionInt()
    {
    UWorld* World = GetWorld();
    if (World == nullptr)
    {
    UE_LOG_ONLINE(Warning, TEXT("UOnlineSessionClient::GetSessionInt: Called with NULL world."));
    return nullptr;
    }

    return Online::GetSessionInterface(World);
    }

    FOnlineSessionV2AccelBytePtr UAccelByteWarsOnlineSessionBase::GetABSessionInt()
    {
    return StaticCastSharedPtr<FOnlineSessionV2AccelByte>(GetSessionInt());
    }
  • The Header file has the map below that defines the Match Pool IDs that you created in the previous tutorial. As you can see, the IDs are the exact same as the ones you set up in the Admin Portal. The name must be the same so you can request matchmaking to the correct Match Pool.

    const TMap<EGameModeType, FString> MatchPoolIds = {
    {EGameModeType::FFA, "unreal-elimination-ds-ams"},
    {EGameModeType::TDM, "unreal-teamdeathmatch-ds-ams"}
    };
  • The Header file also has the map below that translates Match Pool IDs to game modes supported by the game. The game server uses this map to configure the gameplay based on the game mode.

    const TMap<FString, FString> TargetGameModeMap = {
    {"unreal-elimination-ds-ams", "ELIMINATION-DS"},
    {"unreal-teamdeathmatch-ds-ams", "TEAMDEATHMATCH-DS"}
    };
  • In the CPP file, there are several functions to query player information. You will not use these functions directly in this tutorial. These functions are called on the game server side, particularly in the UAccelByteWarsServerSubsystemBase::AuthenticatePlayer_OnRefreshSessionComplete(). The queried information is saved in the game state which is then used by the Match Lobby widget to display the player information.

    void UMatchmakingDSOnlineSession_Starter::QueryUserInfo(
    const int32 LocalUserNum,
    const TArray<FUniqueNetIdRef>& UserIds,
    const FOnQueryUsersInfoComplete& OnComplete)
    {
    UE_LOG_MATCHMAKINGDS(Verbose, TEXT("called"))

    // safety
    if (!GetUserInt())
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("User interface null"))
    ExecuteNextTick(FSimpleDelegate::CreateWeakLambda(this, [this, OnComplete]()
    {
    OnComplete.ExecuteIfBound(false, {});
    }));
    return;
    }

    TArray<FUserOnlineAccountAccelByte*> UserInfo;
    if (RetrieveUserInfoCache(UserIds, UserInfo))
    {
    UE_LOG_MATCHMAKINGDS(Log, TEXT("Cache found"))
    ExecuteNextTick(FSimpleDelegate::CreateWeakLambda(this, [this, UserInfo, OnComplete]()
    {
    OnComplete.ExecuteIfBound(true, UserInfo);
    }));
    }
    // Some data does not exist in cache, query everything
    else
    {
    // Bind delegate
    if (OnQueryUserInfoCompleteDelegateHandle.IsValid())
    {
    GetUserInt()->OnQueryUserInfoCompleteDelegates->Remove(OnQueryUserInfoCompleteDelegateHandle);
    OnQueryUserInfoCompleteDelegateHandle.Reset();
    }
    OnQueryUserInfoCompleteDelegateHandle = GetUserInt()->OnQueryUserInfoCompleteDelegates->AddWeakLambda(
    this, [OnComplete, this](
    int32 LocalUserNum,
    bool bSucceeded,
    const TArray<FUniqueNetIdRef>& UserIds,
    const FString& ErrorMessage)
    {
    OnQueryUserInfoComplete(LocalUserNum, bSucceeded, UserIds, ErrorMessage, OnComplete);
    });

    if (!GetUserInt()->QueryUserInfo(LocalUserNum, UserIds))
    {
    OnQueryUserInfoComplete(LocalUserNum, false, UserIds, "", OnComplete);
    }
    }
    }

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

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

    AccelByte::FRegistry::User.BulkGetUserInfo(
    AbUserIds,
    THandler<FListBulkUserInfo>::CreateWeakLambda(this, [OnComplete, this](FListBulkUserInfo UserInfo)
    {
    OnDSQueryUserInfoComplete(UserInfo, OnComplete);
    }),
    FErrorHandler::CreateWeakLambda(this, [this, OnComplete](int32, const FString&)
    {
    ExecuteNextTick(FSimpleDelegate::CreateWeakLambda(this, [this, OnComplete]()
    {
    OnDSQueryUserInfoComplete(FListBulkUserInfo(), OnComplete);
    }));
    })
    );
    }
    }

Start matchmaking

In this section, you will implement functions to start matchmaking.

  1. Open the MatchmakingDSOnlineSession_Starter Header file and declare a function to start matchmaking.

    public:
    virtual void StartMatchmaking(
    const APlayerController* PC,
    const FName& SessionName,
    const EGameModeNetworkType NetworkType,
    const EGameModeType GameModeType) override;
  2. Still in the same file, declare a callback function that will be called when the start matchmaking process completes.

    protected:
    virtual void OnStartMatchmakingComplete(
    FName SessionName,
    const FOnlineError& ErrorDetails,
    const FSessionMatchmakingResults& Results) override;
  3. Declare a delegate that will be called when the start matchmaking process completes. You can use this delegate to bind some events later, especially when connecting the user interface (UI) with the matchmaking implementation.

    public:
    virtual FOnMatchmakingResponse* GetOnStartMatchmakingCompleteDelegates() override
    {
    return &OnStartMatchmakingCompleteDelegates;
    }

    private:
    FOnMatchmakingResponse OnStartMatchmakingCompleteDelegates;
  4. To start matchmaking, you will need to leave any leftover game sessions. Declare the following function to handle that:

    private:
    void OnLeaveSessionForReMatchmakingComplete(
    FName SessionName,
    bool bSucceeded,
    const int32 LocalUserNum,
    const EGameModeType GameModeType);

    FDelegateHandle OnLeaveSessionForReMatchmakingCompleteDelegateHandle;
  5. Define the functions you declared above. Open the MatchmakingDSOnlineSession_Starter CPP file and begin with the StartMatchmaking() function. In this function, you construct the matchmaking handle to define what kind of matchmaking you want. It sets the Match Pool to the matchmaking handle so it will start the matchmaking based on the Match Pool you configured in the Admin Portal. Before it begins matchmaking, it first checks whether the player is already in the game session or not. If yes, it leaves the game session first and then retries matchmaking, which will be handled by the OnLeaveSessionForReMatchmakingComplete() function. When the matchmaking is successfully started, it then calls the OnStartMatchmakingComplete() function.

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

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

    // If the player is already in a session, then leave the 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);
    LeaveSession(SessionName);
    return;
    }

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

    // Get match pool id based on game mode type
    FString MatchPoolId = MatchPoolIds[GameModeType];

    // if not using AMS, remove suffix -ams (internal purpose)
    if(!UTutorialModuleOnlineUtility::GetIsServerUseAMS())
    {
    MatchPoolId = MatchPoolId.Replace(TEXT("-ams"), TEXT(""));
    }

    // Setup 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);

    // 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);
    }

    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(FSimpleDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
    }));
    }
    }
  6. Define the OnStartMatchmakingComplete() function. This function triggers the delegate you created to inform whether the matchmaking that was started is successful or not.

    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. Define the OnLeaveSessionForReMatchmakingComplete() function. This function will be called after leaving the game session. It then checks whether it is valid to retry matchmaking or not.

    void UMatchmakingDSOnlineSession_Starter::OnLeaveSessionForReMatchmakingComplete(
    FName SessionName,
    bool bSucceeded,
    const int32 LocalUserNum,
    const EGameModeType GameModeType)
    {
    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);
    }
    else
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Is not a game session."));
    OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
    }
    }
  8. You need to handle when the matchmaking completes. Open the MatchmakingDSOnlineSession_Starter Header file and declare the following function:

    protected:
    virtual void OnMatchmakingComplete(FName SessionName, bool bSucceeded) override;
  9. Declare a delegate that will be called when the matchmaking process completes. You can use this delegate to bind some events later, especially when connecting the UI with the matchmaking implementation.

    public:
    virtual FOnMatchmakingResponse* GetOnMatchmakingCompleteDelegates() override
    {
    return &OnMatchmakingCompleteDelegates;
    }

    private:
    FOnMatchmakingResponse 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()->CurrentMatchmakingSearchHandle;
    if (!bSucceeded ||
    !ensure(CurrentMatchmakingSearchHandle.IsValid()) /*This might happen when the MM finished just as we are about to cancel it*/ ||
    !ensure(CurrentMatchmakingSearchHandle->SearchResults.IsValidIndex(0)) ||
    !ensure(CurrentMatchmakingSearchHandle->SearchingPlayerId.IsValid()))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("There is no match result returned."));
    OnMatchmakingCompleteDelegates.Broadcast(SessionName, false);
    return;
    }

    OnMatchmakingCompleteDelegates.Broadcast(SessionName, bSucceeded);

    // Get searching player
    const int32 LocalUserNum =
    GetLocalUserNumFromPlayerController(GetPlayerControllerByUniqueNetId(CurrentMatchmakingSearchHandle->SearchingPlayerId));

    // Join the first session from matchmaking result.
    JoinSession(
    LocalUserNum,
    GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession),
    CurrentMatchmakingSearchHandle->SearchResults[0]);
    }
  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 code below to the RegisterOnlineDelegates() function, which is the first function to be called when the online session initializes.

    void UMatchmakingDSOnlineSession_Starter::RegisterOnlineDelegates()
    {
    Super::RegisterOnlineDelegates();

    // Bind matchmaking delegates.
    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 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()
    {
    Super::ClearOnlineDelegates();

    // Unbind matchmaking delegates.
    GetSessionInt()->OnMatchmakingCompleteDelegates.RemoveAll(this);
    }

Cancel matchmaking

In this section, you will implement functions to cancel matchmaking.

  1. Open the MatchmakingDSOnlineSession_Starter Header file and declare a function to cancel matchmaking.

    public:
    virtual void CancelMatchmaking(APlayerController* PC, const FName& SessionName) override;
  2. Still in the same file, declare a callback function that will be called when the cancel matchmaking process completes.

    protected:
    virtual void OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded) override;
  3. Declare a delegate that will be called when the cancel matchmaking process is completes. You can use this delegate to bind some events later, especially when connecting the UI with the matchmaking implementation.

    public:
    virtual FOnMatchmakingResponse* GetOnCancelMatchmakingCompleteDelegates() override
    {
    return &OnCancelMatchmakingCompleteDelegates;
    }

    private:
    FOnMatchmakingResponse OnCancelMatchmakingCompleteDelegates;
  4. Open the MatchmakingDSOnlineSession_Starter CPP file and define the CancelMatchmaking() function. This function checks whether it is valid to cancel matchmaking or not before executing the action. When the process is complete, it then calls the OnCancelMatchmakingComplete() function.

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

    // safety
    if (!ensure(GetSessionInt()))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Session Interface is not valid."));
    ExecuteNextTick(FSimpleDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnCancelMatchmakingComplete(SessionName, false);
    }));
    return;
    }

    if (!ensure(GetABSessionInt()->CurrentMatchmakingSearchHandle.IsValid() &&
    GetABSessionInt()->CurrentMatchmakingSearchHandle->SearchingPlayerId.IsValid()))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Searching player ID is not valid."));
    ExecuteNextTick(FSimpleDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnCancelMatchmakingComplete(SessionName, false);
    }));
    return;
    }

    if (!GetSessionInt()->CancelMatchmaking(
    *GetABSessionInt()->CurrentMatchmakingSearchHandle->SearchingPlayerId,
    GetPredefinedSessionNameFromType(EAccelByteV2SessionType::GameSession)))
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("Failed executing"))
    // Failed to start matchmaking.
    ExecuteNextTick(FSimpleDelegate::CreateWeakLambda(this, [this, SessionName]()
    {
    OnCancelMatchmakingComplete(SessionName, false);
    }));
    }
    }
  5. Define the OnCancelMatchmakingComplete() function. This function triggers the delegate you created to inform whether the matchmaking that was started is successful or not.

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

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

    void UMatchmakingDSOnlineSession_Starter::RegisterOnlineDelegates()
    {
    Super::RegisterOnlineDelegates();

    // Bind matchmaking delegates.
    GetSessionInt()->OnMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnMatchmakingComplete);
    GetSessionInt()->OnCancelMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnCancelMatchmakingComplete);
    }
  7. When the online session is deinitialized, you need to unbind to stop listening to the event. You can do this by adding the code below in the predefined ClearOnlineDelegates() function, which is the first function to be called when the online session deinitializes.

    void UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
    {
    Super::ClearOnlineDelegates();

    // Unbind matchmaking delegates.
    GetSessionInt()->OnMatchmakingCompleteDelegates.RemoveAll(this);
    GetSessionInt()->OnCancelMatchmakingCompleteDelegates.RemoveAll(this);
    }

Handle when a dedicated server is received

When the matchmaking is finished and the game client joins the game session, the game client then waits to receive a dedicated server to travel to. In this section, you will learn how to handle when that happens.

  1. Create a function to travel the game client to the dedicated server. Open the MatchmakingDSOnlineSession_Starter Header file and declare the following function:

    public:
    virtual bool TravelToSession(const FName SessionName) override;
  2. Open the MatchmakingDSOnlineSession_Starter CPP file and define the function above. This function performs some validation before traveling the game client to the dedicated server. If it's valid, it travels to the dedicated server using the server address.

    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("Session is invalid"));
    return false;
    }

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

    const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
    if (!AbSessionInfo.IsValid())
    {
    UE_LOG_MATCHMAKINGDS(Warning, TEXT("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("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. Create a function to listen when the game client receives a server update. Open the MatchmakingDSOnlineSession_Starter Header file and declare the following function:

    protected:
    virtual void OnSessionServerUpdateReceived(FName SessionName) override;
  4. Declare a delegate that will be called when the dedicated server is received. You can use this delegate to bind some events later, especially when connecting the UI with the matchmaking implementation.

    public:
    virtual FOnServerSessionUpdateReceived* GetOnSessionServerUpdateReceivedDelegates() override
    {
    return &OnSessionServerUpdateReceivedDelegates;
    }

    private:
    FOnServerSessionUpdateReceived OnSessionServerUpdateReceivedDelegates;
  5. Open the MatchmakingDSOnlineSession_Starter CPP file and define the OnSessionServerUpdateReceived() function. This function checks whether it is valid to travel to the dedicated server or not. It also invokes the delegate you created earlier to inform that the server update is received.

    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. Cancelling attempt to travel to server"))
    OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, FOnlineError(true), false);
    return;
    }

    const bool bHasClientTravelTriggered = TravelToSession(SessionName);
    OnSessionServerUpdateReceivedDelegates.Broadcast(SessionName, FOnlineError(true), bHasClientTravelTriggered);
    }
  6. On some occasions, the backend might not be able to provide dedicated server to the game client. Create a function to handle when that happens by opening the MatchmakingDSOnlineSession_Starter Header file and declaring the following function:

    protected:
    virtual void OnSessionServerErrorReceived(FName SessionName, const FString& Message) override;
  7. Open the MatchmakingDSOnlineSession_Starter CPP file and define the function above. This function invokes the session server update delegate you created earlier but marks it as failed.

    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. Bind the function above so it will be called when the server update is received. You can do this by adding the code below to the RegisterOnlineDelegates() function, which is the first function to be called when the online session initializes.

    void UMatchmakingDSOnlineSession_Starter::RegisterOnlineDelegates()
    {
    Super::RegisterOnlineDelegates();

    // Bind matchmaking delegates.
    GetSessionInt()->OnMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnMatchmakingComplete);
    GetSessionInt()->OnCancelMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnCancelMatchmakingComplete);

    // Bind server event delegates.
    GetABSessionInt()->OnSessionServerUpdateDelegates.AddUObject(this, &ThisClass::OnSessionServerUpdateReceived);
    GetABSessionInt()->OnSessionServerErrorDelegates.AddUObject(this, &ThisClass::OnSessionServerErrorReceived);
    }
  9. When the online session is deinitialized, you need to unbind to stop listening to the event. You can do this by adding the code below in the predefined ClearOnlineDelegates() function, which is the first function to be called when the online session deinitializes.

    void UMatchmakingDSOnlineSession_Starter::ClearOnlineDelegates()
    {
    Super::ClearOnlineDelegates();

    // Unbind matchmaking delegates.
    GetSessionInt()->OnMatchmakingCompleteDelegates.RemoveAll(this);
    GetSessionInt()->OnCancelMatchmakingCompleteDelegates.RemoveAll(this);

    // Unbind server event delegates.
    GetABSessionInt()->OnSessionServerUpdateDelegates.RemoveAll(this);
    GetABSessionInt()->OnSessionServerErrorDelegates.RemoveAll(this);
    }

Handle backfill

Backfill is a flow that allows players to join the game server that is not full yet. In this section, you will implement functions to handle backfill.

注記

In this tutorial, when the backfill proposal is received, we will always accept the proposal. To reject the proposal, you can use the GetABSessionInt()->RejectBackfillProposal() function. The flow between accepting and rejecting the proposal is similar.

  1. Open the MatchmakingDSOnlineSession_Starter Header file and declare the following function:

    protected:
    virtual void OnBackfillProposalReceived(FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal) override;
  2. Declare a delegate that will be called when the accepting backfill process completes.

    public:
    virtual FOnMatchmakingAcceptBackfillProposalComplete* GetOnAcceptBackfillProposalCompleteDelegates() override
    {
    return &OnAcceptBackfillProposalCompleteDelegates;
    }

    private:
    FOnMatchmakingAcceptBackfillProposalComplete OnAcceptBackfillProposalCompleteDelegates;
  3. Open the MatchmakingDSOnlineSession_Starter CPP file and define the OnBackfillProposalReceived() function. This function accepts the received backfill so the player can join the game server that is not full yet. It also invokes the delegate you created earlier to inform the game that the accepting backfill process has completed.

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

    // Safety
    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. Bind the function above so it will be called when the backfill proposal is received. You can do this by adding the code below to the RegisterOnlineDelegates() function, which is the first function to be called when the online session initializes.

    void UMatchmakingDSOnlineSession_Starter::RegisterOnlineDelegates()
    {
    Super::RegisterOnlineDelegates();

    // Bind matchmaking delegates.
    GetSessionInt()->OnMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnMatchmakingComplete);
    GetSessionInt()->OnCancelMatchmakingCompleteDelegates.AddUObject(this, &ThisClass::OnCancelMatchmakingComplete);
    GetABSessionInt()->OnBackfillProposalReceivedDelegates.AddUObject(this, &ThisClass::OnBackfillProposalReceived);

    // Bind server event delegates.
    GetABSessionInt()->OnSessionServerUpdateDelegates.AddUObject(this, &ThisClass::OnSessionServerUpdateReceived);
    GetABSessionInt()->OnSessionServerErrorDelegates.AddUObject(this, &ThisClass::OnSessionServerErrorReceived);
    }
  5. When the online session is deinitialized, you need to unbind to stop listening to the event. You can do this by adding the 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()
    {
    Super::ClearOnlineDelegates();

    // Unbind matchmaking delegates.
    GetSessionInt()->OnMatchmakingCompleteDelegates.RemoveAll(this);
    GetSessionInt()->OnCancelMatchmakingCompleteDelegates.RemoveAll(this);
    GetABSessionInt()->OnBackfillProposalReceivedDelegates.RemoveAll(this);

    // Unbind server event delegates.
    GetABSessionInt()->OnSessionServerUpdateDelegates.RemoveAll(this);
    GetABSessionInt()->OnSessionServerErrorDelegates.RemoveAll(this);
    }

Set up dedicated server online subsystem

In this tutorial, you will be working with a game instance subsystem class named MatchmakingDSServerSubsystem_Starter. This class is used by the dedicated server to handle the game session produced by matchmaking. This class is available in the Resources section and consists of the following files:

  • Header file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSServerSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingDS/MatchmakingDSServerSubsystem_Starter.cpp

Handle when a dedicated server receives a session

When the matchmaking is finished, the backend creates and claims a dedicated server and assigns a game session to it. In this section, you will implement a function so the server can handle the received session.

  1. Open the MatchmakingDSServerSubsystem_Starter Header file and create the following function:

    protected:
    virtual void OnServerSessionReceived(FName SessionName) override;
  2. Open the MatchmakingDSServerSubsystem_Starter CPP file and define the function above. When the dedicated server receives a game session from the backend, this function assigns the correct game mode based on the information provided by the game session. This way, the server can provide the correct gameplay to the connected players. Be advised that most of this implementation is specific to Byte Wars, but the main point of this function is to get data from the session info on the backend, which is done by the highlighted lines. You can use this as a reference on how to get specific data from the session info.

    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("Session is invalid"));
    return;
    }

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

    // Query all currently registered user's info
    AuthenticatePlayer_OnRefreshSessionComplete(true);
    }
  3. You need to bind the function above so it will be called when the dedicated server receives a game session from the backend. You can do this by adding the code below to the Initialize() function, which is the first function to be called when the subsystem initializes.

    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. Unbind the function when the subsystem is deinitialized. You can do this by adding the following code to the Deinitialize() function.

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

    GetABSessionInt()->OnServerReceivedSessionDelegates.RemoveAll(this);
    }
  5. Build your project again and make sure there are no errors.

Resources