Access
Connect cross-platform accounts & identity management
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.
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:
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 (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 |
In the Admin Portal, select your desired namespace.
In the left-hand menu, navigate to Game Management, click New Matchmaking, and select Session Templates.
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.
When you’re finished, click Create to save your ruleset.
When you’re finished, click Add to save your match pool.
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.
[OnlineSubsystemAccelByte]
bEnableV2Sessions=true
The next section takes you through the steps to:
Define a Session Template for your four-player game session in the Admin Portal as follows:
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 a simple four-player match rule with one team and up to four players as follows:
{
"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.
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.
When you're finished, click Add to save your match pool.
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.
OnMatchFound
.Invites
. The service also adds Match Info
to the Game Session, including Min Players for Viable Match
, which dictates how the matches are formed.Backfill Ticket
with the GameSession
as Input
. This only happens after GameSession
is created.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:
To start matchmaking with the OSS, follow these steps:
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;
}
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.
MatchmakingSearchHandle->QuerySettings.Set(SETTING_GAMESESSION_SERVERNAME, FString(TEXT("ExampleLocalServerName")), EOnlineComparisonOp::Equals);
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;
}
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);
If the session's server is still in the Creating state, then you need to add a delegate to AddOnSessionServerUpdateDelegate_Handle
so that you'll be notified once the state changes to Ready.
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);
}
The DS can be notified when it has been assigned to a game session, such as follows:
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);
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);
...
}
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}
// 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.
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);
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());
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.
If the optional request is not filled, it will get the value from the configuration used.