Skip to main content

Integrating matchmaking into your game client

Last updated on March 22, 2024

Overview

Matchmaking is the process by which two or more players can be evaluated for a match, and joined to a game session so they can play together. There are several components that the service interacts with during the matchmaking process.

  • Session Template: defines the characteristics a session will be created with. This includes joinability, what game server deployment to use, player requirements, team composition, etc.

  • Match Ticket: defines a request for matchmaking, either by a solo player or a party, with the player information attached for evaluation.

  • Backfill Ticket: similar to a match ticket, however it is only created if auto-backfill is enabled in the match ruleset. In which case, when two or more players are matched they will be joined to the session, and a backfill ticket submitted to the Matchmaking service to continue to find matches, until the minimum number of players have connected to the session.

  • Match Pool: defines a collection of match tickets that can be evaluated by the service for valid matches. Tickets end up in the same pool based on the selected game mode and preferences, if applicable.

  • Match Ruleset: defines the attributes and comparison criteria, that will be used by the default match function to determine if tickets make a valid match. In the case that the match function has been overridden, the ruleset will still need to be configured with any attributes that are stored in the Statistics service that are required for evaluation.

  • Match Function: defines the logic that the service will use to evaluate match tickets. By default, the service will use the criteria defined in the associated match ruleset.

note

This article currently covers setting up an Unreal Engine game. If you would like to use a different engine, please contact us.

In this guide, you will learn how to integrate the AccelByte Matchmaking service into your game client, using the Unreal OnlineSubsystem (OSS) V2 plugin. This includes how to start matchmaking, how to listen for match results and how to join a game session when matching is successful.

Goals

After you complete this guide, you should have an understanding of the following:

  • The matchmaking flow players will experience
  • How to start matchmaking for solo players and parties
  • How to cancel matchmaking before a match is found
  • How to listen for and handle match results
  • How to listen for game session invites and join the session

Prerequisites

Before you begin this guide, you should have an understanding of the following:

  • The Lobby, Session and Matchmaking services
  • The Unreal Engine, including the OnlineSubsystem (OSS)
  • How to add the Unreal OSS V2 Plugin into your game project
  • How to gain access to the game Namespace in your Admin Portal
  • How to configure a Session Template, Match Ruleset, and Match Pool for your game mode

Setup

In order to initiate matchmaking in your game implementation, there are several things you need to set up first:

  • You will need to Enable Sessions V2 in the AccelByte OSS by adding the following to the DefaultEngine.ini file:

    [OnlineSubsystemAccelByte]
    bEnableV2Sessions=true
  • You will need to add the required permissions for Matchmaking and Session V2 to your Game Client, in the Admin Portal.

  • Your Game Client must have authenticated with the AccelByte backend and connected to the Lobby service.

Matchmaking Flow

In this section you will get an understanding of the matchmaking flow for both solo players and parties.

AGS Overview

  1. Player creates a party and invites friends to participate. Next, they will select the desired game mode and set any preferences available.

  2. Once ready to start playing, the player (or party leader) will initiate matchmaking which creates a Match Ticket and queues it into a match pool. If a party leader initiates matchmaking, there will be one Match Ticket submitted with all the players attached.

  3. The service will then evaluate the ticket based on the logic defined in the associated match ruleset in order to find optimal matches.

  4. When a match is found, the service requests a game session from the Session service and attaches the ticket information to the session.

  5. The service will also create a backfill ticket for the game session so additional matches can continue to be found until the specified minimum player requirements are met.

  6. Players will receive an invite to join the game session, once they are selected as a match and can be connected.

Start Matchmaking

To start matchmaking, there are several OSS components you need to be familiar with in order to integrate the service into your game project:

  • Session Interface: provides the functionality used to perform matchmaking and session management.

  • Session Search Handle: stores the results of a session search, required during matchmaking to store the Game Session returned if matching was successful.

  • Matchmaking Callback Functions: listens for specific matchmaking events, such as matching started and matching completed.

  • Join Session Callback Functions: listens for specific session events, such as destroy session or join session.

Once you are ready and have completed the prerequisites and setup instructions above, you can begin adding the required code to request matchmaking.

In the function you plan to initiate matchmaking from, you need to acquire the Session Interface from the AccelByte OSS. Be aware, there is a custom AccelByte Session Interface you will be using to request matchmaking, of type FOnlineSessionAccelBytePtr.

const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem != nullptr))
{
return;
}

const FOnlineSessionAccelBytePtr SessionInterface = StaticCastSharedPtr<FOnlineSessionV2AccelByte>(Subsystem->GetSessionInterface());
if (!ensure(SessionInterface.IsValid()))
{
return;
}
note

We recommended you store the Session Interface as a class member as you will need to access it later on to handle callback results.

You need to acquire the Player ID for the Game Client so that you can later use it to pass the Player Controller ID as part of the request to start matchmaking. The Player Controller ID will also be used later on when handling callback results.

const FUniqueNetIdPtr LocalPlayerId = GetUniquePlayerId();
if (!ensure(LocalPlayerId.IsValid()))
{
return false;
}

You need to create a Session Search Handle FOnlineSessionSearch to be used to store the session result created during the matchmaking process if a match is found successfully. When creating a Session Search Handle, you will need to set the query settings with the following parameters:

  • Player Controller ID: the OSS ID of the player that the match ticket will be submitted for during matchmaking.
  • Match Pool Session Settings: the OSS session settings that will be used during matchmaking SETTING_SESSION_MATCHPOOL. By passing this in, it will be updated with the Match Pool Name you provide as the next parameter.
  • Match Pool Name: the name of the match pool you defined as part of the prerequisites for the specified game mode. This will inform the Matchmaking service to add the newly created match ticket to that match pool for evaluation.
  • Comparison Operator: the OSS operator to be used as part of the session search, in this case EOnlineComparisonOp::Equals.
// Create a new search handle instance for matchmaking. Importantly, you will need to set the match pool that you are searching for with SETTING_SESSION_MATCHPOOL, as shown below.

TSharedRef<FOnlineSessionSearch> MatchmakingSearchHandle = MakeShared<FOnlineSessionSearch>();
MatchmakingSearchHandle->QuerySettings.Set(SETTING_SESSION_MATCHPOOL, FString(TEXT("YOUR_MATCHPOOL_NAME_GOES_HERE")), EOnlineComparisonOp::Equals);
note

We recommend you store a Session Search Handle as a class member and ensure it retains its type, and then assign the returned results after a match is found successfully.

You need to register a FOnMatchmakingCompleteDelegate callback delegate to listen for the match results, which will trigger when matchmaking completes. Refer to the Handling the Match Results Callback section of this guide for sample code for the callback function.

  • Session Name: the name of the game session created if there was a successful match.
  • Success Value: a boolean value indicating whether matchmaking successfully found a match.
// Bind a function that has a return type of void and these parameters:
// FName SessionName, bool bWasSuccessful
const FOnMatchmakingCompleteDelegate OnMatchmakingCompleteDelegate = /* Bind to lambda or class method */;
SessionInterface->AddOnMatchmakingCompleteDelegate_Handle(OnMatchmakingCompleteDelegate);

Once you have the above, you can call StartMatchmaking() from the Session Interface to request matchmaking to start. This function takes the following parameters:

  • Player Controller ID: the OSS ID of the player that the Match Ticket will be submitted for during matchmaking.
  • Session Name: the name of the session created if matchmaking is successful. Typically you will pass NAME_GameSession.
  • Session Settings: the session settings that should be used by the Session service to create the Game Session, FOnlineSessionSettings(). In this case, you will leave it up to the Session service to assign the settings by passing in an empty object.
  • Session Search Handle: the Session Search Handle you created earlier in this section to store the Game Session that will be created if matchmaking is successful.

Once the request to StartMatchmaking() is made, assuming the call returns true, you should bind the Session Search Handle you defined earlier in this section to a class member for future access. If StartMatchmaking() returns false, that indicates that there was an issue calling into the Matchmaking service.

if (SessionInterface->StartMatchmaking(USER_ID_TO_MATCHMAKING_USER_ARRAY(LocalPlayerId.ToSharedRef()), NAME_GameSession, FOnlineSessionSettings(), MatchmakingSearchHandle, OnStartMatchmakingCompleteDelegate))
{
// Update the current search result handle class member with the search handle passed to the matchmaking service.
CurrentMatchmakingSearchHandle = MatchmakingSearchHandle;
}
note

With OnlineSubsystemV2, if you are in a party, the StartMatchmaking call will automatically send the PartyID to be attached as part of the Match Ticket during creation.

Joining a Game Session

Once matchmaking completes, the OnMatchmakingCompleteDelegate you bound prior to starting matchmaking will fire and you can process the results by following these instructions.

You need to retrieve the game session result stored in the SearchResults member array of the Session Search Handle. If the array is empty, refer to Troubleshooting.

// Ensure that we have a valid session search result in the array before continuing
if (!CurrentMatchmakingSearchHandle->SearchResults.IsValidIndex(0))
{
return false;
}

FOnlineSessionSearchResult MatchResult = CurrentMatchmakingSearchHandle->SearchResults[0];
EOnlineSessionTypeAccelByte SessionType = SessionInterface->GetSessionTypeFromSettings(MatchResult.Session.SessionSettings);
if (SessionType != EOnlineSessionTypeAccelByte::GameSession)
{
return false;
}

You need to check if the player is already in a game session, if so, you will need to destroy it so they can join the new session returned through matchmaking. You can register a FOnDestroySessionCompleteDelegate callback delegate to listen for the result.

// Check if we already have a game session, if so, destroy it to join this one
if (SessionInterface->GetNamedSession(NAME_GameSession) != nullptr)
{
const FOnDestroySessionCompleteDelegate OnDestroySessionForJoinCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &UOSSDemoGameSessionSubsystem::OnDestroySessionForJoinComplete, Session);
return SessionInterface->DestroySession(NAME_GameSession, OnDestroySessionForJoinCompleteDelegate);
}

You will need to register a FOnJoinSessionCompleteDelegate callback delegate to listen for the join result, which will trigger when the join request completes. At which point you can request to join the game server, refer to the Joining a Host Server section of this guide for sample code for the callback function.

// Register a delegate for joining the specified session
const FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &UOSSDemoGameSessionSubsystem::OnJoinSessionComplete);
JoinSessionDelegateHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);

Once you have the above, you can call JoinSession() from the Session Interface to join the returned session. This function takes the following parameters:

  • Player Controller ID: the OSS ID of the player that the Match Ticket will be submitted for during matchmaking.
  • Session Name: the name of the game session created if matchmaking is successful.
  • Session: the OSS Session object that the Session service will assign the game session info to for later access.
SessionInterface->JoinSession(LocalPlayerId.ToSharedRef().Get(), NAME_GameSession, Session);
note
  • If a player leaves or drops from a session, they cannot rejoin the same session through the Matchmaking service. The player will need to use the Join API from the Session service. If the session has its joinability set to OPEN or is set to INVITE_ONLY, they can call Session::JoinGameSession to rejoin the session.
  • If a player is still in DISCONNECTED status, their status will turn back to CONNECTED once they reconnect to the Lobby service. In this case, the player can get the game session details by using Session::GetMyGameSessions or Session::GetGameSessionDetails if they have the game ID.

Here is a full example of a callback function you can use as the delegate to listen for OnMatchmakingComplete results:

void OnMatchmakingCompleteDelegate(FName SessionName, bool bWasSuccessful)
{
if (SessionName != NAME_GameSession)
{
return;
}

EOnlineSessionTypeAccelByte SessionType = SessionInterface->GetSessionTypeFromSettings(MatchResult.Session.SessionSettings);
if (SessionType != EOnlineSessionTypeAccelByte::GameSession)
{
return false;
}

// Ensure that we have a valid session search result in the array before continuing
if (!CurrentMatchmakingSearchHandle->SearchResults.IsValidIndex(0))
{
return false;
}

FOnlineSessionSearchResult MatchResult = CurrentMatchmakingSearchHandle->SearchResults[0];

// Check if we already have a game session that we are in, if so, destroy it to join this one
if (SessionInterface->GetNamedSession(NAME_GameSession) != nullptr)
{
const FOnDestroySessionCompleteDelegate OnDestroySessionForJoinCompleteDelegate = FOnDestroySessionCompleteDelegate::CreateUObject(this, &UOSSDemoGameSessionSubsystem::OnDestroySessionForJoinComplete, Session);
return SessionInterface->DestroySession(NAME_GameSession, OnDestroySessionForJoinCompleteDelegate);
}

// Register a delegate for joining the specified session
const FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate = FOnJoinSessionCompleteDelegate::CreateUObject(this, &UOSSDemoGameSessionSubsystem::OnJoinSessionComplete);


JoinSessionDelegateHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);

const FUniqueNetIdPtr LocalPlayerId = GetUniquePlayerId();
if (!ensure(LocalPlayerId.IsValid()))
{
return false;
}

return SessionInterface->JoinSession(LocalPlayerId.ToSharedRef().Get(), NAME_GameSession, Session);
}

Joining a Game Host

If joining the game session completes, the OnJoinSessionComplete you bound prior to requesting to join the session will fire and you can process the results by following these instructions.

You will need to check the results enum passed into the delegate to determine if the join request completed successfully and you have joined the game session.

if (Result != EOnJoinSessionCompleteResult::Success)
{
return;
}

If successful, you have joined the game session and can extract the associated host connection from the Session Interface by calling GetResolvedConnectString(). The function takes the following parameters:

  • Session Name: the name of the game session created if matchmaking is successful.
  • TravelURL: will contain the connection string, if the session is connected to the host.
  • Game Port: the host port for the connection.

After requesting the connection string, the TravelURL will either contain a valid connection string or will be empty. If it contains a valid connection string that means the game session has been connected and now you can use the string to connect the player to the host.

If it is empty, then the game session is still waiting to connect and you will need to register a OnSessionServerUpdate callback delegate to listen for the host join result and wait for the game session to successfully connect to the host.

FString TravelUrl{};

if (SessionInterface->GetResolvedConnectString(SessionName, TravelUrl, NAME_GamePort) && !TravelUrl.IsEmpty())
{
// If we successfully got a connect string from the session, then this is where you would connect to it
}
else
{
// Otherwise, bind a delegate to OnSessionServerUpdate to wait for a server to be spun up. Once this
const FOnSessionServerUpdateDelegate OnSessionServerUpdateDelegate = FOnSessionServerUpdateDelegate::CreateUObject(this, &UOSSDemoGameSessionSubsystem::OnSessionServerUpdate);

SessionInterface->AddOnSessionServerUpdateDelegate_Handle(OnSessionServerUpdateDelegate);
}

Here is a full example of a callback function you can use as the delegate to listen for OnJoinSessionComplete results:

void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if (SessionName != NAME_GameSession)
{
return;
}

if (Result != EOnJoinSessionCompleteResult::Success)
{
return;
}

const FOnlineSessionAccelBytePtr SessionInterface = GetSessionInterface();
check(SessionInterface.IsValid());

// Remove our delegate handler for join session, we will rebind if we join another session
SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionDelegateHandle);
JoinSessionDelegateHandle.Reset();

FNamedOnlineSession* Session = SessionInterface->GetNamedSession(SessionName);
check(Session != nullptr)

TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(Session->SessionInfo);
check(SessionInfo.IsValid());

FString TravelUrl{};
if (SessionInterface->GetResolvedConnectString(SessionName, TravelUrl, NAME_GamePort) && !TravelUrl.IsEmpty())
{
// If we successfully got a connect string from the session, then this is where you would connect to it
}
else
{
// Otherwise, bind a delegate to OnSessionServerUpdate to wait for a server to be spun up. Once this
// delegate is fired, you may call the same GetResolvedConnectString to get the server address to
// connect to.
const FOnSessionServerUpdateDelegate OnSessionServerUpdateDelegate = FOnSessionServerUpdateDelegate::CreateUObject(this, &UOSSDemoGameSessionSubsystem::OnSessionServerUpdate);
SessionInterface->AddOnSessionServerUpdateDelegate_Handle(OnSessionServerUpdateDelegate);
}
}

Canceling Matchmaking

If you need to cancel matchmaking before the process completes or timeout occurs, you can call FOnlineSessionV2Accelbyte::CancelMatchmaking(), passing in the Player Controller ID, the Session Name, and the NAME_GameSession you provided when you requested matchmaking to start.

Supported Notifications

As part of the matchmaking process, the service will send the following notifications to the Game Client through the Lobby service and will be viewable in the log files.

NotificationDetails
OnSessionInvitedNotification that is sent to the Game Client indicating that there is a pending session invite.
OnSessionJoinedNotification that is sent to the Game Client indicating that it has successfully joined a session.
OnSessionKickedNotification that is sent to the Game Client indicating that it has been kicked out of a session.
OnSessionEndedNotification that is sent to the Game Client indicating that a session they were in has ended.
OnSessionMembersChangedNotification that is sent to all connected Game Clients informing them there has been a change in membership, player has connected or disconnected.
OnSessionDataChangedNotification that is sent to all connected Game Clients informing them there has been a change to the session information.
dsNotifNotification that is sent to all connected Game Clients providing them with the Dedicated Server status.
OnMatchmakingTicketExpiredNotification that is sent to the Game Client indicating that the request for matchmaking has exceeded the defined period in the Match Pool and has timed out.

Troubleshooting

In this section, you can find common errors and issues that may occur when using the service, along with recommendations on how to resolve them.

OnMatchmakingTicketExpired Notification from the Lobby service

If you receive the OnMatchmakingTicketExpired notification from the Lobby service, that means that the ticket reached the timeout limit and the player has been removed from matchmaking. This often happens if the timeout defined in the match pool configuration is too short or the match ruleset doesn't allow enough rule flexing to enable the player to match with others.

  • Review the Match Pool configuration to determine if the Match or Backfill Ticket timeout is too short.
  • Review the Match Ruleset to make sure if the minimum player/team requirements have been set correctly.

OnMatchmakingComplete SearchResults Array is Empty

When listening for the match result to notify you that matchmaking is complete, you may encounter an empty SearchResults array in the Session Search Handle. This likely means that the handle hasn't been saved as TSharedRef, which will prevent you from retrieving the matchmaking session result.

  • Be sure the Session Search Handle maintains type TSharedRef throughout.