Skip to main content

Use the SDK for dedicated server matchmaking - Quick match with dedicated servers - (Unity module)

Last updated on November 25, 2024

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.

  1. 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 from GameSessionConfig that contains the configuration of a game session based on a game mode and server type. Also, this function uses StartMatchmakingAsync from MatchmakingSessionWrapper, 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;
    }
  2. 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;
    }
  3. 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;
    }
  4. Once the OnMatchfound notification has been received from the lobby, you should join the game session. Go to StartJoinToGameSession and update the function based on the code below. In this function, you will connect to the game session using JoinSession from the SessionEssentials 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);
    }
  5. 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);
    }

  6. In RegisterMatchmakingEventListener, you listen to the OnDSStatusUpdate event that comes from AGS Lobby. Update the OnDSStatusUpdateCallback function to receive the dedicated server status update and proceed to the game using TravelToDS 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