Use the SDK for dedicated server matchmaking - Quick match with dedicated servers - (Unity module)
Unwrap the wrapper
After setting up a Match Pool, you will call the AccelByte Gaming Services (AGS) Game SDK for dedicated server (DS) matchmaking. This is done in the MatchmakingSessionDSWrapper
class in the StartDSMatchmaking
function to initiate the matchmaking process using a dedicated server, since you will use the Match Pool name you set up previously with the dedicated server in its Session Template.
What is in MatchmakingSessionWrapper
MatchmakingSessionWrapper
has been prepared for you as a parent class, containing predefined code that leverages the AGS Game SDK to perform various matchmaking-related functions.
MatchmakingV2
class from AGS Game SDK is used to create and delete matchmaking tickets. The StartMatchmakingAsync
method creates a match ticket for the player, while CheckActiveGameSessionClient
ensures the player is not currently in an active session. To create a new ticket, players should refrain from joining any ongoing sessions.
public async UniTask StartMatchmakingAsync(string matchPool, bool isLocal = false)
{
CheckActiveGameSessionClient();
await WaitCheckActiveGameSessionAsync();
if (playerSessions.Count > 0)
{
BytewarsLogger.Log($"Player is already in a session: {playerSessions.Count}");
OnMatchmakingError?.Invoke($"Player is already in a session \n cannot start matchmaking");
isGetActiveSessionCompleted = false;
playerSessions.Clear();
return;
}
MatchmakingV2CreateTicketRequestOptionalParams optionalParams = new MatchmakingV2CreateTicketRequestOptionalParams();
optionalParams.attributes = new Dictionary<string, object>();
// Add local server name.
if (isLocal)
{
optionalParams.attributes.Add("server_name", ConnectionHandler.LocalServerName);
}
// Add client version
optionalParams.attributes.Add("client_version", TutorialModuleUtil.IsOverrideDedicatedServerVersion() ? Application.version : string.Empty);
// Add preferred regions.
Dictionary<string, int> preferredRegions = RegionPreferencesHelper.GetEnabledRegions().ToDictionary(x => x.RegionCode, y => (int)y.Latency);
if (preferredRegions.Count > 0)
{
optionalParams.latencies = preferredRegions;
}
// Play with Party additional code
if (!string.IsNullOrEmpty(PartyHelper.CurrentPartyId))
{
optionalParams.sessionId = PartyHelper.CurrentPartyId;
}
matchmakingV2.CreateMatchmakingTicket(matchPool, optionalParams, OnStartMatchmakingComplete);
}
You can only delete the match ticket if the player has not yet joined the game session created by the matchmaker.
protected internal void CancelMatchmaking(string matchTicketId)
{
matchmakingV2.DeleteMatchmakingTicket(matchTicketId, result => OnCancelMatchmakingComplete(result, matchTicketId));
}
You will observe lobby updates to check for match status. When a match is discovered, you will be ready to join the game session.
protected void BindMatchFoundNotification()
{
lobby.MatchmakingV2MatchFound += OnMatchmakingFound;
}
You get notifications from the lobby when the dedicated server is available using lobby.SessionV2DsStatusChanged
.
protected void BindOnDSUpdateNotification()
{
lobby.SessionV2DsStatusChanged += OnDSStatusUpdated;
}
Implement dedicated server matchmaking
You will continue the dedicated server matchmaking implementation in the MatchmakingSessionDSWrapper_Starter.cs
file.
You need to create a matchmaking ticket based on the match pool name. Go to the
StartDSMatchmaking
and update the function using the code below. In this function, you will get the match pool name fromGameSessionConfig
that contains the configuration of a game session based on a game mode and server type. Also, this function usesStartMatchmakingAsync
fromMatchmakingSessionWrapper
, which is responsible for creating a match ticket.protected internal async void StartDSMatchmaking(InGameMode inGameMode)
{
isMatchTicketExpired = false;
selectedInGameMode = inGameMode;
Dictionary<InGameMode,
Dictionary<GameSessionServerType,
SessionV2GameSessionCreateRequest>> sessionConfig = GameSessionConfig.SessionCreateRequest;
if (!sessionConfig.TryGetValue(selectedInGameMode, out var matchTypeDict))
{
BytewarsLogger.LogWarning("Matchtype Not Found");
OnMatchmakingWithDSError.Invoke("Unable to get session configuration");
return;
}
if (GConfig.IsUsingAMS())
{
gameSessionServerType = GameSessionServerType.DedicatedServerAMS;
}
if (!matchTypeDict.TryGetValue(gameSessionServerType, out var request))
{
BytewarsLogger.LogWarning("SessionV2GameSessionCreateRequest Not Found");
OnMatchmakingWithDSError.Invoke("Unable to get matchpool");
return;
}
ConnectionHandler.Initialization();
await StartMatchmakingAsync(request.matchPool, ConnectionHandler.IsUsingLocalDS());
GameManager.Instance.OnClientLeaveSession += OnClientLeave;
}To cancel matchmaking, use this function:
protected internal void CancelDSMatchmaking()
{
if (isMatchTicketExpired)
{
BytewarsLogger.Log($"Match ticket : {matchTicket} is expired {isMatchTicketExpired}");
return;
}
CancelMatchmaking(matchTicket);
GameManager.Instance.OnClientLeaveSession -= OnClientLeave;
}Bind and unbind all the events into this class:
private void RegisterMatchmakingEventListener()
{
BindMatchmakingStartedNotification();
BindMatchFoundNotification();
BindMatchmakingExpiredNotification();
BindOnDSUpdateNotification();
BindOnInviteToGameSessionNotification();
BindOnUserJoinedSessionNotification();
OnMatchStarted += OnMatchStartedCallback;
OnMatchFound += OnMatchFoundCallback;
OnMatchExpired += OnMatchExpiredCallback;
OnMatchTicketCreated += OnMatchTicketCreatedCallback;
OnMatchTicketDeleted += OnMatchTicketDeletedCallback;
OnDSStatusUpdate += OnDSStatusUpdateCallback;
OnInviteToGameSession += OnInviteToGameSessionCallback;
OnJoinedGameSession += OnJoinedGameSessionCallback;
}private void DeRegisterMatchmakingEventListener()
{
UnBindMatchmakingStartedNotification();
UnBindMatchFoundNotification();
UnBindMatchmakingExpiredNotification();
UnBindOnDSUpdateNotification();
UnBindOnInviteToGameSessionNotification();
UnBindOnUserJoinedSessionNotification();
OnMatchStarted -= OnMatchStartedCallback;
OnMatchFound -= OnMatchFoundCallback;
OnMatchExpired -= OnMatchExpiredCallback;
OnMatchTicketCreated -= OnMatchTicketCreatedCallback;
OnMatchTicketDeleted -= OnMatchTicketDeletedCallback;
OnDSStatusUpdate -= OnDSStatusUpdateCallback;
OnInviteToGameSession -= OnInviteToGameSessionCallback;
OnJoinedGameSession -= OnJoinedGameSessionCallback;
}Once the
OnMatchfound
notification has been received from the lobby, you should join the game session. Go toStartJoinToGameSession
and update the function based on the code below. In this function, you will connect to the game session usingJoinSession
from theSessionEssentials
module.private async void StartJoinToGameSession(string sessionId)
{
OnJoinSessionCompleteEvent += OnJoiningSessionCompletedAsync;
OnMatchmakingWithDSJoinSessionStarted?.Invoke();
// Wait a moment to ensure the server information is received before joining the game session.
await UniTask.Delay(1000);
JoinSession(sessionId);
}You will leave the session once the game has ended or when the game client does not receive any dedicated server update notifications. To leave the session, you need to call
LeaveSession
from the session essentials and pass the session ID into it.public void LeaveCurrentSession()
{
LeaveSession(cachedSessionId);
Reset(true);
}In
RegisterMatchmakingEventListener
, you listen to theOnDSStatusUpdate
event that comes from AGS Lobby. Update theOnDSStatusUpdateCallback
function to receive the dedicated server status update and proceed to the game usingTravelToDS
function.private async void OnDSStatusUpdateCallback(Result<SessionV2DsStatusUpdatedNotification> result)
{
BytewarsLogger.Log($"{GameData.CachedPlayerState.playerId}");
if (!result.IsError)
{
SessionV2GameSession session = result.Value.session;
SessionV2DsInformation dsInfo = session.dsInformation;
BytewarsLogger.Log($"DS Status updated: {dsInfo.status}");
switch (dsInfo.status)
{
case SessionV2DsStatus.AVAILABLE:
OnDSAvailable?.Invoke(true);
// Wait a moment to ensure the server information is received before travel to the game server.
await UniTask.Delay(1000);
TravelToDS(session, selectedInGameMode);
UnbindMatchmakingEvent();
break;
case SessionV2DsStatus.FAILED_TO_REQUEST:
OnDSError?.Invoke($"DS Status: {dsInfo.status}");
UnbindMatchmakingEvent();
break;
case SessionV2DsStatus.REQUESTED:
break;
case SessionV2DsStatus.ENDED:
UnbindMatchmakingEvent();
break;
}
}
else
{
BytewarsLogger.LogWarning($"Error: {result.Error.Message}");
OnDSError?.Invoke($"Error: {result.Error.Message}");
}
Reset(false);
}
Handle backfill
Your game server might not always have the maximum number of players. To fill your game server with new players, you can implement backfill during matchmaking. This backfill implementation is located in the MatchmakingSessionDSWrapperServer.cs
.
A variable called serverDSHub
has been defined that will store any functions related to DS Hub notifications. Then, matchmakingV2Server
will store functions about accepting and rejecting backfill proposals. In the Awake()
function, you will see the following code:
private void Awake()
{
base.Awake();
matchmakingV2Server = AccelByteSDK.GetServerRegistry().GetApi().GetMatchmakingV2();
serverDSHub = AccelByteSDK.GetServerRegistry().GetApi().GetDsHub();
...
}
Take a look into MatchmakingSessionDSWrapperServer.BackFillProposal()
, which will appear as follows:
public void BackFillProposal()
{
serverDSHub.MatchmakingV2BackfillProposalReceived += result =>
{
if (!result.IsError)
{
BytewarsLogger.Log($"BackFillProposal");
if (!isGameStarted)
{
OnBackfillProposalReceived(result.Value, isGameStarted);
BytewarsLogger.Log($"Start back-filling process {result.Value.matchSessionId}");
}
else
{
OnBackfillProposalRejected(result.Value);
}
}
else
{
BytewarsLogger.LogWarning($"BackFillProposal {result.Error.Message}");
}
};
}
The MatchmakingSessionDSWrapperServer
has BackFillProposal
function that will later be implemented in the MultiplayerDSAMSWrapper_Starter
You already have a function to accept and reject the backfill proposal. Open the MatchmakingSessionDSWrapperServer
and you can see OnBackfillProposalReceived
.
private void OnBackfillProposalReceived(MatchmakingV2BackfillProposalNotification proposal, bool isStopBackfilling)
{
matchmakingV2Server.AcceptBackfillProposal(proposal, isStopBackfilling, result =>
{
if (!result.IsError)
{
BytewarsLogger.Log($"Back-filling accepted {!isStopBackfilling}");
}
});
}
To reject the backfill proposal, we have this function OnBackfillProposalRejected
.
private void OnBackfillProposalRejected(MatchmakingV2BackfillProposalNotification proposal)
{
matchmakingV2Server.RejectBackfillProposal(proposal, true, result =>
{
if (!result.IsError)
{
BytewarsLogger.Log($"Back-filling rejected - Game already started");
}
});
}
Compile to make sure there are no errors.
Resources
- The file used in this tutorial section is available in the ByteWars GitHub repository.