Use SDK to create match sessions - Create joinable sessions with dedicated servers - (Unity module)
Unwrap the wrapper
In this tutorial, you will learn how to implement AccelByte Gaming Services (AGS) Matchmaking using the AGS Software Development Kit (SDK). In Byte Wars, a game instance wrapper is defined in the MatchSessionDSWrapper
class. This wrapper manages game session related actions such as creating and browsing game sessions. For this tutorial, you will use the MatchSessionDSWrapper_Starter
class, which is starter version of the MatchSessionDSWrapper
class.
What's in the starter pack
To follow this tutorial, you will use the starter wrapper class called MatchSessionDSWrapper_Starter
. This class inherits from the MatchSessionEssentialsWrapper
class, which is defined in the following file:
- C# file:
Assets/Resources/Modules/Play/MatchSessionEssentials/Scripts/MatchSessionEssentialsWrapper.cs
The MatchSessionDSWrapper_Starter
class is defined in the files below:
- C# file:
Assets/Resources/Modules/Play/MatchSessionDS/Scripts/MatchSessionDSWrapper_Starter.cs
Additionally, there are model classes containing helper functions and variables, defined in the following files:
- C# file:
Assets/Resources/Modules/Play/OnlineSessionUtils/Scripts/AccelByteWarsOnlineSessionModels.cs
- C# file:
Assets/Resources/Modules/Play/MatchSessionEssentials/Scripts/MatchSessionEssentialsModels.cs
The MatchSessionEssentialsWrapper
itself inherits from the SessionEssentialsWrapper
class, which contains basic session management functionality such as joining, leaving, and rejecting session invitations. You learned how to implement this functionality in the previous Introduction to Session module. The MatchSessionEssentialsWrapper
contains several predefined helper components, including:
-
Delegates to handle game session states:
public static ResultCallback<SessionV2DsStatusUpdatedNotification> OnDSStatusChanged = delegate { };
The root parent class of MatchSessionEssentialsWrapper
named AccelByteWarsOnlineSession
class also contains several predefined helper components, including:
-
A helper method to connect the game client to the dedicated server.
public virtual void TravelToDS(SessionV2GameSession session, InGameMode gameMode)
{
SessionV2DsInformation dsInfo = session.dsInformation;
if (dsInfo == null)
{
BytewarsLogger.LogWarning("Failed to travel to dedicated server. Dedicated server information not found.");
return;
}
if (NetworkManager.Singleton.IsListening)
{
BytewarsLogger.LogWarning("Failed to travel to dedicated server. The instance is running as listen server.");
return;
}
string ip = dsInfo.server.ip;
ushort port = (ushort)dsInfo.server.port;
InitialConnectionData initialData = new InitialConnectionData()
{
sessionId = string.Empty,
inGameMode = gameMode,
serverSessionId = session.id,
userId = GameData.CachedPlayerState.PlayerId
};
GameManager.Instance.ShowTravelingLoading(() =>
{
BytewarsLogger.Log("Travel to dedicated server as client.");
GameManager.Instance.StartAsClient(ip, port, initialData);
});
}
The AccelByteWarsOnlineSessionModels
file contains several predefined helper components, including:
-
A string constant to store the session template name, based on the Session Template configured in the Admin Portal:
public const string EliminationDSAMSSessionTemplateName = "unity-elimination-ds-ams";
public const string TeamDeathmatchDSAMSSessionTemplateName = "unity-teamdeathmatch-ds-ams"; -
A helper dictionary that maps in-game modes to session request models based on the session template name:
public static readonly Dictionary<InGameMode, Dictionary<GameSessionServerType, SessionV2GameSessionCreateRequest>> SessionCreateRequestModels = new()
{
...
{
InGameMode.CreateMatchElimination, new()
{
...
{
GameSessionServerType.DedicatedServerAMS,
new SessionV2GameSessionCreateRequest()
{
type = SessionConfigurationTemplateType.DS,
joinability = SessionV2Joinability.OPEN,
configurationName = EliminationDSAMSSessionTemplateName,
matchPool = EliminationDSAMSSessionTemplateName,
clientVersion = ClientVersion
}
}
}
},
{
InGameMode.CreateMatchTeamDeathmatch, new()
{
...
{
GameSessionServerType.DedicatedServerAMS,
new SessionV2GameSessionCreateRequest()
{
type = SessionConfigurationTemplateType.DS,
joinability = SessionV2Joinability.OPEN,
configurationName = TeamDeathmatchDSAMSSessionTemplateName,
matchPool = TeamDeathmatchDSAMSSessionTemplateName,
clientVersion = ClientVersion
}
}
}
}
...
}; -
A helper function to retrieve the session request model by in-game mode:
public static SessionV2GameSessionCreateRequest GetGameSessionRequestModel(
InGameMode gameMode,
GameSessionServerType serverType)
{
if (!SessionCreateRequestModels.TryGetValue(gameMode, out var matchTypeDict))
{
return null;
}
matchTypeDict.TryGetValue(serverType, out var request);
return request;
}
The MatchSessionEssentialsModels
file contains several predefined helper components, including:
-
Custom constants to be used to filter game session queries based on the server type.
public static readonly string MatchSessionAttributeKey = "match_session_type";
public static readonly string MatchSessionDSAttributeValue = "unity_match_session_ds";
public static readonly Dictionary<string, object> MatchSessionDSAttribute = new()
{
{ MatchSessionAttributeKey, MatchSessionDSAttributeValue }
}; -
Helper class to construct the browse game sessions result.
public class BrowseSessionModel
{
public SessionV2GameSession Session { get; private set; }
public AccountUserPlatformData Owner { get; private set; }
public int CurrentMemberCount { get; private set; }
public int MaxMemberCount { get; private set; }
public InGameMode GameMode { get; private set; }
public GameSessionServerType ServerType { get; private set; }
public BrowseSessionModel()
{
Session = null;
Owner = null;
CurrentMemberCount = 0;
MaxMemberCount = 0;
GameMode = InGameMode.None;
ServerType = GameSessionServerType.None;
}
public BrowseSessionModel(SessionV2GameSession session, AccountUserPlatformData owner)
{
Session = session;
Owner = owner;
CurrentMemberCount = session.members.Count(member => member.status == SessionV2MemberStatus.JOINED);
MaxMemberCount = session.configuration.maxPlayers;
GameMode = GetGameSessionGameMode(session);
ServerType = GetGameSessionServerType(session);
}
}
public class BrowseSessionResult
{
public readonly Dictionary<string, object> QueryAttribute;
public readonly PaginatedResponse<BrowseSessionModel> Result;
public BrowseSessionResult(
Dictionary<string, object> queryAttribute,
PaginatedResponse<BrowseSessionModel> result)
{
QueryAttribute = queryAttribute;
Result = result;
}
}
Create match session
This section will guide you on how to create game session with dedicated server using the AGS SDK.
-
Open the
MatchSessionDSWrapper_Starter
class and create the following function. This function enriches the session creation request by adding several attributes. Then, it sends the session creation request by calling the parent class function, which is what you learned from the Introduction to Session module. Once the request is completed, it checks whether the dedicated server is ready to travel. From the code below, you may notice that it also enriches the request by adding several values to the optional attributes. Here's what those attributes do:serverName
: The attribute used to set the requested local server name for testing the game with a local dedicated server.clientVersion
: The attribute used to select the build config name for testing using the AMS development fleet.requestedRegions
: The attribute used to set the requested regions so the session will use the AMS dedicated server in a specific region.members
: The attribute used to add session members. In this case, it is used to add party members to join the game session.attributes
: The custom attribute list you can add for your game session. In this case, it is used to filter the session by server type using the customMatchSessionDSAttribute
attribute.
public override void CreateGameSession(
SessionV2GameSessionCreateRequest request,
ResultCallback<SessionV2GameSession> onComplete)
{
// Add local server name.
if (string.IsNullOrEmpty(ConnectionHandler.LocalServerName))
{
request.serverName = ConnectionHandler.LocalServerName;
}
// Add client version
request.clientVersion = AccelByteWarsOnlineSessionModels.ClientVersion;
// Add preferred regions.
string[] preferredRegions =
RegionPreferencesModels.GetEnabledRegions().OrderBy(x => x.Latency).Select(x => x.RegionCode).ToArray();
if (preferredRegions.Length > 0)
{
request.requestedRegions = preferredRegions;
}
// Add party session id for playing with party feature.
SessionV2PartySession partySession = PartyEssentialsModels.PartyHelper.CurrentPartySession;
if (partySession != null && !string.IsNullOrEmpty(partySession.id))
{
request.members = partySession.members;
}
// Add custom attribute as filter for session browser.
request.attributes = MatchSessionDSAttribute;
// Reregister delegate to listen for dedicated server status changed event.
OnDSStatusChanged -= OnDSStatusChangedReceived;
OnDSStatusChanged += OnDSStatusChangedReceived;
base.CreateGameSession(request, (result) =>
{
// If dedicated server is ready, broadcast the dedicated server status changed event.
if (!result.IsError && result.Value.dsInformation.StatusV2 == SessionV2DsStatus.AVAILABLE)
{
OnDSStatusChanged?.Invoke(Result<SessionV2DsStatusUpdatedNotification>.CreateOk(new()
{
session = result.Value,
sessionId = result.Value.id,
error = string.Empty
}));
}
onComplete?.Invoke(result);
});
}
Join match session
This section will guide you on how to join game session with dedicated server using the AGS SDK.
-
Open the
MatchSessionDSWrapper_Starter
class and create the following function. This function sends the join session request by calling the parent class function, which is what you learned from the Introduction to Session module. Once the request is completed, it checks whether the dedicated server is ready to travel.public override void JoinGameSession(
string sessionId,
ResultCallback<SessionV2GameSession> onComplete)
{
// Reregister delegate to listen for dedicated server status changed event.
OnDSStatusChanged -= OnDSStatusChangedReceived;
OnDSStatusChanged += OnDSStatusChangedReceived;
base.JoinGameSession(sessionId, (result) =>
{
// If dedicated server is ready, broadcast the dedicated server status changed event.
if (!result.IsError && result.Value.dsInformation.StatusV2 == SessionV2DsStatus.AVAILABLE)
{
OnDSStatusChanged?.Invoke(Result<SessionV2DsStatusUpdatedNotification>.CreateOk(new()
{
session = result.Value,
sessionId = result.Value.id,
error = string.Empty
}));
}
onComplete?.Invoke(result);
});
}
Browse match sessions
This section will guide you on how to query game sessions using the AGS SDK.
-
Open the
MatchSessionDSWrapper_Starter
class and create the following function to query game sessions based on specific attribute. In this case, it filter only game session that has dedicated server type using theMatchSessionDSAttribute
. If it has specific page to load, it tried to parse the pagination URL and add it to the query attribute. Finally, it starts to query the game session and return the result along with the session owner details.public override void BrowseGameSessions(
string pageUrl,
ResultCallback<BrowseSessionResult> onComplete)
{
// If provided, try parse the pagination URL parameters and add them to the query attributes.
Dictionary<string, object> queryAttributes = new(MatchSessionDSAttribute);
if (!string.IsNullOrEmpty(pageUrl))
{
Dictionary<string, object> queryParams = ParseQuerySessionParams(pageUrl);
if (queryParams == null)
{
BytewarsLogger.LogWarning($"Failed to find game sessions. Pagination URL is invalid.");
onComplete?.Invoke(Result<BrowseSessionResult>.CreateError(ErrorCode.InvalidArgument, InvalidSessionPaginationMessage));
return;
}
queryParams.ToList().ForEach(x => queryAttributes[x.Key] = x.Value);
}
// Query game sessions and filter them based on the custom attributes.
Session.QueryGameSession(queryAttributes, sessionResult =>
{
if (sessionResult.IsError)
{
BytewarsLogger.LogWarning(
$"Failed to query game sessions. " +
$"Error {sessionResult.Error.Code}: {sessionResult.Error.Message}");
onComplete?.Invoke(Result<BrowseSessionResult>.CreateError(sessionResult.Error));
return;
}
BytewarsLogger.Log("Success to query game sessions.");
// Filter based on the enabled preferred regions.
List<SessionV2GameSession> filteredSessions =
RegionPreferencesModels.FilterEnabledRegionGameSession(sessionResult.Value.data.ToList());
// Return immediately if result is empty.
if (filteredSessions.Count <= 0)
{
onComplete?.Invoke(Result<BrowseSessionResult>.CreateOk(
new BrowseSessionResult(
queryAttributes,
new PaginatedResponse<BrowseSessionModel>()
{
data = Array.Empty<BrowseSessionModel>(),
paging = sessionResult.Value.paging
})));
return;
}
// Query the session owner user information.
string[] sessionOwners = filteredSessions.Select(x => x.createdBy).ToArray();
User.GetUserOtherPlatformBasicPublicInfo("ACCELBYTE", sessionOwners, (userResult) =>
{
if (userResult.IsError)
{
BytewarsLogger.LogWarning(
$"Failed to query game sessions. " +
$"Error {userResult.Error.Code}: {userResult.Error.Message}");
onComplete?.Invoke(Result<BrowseSessionResult>.CreateError(userResult.Error));
return;
}
// Construct session results.
List<BrowseSessionModel> result = new();
foreach(SessionV2GameSession session in filteredSessions)
{
AccountUserPlatformData ownerData = userResult.Value.Data.FirstOrDefault(x => x.UserId == session.createdBy);
if (session != null && ownerData != null)
{
result.Add(new(session, ownerData));
}
}
// Return results.
onComplete?.Invoke(Result<BrowseSessionResult>.CreateOk(
new BrowseSessionResult(
queryAttributes,
new PaginatedResponse<BrowseSessionModel>()
{
data = result.ToArray(),
paging = sessionResult.Value.paging
})));
});
});
}
Listen to match session events
This section will guide you on how to listen on the game session events.
-
Open the
MatchSessionDSWrapper_Starter
class and replace the predefinedAwake()
function with the code below. This code binds delegates to listen for events, such when the backend finds a dedicated server to serve the session and when the browse session menu is initiated to display the available game sessions to join.protected override void Awake()
{
base.Awake();
BrowseMatchMenu.OnBrowseDSMatch += BrowseGameSessions;
Lobby.SessionV2DsStatusChanged += (result) => OnDSStatusChanged?.Invoke(result);
} -
Then, create a new function to handle when the dedicated server status is received. If the dedicated server is ready, the game client will travel to it. Otherwise, it will throw errors based on the status types.
private void OnDSStatusChangedReceived(Result<SessionV2DsStatusUpdatedNotification> result)
{
if (result.IsError)
{
OnDSStatusChanged -= OnDSStatusChangedReceived;
BytewarsLogger.LogWarning(
$"Failed to handle dedicated server status changed event. " +
$"Error {result.Error.Code}: {result.Error.Message}");
return;
}
SessionV2GameSession session = result.Value.session;
SessionV2DsInformation dsInfo = session.dsInformation;
// Check if the requested game mode is supported.
InGameMode requestedGameMode = GetGameSessionGameMode(session);
if (requestedGameMode == InGameMode.None)
{
BytewarsLogger.LogWarning(
$"Failed to handle dedicated server status changed event. " +
$"Session's game mode is not supported by the game.");
OnDSStatusChanged -= OnDSStatusChangedReceived;
OnDSStatusChanged?.Invoke(
Result<SessionV2DsStatusUpdatedNotification>.
CreateError(ErrorCode.NotAcceptable, InvalidSessionTypeMessage));
return;
}
// Check the dedicated server status.
switch (dsInfo.StatusV2)
{
case SessionV2DsStatus.AVAILABLE:
OnDSStatusChanged -= OnDSStatusChangedReceived;
TravelToDS(session, requestedGameMode);
break;
case SessionV2DsStatus.FAILED_TO_REQUEST:
case SessionV2DsStatus.ENDED:
case SessionV2DsStatus.UNKNOWN:
OnDSStatusChanged -= OnDSStatusChangedReceived;
BytewarsLogger.LogWarning(
$"Failed to handle dedicated server status changed event. " +
$"Session failed to request for dedicated server due to unknown reason.");
break;
default:
BytewarsLogger.Log($"Received dedicated server status change. Status: {dsInfo.StatusV2}");
break;
}
}
Resources
-
The files used in this tutorial are available in the Unity Byte Wars GitHub repository.
- Assets/Resources/Modules/Play/OnlineSessionUtils/Scripts/AccelByteWarsOnlineSession.cs
- Assets/Resources/Modules/Play/OnlineSessionUtils/Scripts/AccelByteWarsOnlineSessionModels.cs
- Assets/Resources/Modules/Play/SessionEssentials/Scripts/SessionEssentialsWrapper.cs
- Assets/Resources/Modules/Play/MatchSessionEssentials/Scripts/MatchSessionEssentialsWrapper.cs
- Assets/Resources/Modules/Play/MatchSessionEssentials/Scripts/MatchSessionEssentialsModels.cs
- Assets/Resources/Modules/Play/MatchSessionDS/Scripts/MatchSessionDSWrapper_Starter.cs