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

サブシステムを実装する - ピアツーピアでのクイックマッチ - (Unreal Engine モジュール)

Last updated on May 30, 2024

Peer-to-peer matchmaking flow

Take some time to understand how matchmaking using peer-to-peer (P2P) works by looking at 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 P2P server (also known as a host or listen server). For a refresher of the basics on how game sessions work, refer back to the Introduction to Session module.

In this tutorial, you will be working with an online session class named MatchmakingP2POnlineSession_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/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2POnlineSession_Starter.cpp

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

  • Similar to the Introduction to Session, 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 following map that defines the Match Pool IDs you created previously. As you can see, the IDs are the same exact names as the ones you set up in Admin Portal. These are required so you can request matchmaking to the correct Match Pool.

    const TMap<EGameModeType, FString> MatchPoolIds = {
    {EGameModeType::FFA, "unreal-elimination-p2p"},
    {EGameModeType::TDM, "unreal-teamdeathmatch-p2p"}
    };
  • The Header file also has the following map that translates the 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-p2p", "ELIMINATION-P2P"},
    {"unreal-teamdeathmatch-p2p", "TEAMDEATHMATCH-P2P"}
    };
  • 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 P2P host, 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 UMatchmakingP2POnlineSession_Starter::QueryUserInfo(
    const int32 LocalUserNum,
    const TArray<FUniqueNetIdRef>& UserIds,
    const FOnQueryUsersInfoComplete& OnComplete)
    {
    UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))

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

    TArray<FUserOnlineAccountAccelByte*> UserInfo;
    if (RetrieveUserInfoCache(UserIds, UserInfo))
    {
    UE_LOG_MATCHMAKINGP2P(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
    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);
    }
    }
    }

Start matchmaking

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

  1. Open the MatchmakingP2POnlineSession_Starter Header file and then 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 just declared above. Open the MatchmakingP2POnlineSession_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. We set the Match Pool to the matchmaking handle so it will start the matchmaking based on the Match Pool configured in the Admin Portal. Also, before it begins matchmaking, it first checks whether the player is already in the game session or not. If yes, then it leaves the game session first and then retries matchmaking, which will be handled by the OnLeaveSessionForReMatchmakingComplete() function. When the matchmaking successfully starts, it then calls the OnStartMatchmakingComplete() function.

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

    // safety
    if (!ensure(GetSessionInt()))
    {
    UE_LOG_MATCHMAKINGP2P(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_MATCHMAKINGP2P(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_MATCHMAKINGP2P(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
    const FString MatchPoolId = MatchPoolIds[GameModeType];

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

    if (!GetSessionInt()->StartMatchmaking(
    USER_ID_TO_MATCHMAKING_USER_ARRAY(PlayerNetId.ToSharedRef()),
    SessionName,
    FOnlineSessionSettings(),
    MatchmakingSearchHandle,
    FOnStartMatchmakingComplete::CreateUObject(this, &ThisClass::OnStartMatchmakingComplete)))
    {
    UE_LOG_MATCHMAKINGP2P(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 has started is successful or not.

    void UMatchmakingP2POnlineSession_Starter::OnStartMatchmakingComplete(
    FName SessionName,
    const FOnlineError& ErrorDetails,
    const FSessionMatchmakingResults& Results)
    {
    UE_LOG_MATCHMAKINGP2P(
    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 if it is valid to retry to start matchmaking or not.

    void UMatchmakingP2POnlineSession_Starter::OnLeaveSessionForReMatchmakingComplete(
    FName SessionName,
    bool bSucceeded,
    const int32 LocalUserNum,
    const EGameModeType GameModeType)
    {
    UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))

    GetOnLeaveSessionCompleteDelegates()->Remove(OnLeaveSessionForReMatchmakingCompleteDelegateHandle);

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

    StartMatchmaking(PC, SessionName, EGameModeNetworkType::P2P, GameModeType);
    }
    else
    {
    UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Is not a game session."));
    OnStartMatchmakingComplete(SessionName, FOnlineError(false), {});
    }
    }
  8. You need to handle when the matchmaking completes. Open the MatchmakingP2POnlineSession_Starter Header file and then declare the following function:

    protected:
    virtual void OnMatchmakingComplete(FName SessionName, bool bSucceeded) override;
  9. Declare a delegate that will be called when the matchmaking 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 MatchmakingP2POnlineSession_Starter CPP file and then define the OnMatchmakingComplete() function. This function will check whether a game session was found after the matchmaking completes. If found, it then joins the game session. It also invokes the delegate you created earlier to inform the game that the matchmaking process has completed.

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

    const TSharedPtr<FOnlineSessionSearchAccelByte> CurrentMatchmakingSearchHandle = GetABSessionInt()->CurrentMatchmakingSearchHandle;
    if (!bSucceeded ||
    !ensure(CurrentMatchmakingSearchHandle.IsValid()) ||
    !ensure(CurrentMatchmakingSearchHandle->SearchResults.IsValidIndex(0)) ||
    !ensure(CurrentMatchmakingSearchHandle->SearchingPlayerId.IsValid()))
    {
    UE_LOG_MATCHMAKINGP2P(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. Bind the function above so it will be called when the 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 UMatchmakingP2POnlineSession_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 deinitializes.

    void UMatchmakingP2POnlineSession_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 MatchmakingP2POnlineSession_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 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 MatchmakingP2POnlineSession_Starter CPP file and define the CancelMatchmaking() function first. This function checks whether it is valid to cancel matchmaking or not before executing the action. When the process completes, it calls the OnCancelMatchmakingComplete() function.

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

    // safety
    if (!ensure(GetSessionInt()))
    {
    UE_LOG_MATCHMAKINGP2P(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_MATCHMAKINGP2P(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_MATCHMAKINGP2P(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 has been canceled was successful or not.

    void UMatchmakingP2POnlineSession_Starter::OnCancelMatchmakingComplete(FName SessionName, bool bSucceeded)
    {
    UE_LOG_MATCHMAKINGP2P(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 UMatchmakingP2POnlineSession_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 UMatchmakingP2POnlineSession_Starter::ClearOnlineDelegates()
    {
    Super::ClearOnlineDelegates();

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

Handle when a P2P host is received

When the matchmaking is finished and the game client joins the game session, the game client then waits to receive the P2P host address to travel. In this section, you will learn how to handle when this happens.

  1. Create a function to travel the game client to the P2P host. Open the MatchmakingP2POnlineSession_Starter Header file and declare the following function:

    public:
    virtual bool TravelToSession(const FName SessionName) override;
  2. Open the MatchmakingP2POnlineSession_Starter CPP file and define the function above. This function performs some validation before traveling the game client to the P2P host. If it is valid, then it travels to the P2P host using its address.

    bool UMatchmakingP2POnlineSession_Starter::TravelToSession(const FName SessionName)
    {
    UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))

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

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

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

    const TSharedPtr<FOnlineSessionInfoAccelByteV2> AbSessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(SessionInfo);
    if (!AbSessionInfo.IsValid())
    {
    UE_LOG_MATCHMAKINGP2P(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_MATCHMAKINGP2P(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_MATCHMAKINGP2P(Warning, TEXT("Player controller is not (derived from) AAccelByteWarsPlayerController"));
    return false;
    }

    FString ServerAddress = "";

    // If local user is not the P2P host -> connect to host
    if (!GetABSessionInt()->IsPlayerP2PHost(GetLocalPlayerUniqueNetId(PlayerController).ToSharedRef().Get(), SessionName))
    {
    UE_LOG_MATCHMAKINGP2P(Log, TEXT("Is not P2P host, travelling to host"));
    GetABSessionInt()->GetResolvedConnectString(SessionName, ServerAddress);
    if (ServerAddress.IsEmpty())
    {
    UE_LOG_MATCHMAKINGP2P(Warning, TEXT("Can't find session's server address"));
    return false;
    }
    }
    else
    {
    UE_LOG_MATCHMAKINGP2P(Log, TEXT("Is P2P host, travelling as listen server"));
    ServerAddress = "MainMenu?listen";
    }

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

    return true;
    }
  3. Create a function to listen for when the game client receives a server update. A server update is an event when the game client receives a game server to travel from the backend, in this case the P2P host. Open the MatchmakingP2POnlineSession_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 server update 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 MatchmakingP2POnlineSession_Starter CPP file and define the OnSessionServerUpdateReceived() function. This function checks whether it is valid to travel to the P2P host or not. It also invokes the delegate you created earlier to inform that the server update has been received.

    void UMatchmakingP2POnlineSession_Starter::OnSessionServerUpdateReceived(FName SessionName)
    {
    UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))

    if (bLeavingSession)
    {
    UE_LOG_MATCHMAKINGP2P(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 fail to find a game server for the game client. Create a function to handle when this happens. Open the MatchmakingP2POnlineSession_Starter Header file and declare the following function:

    protected:
    virtual void OnSessionServerErrorReceived(FName SessionName, const FString& Message) override;
  7. Open the MatchmakingP2POnlineSession_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 UMatchmakingP2POnlineSession_Starter::OnSessionServerErrorReceived(FName SessionName, const FString& Message)
    {
    UE_LOG_MATCHMAKINGP2P(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 UMatchmakingP2POnlineSession_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 UMatchmakingP2POnlineSession_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, it 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 MatchmakingP2POnlineSession_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 process of accepting backfill completes.

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

    private:
    FOnMatchmakingAcceptBackfillProposalComplete OnAcceptBackfillProposalCompleteDelegates;
  3. Open the MatchmakingP2POnlineSession_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 UMatchmakingP2POnlineSession_Starter::OnBackfillProposalReceived(FAccelByteModelsV2MatchmakingBackfillProposalNotif Proposal)
    {
    UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))

    // Safety
    if (!ensure(GetABSessionInt().IsValid()))
    {
    UE_LOG_MATCHMAKINGP2P(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_MATCHMAKINGP2P(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 UMatchmakingP2POnlineSession_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 deinitializes.

    void UMatchmakingP2POnlineSession_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 peer-to-peer host online subsystem

In this tutorial, you will be working with a game instance subsystem named MatchmakingP2PServerSubsystem_Starter. This class is used by the P2P 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/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.h
  • CPP file: /Source/AccelByteWars/TutorialModules/Play/MatchmakingP2P/MatchmakingP2PServerSubsystem_Starter.cpp

Handle the P2P host receiving a session

When matchmaking is finished, the backend selects one of the connected game clients to be the P2P host and assigns a game session to it. In this section, you will implement a function so the P2P host can handle the received session.

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

    protected:
    virtual void OnServerSessionReceived(FName SessionName) override;
  2. Open the MatchmakingP2PServerSubsystem_Starter CPP file and define the function above. When the P2P host 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 P2P host can provide the correct gameplay to the connected players.

    void UMatchmakingP2PServerSubsystem_Starter::OnServerSessionReceived(FName SessionName)
    {
    Super::OnServerSessionReceived(SessionName);
    UE_LOG_MATCHMAKINGP2P(Verbose, TEXT("called"))

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

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

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

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

    const FNamedOnlineSession* Session = MatchmakingOnlineSession->GetSession(SessionName);
    if (!Session)
    {
    UE_LOG_MATCHMAKINGP2P(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. Bind the function above so it will be called when the P2P host 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 UMatchmakingP2PServerSubsystem_Starter::Initialize(FSubsystemCollectionBase& Collection)
    {
    Super::Initialize(Collection);

    UOnlineSession* BaseOnlineSession = GetWorld()->GetGameInstance()->GetOnlineSession();
    if (!ensure(BaseOnlineSession))
    {
    return;
    }
    MatchmakingOnlineSession = Cast<UMatchmakingP2POnlineSession_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 UMatchmakingP2PServerSubsystem_Starter::Deinitialize()
    {
    Super::Deinitialize();

    GetABSessionInt()->OnServerReceivedSessionDelegates.RemoveAll(this);
    }

Resources