Last Updated: 11/21/2022, 10:36:33 AM

# Multiplayer V2

# Overview

AccelByte Cloud’s Multiplayer V2 is a session-based player matchmaker that offers more flexibility than our current Matchmaking (opens new window) service. Game developers can now use their own matchmaking functions to override the defaults of our original service.

# How It Works

Multiplayer V2 works in almost the exact same way as Matchmaking V1, but it no longer uses the Lobby to manage parties. Instead, parties are managed by the Session Service. The Lobby is still used to send notifications back to the game client. The flow is as follows:

Multiplayer V2

Party:

The client asks the Session service to create a party. A Party Invitation notification is then relayed from the Session service through the Lobby.

Matchmaking:

The client asks the Matchmaking service to request a match. The Matchmaking service then asks the Session service to create a session. Whether the session creation succeeds or fails, A notification is sent back to the client through the Lobby.

# Permissions

Permissions (opens new window) are used to grant access to specific resources within our Cloud services. Make sure your account has the following permissions before you attempt to manage Multiplayer V2.

Usage Permissions Action
To add, edit and delete Session Template ADMIN:NAMESPACE:*:SESSION:CONFIGURATION CREATE, READ, UPDATE, DELETE
To view session in Session and Parties NAMESPACE:*:SESSION:GAME CREATE, READ, UPDATE, DELETE
To create matchmaking ticket in MPv2 NAMESPACE:*:MATCHMAKING:TICKET CREATE, READ, UPDATE, DELETE
To add, edit and delete Match Pool NAMESPACE:*:MATCHMAKING:POOL CREATE, READ, UPDATE, DELETE
To add, edit and delete Match Ruleset NAMESPACE:*:MATCHMAKING:RULES CREATE, READ, UPDATE, DELETE
To add Match Function in Match Pool NAMESPACE:*:MATCHMAKING:FUNCTIONS CREATE, READ, UPDATE, DELETE

# Manage Multiplayer V2 in the Admin Portal

# Create a Session Template

  1. In the Admin Portal, select your desired namespace.

  2. In the left-hand menu, navigate to Game Management, click New Matchmaking, and select Session Templates.

Multiplayer V2

  1. Select + Add Session Template in the top right-hand corner.

Multiplayer V2

  1. The Add Session Template form will appear. Fill in the following fields:

Multiplayer V2

  • Session Template Name: enter a name for your session template.
  • Session Type: select the session type from the dropdown menu.
  • Min Players: set the minimum number of players allowed in the session.
  • Max Players: set the maximum number of players allowed in the session.
  • Invite Timeout: set the time limit (in seconds) before an invite from the session times out.
  • Inactive Timeout: set how long (in seconds) the session will wait before timing out a player that has not responded.
  • Joinability: select the joinability from the dropdown menu.
  • Requested Regions: select the regions this session can be used in from the dropdown menu.

NOTE

The party leader will only be able to send invites to the number of players defined in the Max Players value, minus 1 to account for the party leader. The party leader will only be able to send out additional invites if the Invite Timeout limit is reached on any previously sent invite.

  1. When you’re finished, click Add to save your session.

# Create a Match Ruleset

  1. In the Admin Portal, select your desired namespace. In the left-hand menu, navigate to Game Management, click New Matchmaking, and select Match Configuration.

Multiplayer V2

  1. Select + Create Rulesets in the top right-hand corner.

Multiplayer V2

  1. The Create Match Ruleset page will appear. Fill in the following fields:

Multiplayer V2

  • Ruleset Name: enter a name for your match ruleset.
  • Configuration (JSON): input your configuration in JSON format. A configuration template will appear in this field which you can edit for your ruleset or replace with your own JSON.

When you’re finished, click Create to save your ruleset.

# Create a Match Pool

  1. In the Admin Portal, select your desired namespace. In the left-hand menu, navigate to Game Management, click New Matchmaking, and select Match Configuration.

Multiplayer V2

  1. Select the Match Pools tab at the top of the page and then click + Add Match Pools in the top right-hand corner.

Multiplayer V2

  1. The Add Match Pools form will appear. Fill in the following fields:

Multiplayer V2

  • Match Pool Name: enter a name for your match pool name.
  • Match Ruleset: select a Match Ruleset from the dropdown menu.
  • Session Template: select a Session Template from the dropdown menu.
  • Match Function: select a Match Function from the dropdown menu.

When you’re finished, click Add to save your match pool.

# Simple Matchmaking with Backfill

These instructions will guide you through the process of creating simple four-player matchmaking using a Dedicated Server (DS), with support for solo or party play, using AccelByte Cloud’s Unreal OnlineSubsystem v2 plugin.

We will show you how to configure Backfill to let players join the session via matchmaking until the session is full.

# Prerequisites

[OnlineSubsystemAccelByte]
bEnableV2Sessions=true

# Create Definitions

The next section takes you through the steps to:

  • Define a Session Template that can accommodate up to four players using DS with the correct Armada deployment.
  • Define a Match Rule to accommodate up to four players with Backfill enabled.
  • Define a Match Pool to associate the Match Rule and the Session Template.

# Define the Session Template

Define a Session Template for your four-player game session in the Admin Portal as follows:

  1. Log in to the Admin Portal, select your desired namespace, under the New Matchmaking section, select Session Templates and click the + Add Session Template button.

Multiplayer V2

  1. The Add Session Template form will appear. Fill in the required fields:

Multiplayer V2

  • Session Template Name: 4_player_session

  • Session Type: DS

  • Deployment: Specify the right Armada DS deployment (from Prerequisites).

  • Min Players: One

  • Max Players: Four

  • Joinability: We will use Open to have this discoverable by Session Query API.

    When you're finished, click Add.

# Define the Match Rule

Define a simple four-player match rule with one team and up to four players as follows:

  1. Log in to the Admin Portal, select your desired namespace, under the New Matchmaking section, select Match Configurations and click the + Create Rulesets button.

Multiplayer V2

  1. The Create Match Ruleset page will appear. Fill in the following fields:

Multiplayer V2

  • Ruleset Name: enter a name for your session template.
  • Configuration (JSON): input your configuration in JSON format. The field will be prefilled with a template JSON. In this In this example, we will delete the prefilled template and use:
{
 "name": "4_player_match",
 "data": {
   "auto_backfill": true,
   "alliance": {
     "min_number": 1,
     "max_number": 1,
     "player_min_number": 1,
     "player_max_number": 4
   }
 }
}

When you're finished, click Create to save your ruleset.

# Define the Match Pool

Use the 4_player_session session template to define a match pool for matchmaking with the 4_player_match ruleset so it can spin up the DS from the correct deployment.

  1. On the Match Configuration page, select the Match Pools tab at the top and click + Add Match Pools in the top right-hand corner.

Multiplayer V2

  1. The Add Match Pools form will appear. Fill in the following fields:

Multiplayer V2

  • Match Pool Name: 4_player_pool
  • Match Ruleset: 4_player_match
  • Session Template: 4_player_session
  • Match Function: default

When you're finished, click Add to save your match pool.

# Game Client Matchmaking Flow

Multiplayer V2

This diagram shows basic matchmaking where players submit tickets to get into a match. In this case, Players 1 and 2 are in a Party and Player 3 is in solo play.

  1. Players 1 and 2 (a party) and Player 3 (solo play) all request to start matchmaking. When a match is found, the Matchmaking service replies with OnMatchFound.
  2. The Matchmaking service creates a game session and puts all the participants into the session with Invites. The service also adds Match Info to the Game Session, including Min Players for Viable Match, which dictates how the matches are formed.
  3. The Matchmaking service finds a match. The match function then creates a Backfill Ticket with the GameSession as Input. This only happens after GameSession is created.
  4. The players accept the invite with JoinSession. They can also Get Session Details to retrieve details about the session they were invited to. In this case, the session will have details about their Tickets.

NOTE

Accept Invite replaces V1's Consent (opens new window).

In this section you will learn how to implement Matchmaking from the game client. We will explain how to:

  • Start Matchmaking
  • Handle Match Result Callbacks
  • Handle Invites to Game Sessions and How to Join Sessions
  • Get Dedicated Server Information from Game Session and Connect to the DS

# Start Matchmaking

To start matchmaking with the OSS, follow these steps:

  1. Use the following code to get FOnlineSessionV2AccelByte SessionInterface.
const IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld());
if (!ensure(Subsystem != nullptr))
{
    return;
}

const FOnlineSessionAccelBytePtr SessionInterface = StaticCastSharedPtr<FOnlineSessionV2AccelByte>(Subsystem->GetSessionInterface());
if (!ensure(SessionInterface.IsValid()))
{
    return;
}
  1. Create a session search handle and save it somewhere so that you can find it later in the matchmaking process.
  2. Register the matchmaking delegate to listen to matchmaking results (match found or timeout) and then start the matchmaking process with registered delegates.
// 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("4_player_pool")), EOnlineComparisonOp::Equals);

// From here, you can add any other attributes that you want to match against using the same Set method.

FUniqueNetIdPtr LocalPlayerId = /* Get from Local Player instance */;

// Bind a delegate for when we have completed matchmaking. If this is fired and the result is successful, the search
// handle that you created previously will have a match result in the SearchResults array.
// Bind this to 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);

// Bind a delegate for when we have completed the call to kick off matchmaking. This does not mean matchmaking is complete.
// You will need to wait for the delegate we bound above to fire to consider matchmaking complete.
// Bind this to a function that has a return type of void and these parameters:
// FName SessionName, const FOnlineError& ErrorDetails, const FSessionMatchmakingResults& Results
const FOnStartMatchmakingComplete OnStartMatchmakingCompleteDelegate = /* Bind to lambda or class method */;
if (SessionInterface->StartMatchmaking(USER_ID_TO_MATCHMAKING_USER_ARRAY(LocalPlayerId.ToSharedRef()), NAME_GameSession, FOnlineSessionSettings(), MatchmakingSearchHandle, OnStartMatchmakingCompleteDelegate))
{
    // StartMatchmaking will modify the search handle we passed in, so that it can track information like who
    // kicked off the matchmaking request, and what the ID of the ticket is. With that in mind, you will want
    // to update your stored search handle to match the modified one here. We would recommend storing the search
    // handle as a class member, or passing the handle along to the delegate for matchmaking complete so that
    // matchmaking session results can be accessed. This example shows updating a class member.
    CurrentMatchmakingSearchHandle = MatchmakingSearchHandle;
}

IMPORTANT

With OnlineSubsystemV2, if you are in a party, the Start Matchmaking call will automatically send the PartyID along with the match ticket creation.

  1. Use the following code in conjunction with the above to set the matchmaking into a specific local DS:
MatchmakingSearchHandle->QuerySettings.Set(SETTING_GAMESESSION_SERVERNAME, FString(TEXT("ExampleLocalServerName")), EOnlineComparisonOp::Equals);

# Handle Match Complete Callback

Use the following code to handle the match complete callback:

SessionInterface->AddOnMatchmakingCompleteDelegate_Handle(OnMatchmakingCompleteDelegate);

// Bind a delegate for when we have completed the call to kick off matchmaking. This does not mean matchmaking is complete.
// You will need to wait for the delegate we bound above to fire to consider matchmaking complete.
// Bind this to a function that has a return type of void and these parameters:
// FName SessionName, const FOnlineError& ErrorDetails, const FSessionMatchmakingResults& Results
const FOnStartMatchmakingComplete OnStartMatchmakingCompleteDelegate = /* Bind to lambda or class method */;
if (SessionInterface->StartMatchmaking(USER_ID_TO_MATCHMAKING_USER_ARRAY(LocalPlayerId.ToSharedRef()), NAME_GameSession, FOnlineSessionSettings(), MatchmakingSearchHandle, OnStartMatchmakingCompleteDelegate))
{
    // StartMatchmaking will modify the search handle we passed in, so that it can track information like who
    // kicked off the matchmaking request, and what the ID of the ticket is. With that in mind, you will want
    // to update your stored search handle to match the modified one here. We would recommend storing the search
    // handle as a class member, or passing the handle along to the delegate for matchmaking complete so that
    // matchmaking session results can be accessed. This example shows updating a class member.
    CurrentMatchmakingSearchHandle = MatchmakingSearchHandle;
}

# Handle Game Session Invite and Join Session

Use the following code to handle a game session invite and join a session:

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

// 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 = GetAssociatedUniqueId();
if (!ensure(LocalPlayerId.IsValid()))
{
	return false;
}

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

# Get Dedicated Server Information from Game Session and Connect to the DS

Use the following to retrieve DS information from game sessions and connect to the DS:

// Ignore non-game session create results
if (SessionName != NAME_GameSession)
{
	return;
}

if (!bWasSuccessful)
{
	return;
}

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

// Remove our delegate handler for create session, we will rebind if we create a new session
SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionDelegateHandle);
CreateSessionDelegateHandle.Reset();

FNamedOnlineSession* Session = SessionInterface->GetNamedSession(SessionName);
if (!ensure(Session != nullptr))
{
	return;
}

TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo = StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(Session->SessionInfo);
if (!ensure(SessionInfo.IsValid()))
{
	return;
}

ULocalPlayer* LocalPlayer = GetLocalPlayer();
if (!ensure(LocalPlayer != nullptr))
{
	return;
}

APlayerController* Controller = LocalPlayer->GetPlayerController(GetWorld());
if (!ensure(Controller != nullptr))
{
	return;
}

// If the server type for the created session is either NONE (local) or P2P, then we just want to travel to the lobby as a listen server
const EAccelByteV2SessionConfigurationServerType ServerType = SessionInfo->GetServerType();
if (ServerType == EAccelByteV2SessionConfigurationServerType::NONE || ServerType == EAccelByteV2SessionConfigurationServerType::P2P)
{
	Controller->ClientTravel(TEXT("LobbyMap?listen"), TRAVEL_Absolute);
	return;
}

// Otherwise, check if we already have a DS to connect to, and if so then we want to travel to it
FString TravelUrl{};
if (SessionInterface->GetResolvedConnectString(SessionName, TravelUrl) && !TravelUrl.IsEmpty())
{
	Controller->ClientTravel(TravelUrl, TRAVEL_Absolute);
}

# Handle Match Results from the DS Flow

Multiplayer V2

# Get Session Information from DS

The DS can be notified when it has been assigned to a game session, such as follows:

  1. Register the delegate to handle the ReceivedSession event.
// For server, hook into the moment that the DS gets session information, read the MAPNAME, then do a ServerTravel to that map

const FOnServerReceivedSessionDelegate OnServerReceivedSessionDelegate = FOnServerReceivedSessionDelegate::CreateUObject(this, &UCommonSessionSubsystem::OnServerReceivedSession);
ServerReceivedSessionDelegateHandle = SessionInterface->AddOnServerReceivedSessionDelegate_Handle(OnServerReceivedSessionDelegate);
  1. When the DS is assigned to a game session, it can get Match Information out of the Session Data.
void UCommonSessionSubsystem::OnServerReceivedSession(FName SessionName)
{
	// Ignore non-game session join results
	if (SessionName != NAME_GameSession)
	{
		UE_LOG(LogCommonSession, Log, TEXT("Server - Named session was not of type GameSession, skipping!"));
		return;
	}

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

	// Remove our delegate handler we will rebind if needed later
	SessionInterface->ClearOnServerReceivedSessionDelegate_Handle(ServerReceivedSessionDelegateHandle);
	ServerReceivedSessionDelegateHandle.Reset();

	FNamedOnlineSession* Session = SessionInterface->GetNamedSession(SessionName);
	if (!ensure(Session != nullptr))
	{
		UE_LOG(LogCommonSession, Error, TEXT("Server - Named session was null! Unable to travel to map"));
		return;
	}

	FString MapName = TEXT("");
	Session->SessionSettings.Get(SETTING_MAPNAME, MapName);
	...
}

# Approve or Reject the Backfill Proposal from the DS

IMPORTANT

By enabling auto_backfill in the Match Ruleset, the Matchmaking service will automatically queue the Match Session for backfill processing so long as it's not full.

When the Matchmaking service finds new tickets to be matched into the existing DS session, it will send BackfillProposal to the DS.

The DS can choose to accept (which will add the tickets into the session and all new players will get invited into the session) or reject (which puts all the tickets back into the matchmaking queue).

To enable the DS to accept and reject backfill, the DS's OAuth client must have this permission: NAMESPACE:{namespace}:MATCHMAKING:BACKFILL {UPDATE}

  1. Connect to the ServerDSHub.
// If connecting a cloud-hosted server to DS Hub, you'll need to get the name of the server
// from the POD_NAME environment variable and pass it to the Connect method
const FString ServerName = FGenericPlatformMisc::GetEnvironmentVariable(TEXT("POD_NAME"));
FRegistry::ServerDSHub.Connect(ServerName);

// Otherwise, if you are connecting a local DS to the DS hub, then you should pass in the
// name that you gave that local server as the parameter to the Connect method.
  1. Register the Backfill Proposal Notification handler.
const AccelByte::GameServerApi::FOnV2BackfillProposalNotification OnV2BackfillProposalNotificationDelegate = AccelByte::GameServerApi::FOnV2BackfillProposalNotification::CreateLambda([](const FAccelByteModelsV2MatchmakingBackfillProposalNotif& Proposal) {
    // From the Backfill Proposal, you can evaluate if this proposal is good for your game or not by inspecting the team formation. From here, you can either accept or reject the backfill proposal.
    // ACCEPT OR REJECT PROPOSAL
});

FRegistry::ServerDSHub.SetOnV2BackfillProposalNotificationDelegate(OnV2BackfillProposalNotificationDelegate);
  1. You can now choose to:

    a. Accept the Backfill Proposal, or

    FRegistry::ServerMatchmakingV2.AcceptBackfillProposal(Proposal.BackfillTicketID, Proposal.ProposalID, false, FVoidHandler(), FErrorHandler());
    

    b. Reject the Proposal.

    // From here, you can either accept or reject the backfill proposal. An example of
    // calling both methods is below. Note that the boolean parameter after the IDs
    // signals to matchmaking whether or not we want to backfill further after this.
    // A value of true indicates that we wish to stop backfilling, while a value of
    // false indicates that we do not wish to stop backfilling.
    FRegistry::ServerMatchmakingV2.RejectBackfillProposal(Proposal.BackfillTicketID, false /*continue backfilling*/, FVoidHandler(), FErrorHandler());
    
  2. Use the boolean argument to instruct the Matchmaking service to continue or stop the backfilling process. Set it to true if you want to stop backfilling, or false otherwise.

# Implement Multiplayer V2 Using the Unreal Engine SDK

# Party

# Create a Party

If the optional request is not filled, it will get the value from the configuration used.

# Retrieve Parties

# Retrieve Party Details by Party ID

# Update Party Data

# Invite Player to Party

# Join a Party

# Rejecting Party Invite

# Leave Current Party

# Kick Player from Party

# Promote Member to Party Leader

# Listen to Party Notifications

# Game Session

# Create a Game Session

# Retrieve Game Sessions

# Retrieve Game Session Details

# Update Game Session

# Delete a Game Session

# Invite Player to Session

# Join a Game Session

# Reject Game Session Invite

# Leave Game Session

# Listen to Game Session Notifications

# Matchmaking

# Create Match Ticket

# Get Ticket Details

# Delete Match Ticket

# Server

# Connect and Listen for DSHub Notifications

# Disconnect from DSHub

# Listen to Backfill Proposal Notification

# Accept Backfill Proposal

# Reject Backfill Proposal

# Retrieve Game Session Details

# Update Game Session

# Delete Game Session