Use Photon Multiplayer with AGS Matchmaking
Overview
AccelByte Gaming Services (AGS) uses a uniquely generated session ID for the multiplayer network room name, which allows for the use of Photon Multiplayer. The services used to make this possible are AGS Authentication, Lobby (for matchmaking websocket events), Matchmaking (for matchmaking API), and Session (for tracking game sessions).
This article provides information on how Photon works with AGS, along with steps on how to integrate it.
Prerequisites
To complete all the steps in this article, you will need:
- AGS Authentication, Lobby, Matchmaking, and Session have all been integrated into your game and configured properly.
Photon with AGS flow
See the following flow diagram to understand how Photon works with AGS:
Authentication
To be able to use the services within AGS required for matchmaking, the player must be authenticated and logged in. This can be done using the User service wrapper.
public void Login()
{
var userWrapper = AccelByteSDK.GetClientRegistry().GetApi().GetUser();
userWrapper.LoginWithDeviceId((Result<TokenData, OAuthError> result) =>
{
if (result.IsError)
{
Debug.Log($"Error- [{result.Error.error}]{result.Error.error_description}");
// Handle login error
}
// Login success, continue with flow.
});
}
Connect and set up relevant Lobby events
AGS Matchmaking uses websocket for events. The websocket connection and adding listeners to events is done through the AGS Lobby service wrapper.
We strongly recommend connecting to the Lobby service immediately after successful authentication. This practice helps prevent race conditions and ensures accurate CCU metrics tracking. Use the following code to establish the Lobby connection:
public void ConnectAndSetupLobby()
{
var lobbyWrapper = AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
// It is strongly advised to connect the lobby service as soon as the user
// has successfully logged in to be able to avoid any race condition related to other
// services and lobby events, and to be able to track CCU metrics.
lobbyWrapper.Connect();
// Make sure that lobbyWrapper.IsConnected == true before calling StartMatchmaking()
// Alternatively, you can also trigger StartMatchmaking via an event
lobbyWrapper.Connected += () =>
{
StartMatchmaking();
}
// Matchmaking events
lobbyWrapper.MatchmakingV2MatchmakingStarted += OnMatchmakingStarted;
lobbyWrapper.MatchmakingV2MatchFound += OnMatchFound;
lobbyWrapper.MatchmakingV2TicketExpired += OnTicketExpired;
lobbyWrapper.SessionV2InvitedUserToGameSession += OnReceivedSessionInvite;
}
Start matchmaking
To start matchmaking, you will need to create a matchmaking ticket. This can be done using the AGS Matchmaking service wrapper.
public void StartMatchmaking()
{
var lobbyWrapper = AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
if (!lobbyWrapper.IsConnected)
{
Debug.LogWarning("Lobby is not connected, unable to start matchmaking");
return;
}
var matchmakingWrapper = AccelByteSDK.GetClientRegistry().GetApi().GetMatchmakingV2();
var optionalParams = new MatchmakingV2CreateTicketRequestOptionalParams()
{
// Keep this null if the player is solo.
// If the player is in a party, put the party session ID here.
sessionId = null,
// Keep this null if you don't plan for the player
// to be able to select/prioritize gameplay options.
// You can put any value here and matchmaking will
// try to match players with the same attribute values.
attributes = new Dictionary<string, object>()
{
{ "map", 1 },
{ "difficulty", "normal" }
}
};
matchmakingWrapper.CreateMatchmakingTicket("yourMatchPoolName", optionalParams, result =>
{
if (result.IsError)
{
Debug.Log($"Error- [{result.Error.Code}] {result.Error.Message}");
// Handle matchmaking API error here
}
// Matchmaking started, continue with flow.
});
}
Photon integration via matchmaking event callbacks
These are the suggested implementations for the callbacks to the AGS Lobby matchmaking events from above. You are free to extend or modify them as per your needs. Photon integration is done after the player has joined the AGS game session (end of matchmaking process, OnReceivedSessionInvite
). You will be using the game session's ID as the room name to create or join depending on your chosen Photon framework.
private void OnMatchmakingStarted(Result<MatchmakingV2MatchmakingStartedNotification> result)
{
// Called when the party leader has successfully started matchmaking.
// Do your setup for other party members here.
// (for example, notify players that matchmaking has started, show timer UI, etc.)
if (result.IsError)
{
Debug.Log($"Error- [{result.Error.Code}] {result.Error.Message}");
// Handle matchmaking event error here
return;
}
}
private void OnMatchFound(Result<MatchmakingV2MatchFoundNotification> result)
{
// Called when a match has been found.
// Do your setup/preparations here.
// (for example, preloading assets in background, show match found UI, etc.)
if (result.IsError)
{
Debug.Log($"Error- [{result.Error.Code}] {result.Error.Message}");
// Handle matchmaking event error here
return;
}
}
private void OnTicketExpired(Result<MatchmakingV2TicketExpiredNotification> result)
{
// Called when ticket has reached the set
// expiration timeout duration without finding a match.
// You can safely call StartMatchmaking() again here
// to automatically restart the process (for party leader only, if in a party),
// or notify the players that no match has been found (if they ever have restricting attributes).
if (result.IsError)
{
Debug.Log($"Error- [{result.Error.Code}] {result.Error.Message}");
// Handle matchmaking event error here
return;
}
StartMatchmaking();
}
private void OnReceivedSessionInvite(Result<SessionV2GameInvitationNotification> result)
{
// Called when a match session is ready and has invited the player to join.
// You can use the sessionId from the result as the room
// name for your Photon room to join.
if (result.IsError)
{
Debug.Log($"Error- [{result.Error.Code}] {result.Error.Message}");
// Handle matchmaking event error here
return;
}
// Store this somewhere persistent
// as it will be used later on to clean up the session after the game ends or the player has left/disconnected.
string sessionId = result.Value.sessionId;
var sessionWrapper = AccelByteSDK.GetClientRegistry().GetApi().GetSession();
sessionWrapper.JoinGameSession(sessionId, joinResult =>
{
if (joinResult.IsError)
{
Debug.Log($"Error- [{result.Error.Code}] {result.Error.Message}");
// Handle session API error here
return;
}
// Join session success, proceed with Photon networking.
// Photon PUN:
// PhotonNetwork.JoinOrCreateRoom(sessionId, roomOptions, typedLobby);
// Photon Realtime / Quantum:
// loadBalancingClient.OpJoinOrCreateRoom(new EnterRoomParams {
// RoomName = accelByteSessionIdFromLobbyEvent
// });
// Photon Fusion:
// networkRunner.StartGame(new StartGameArgs {
// SessionName = accelByteSessionIdFromLobbyEvent
// });
});
}
Session cleanup
Once your game has finished or if the player has left/disconnected, be sure to also call leave on the AccelByte session service wrapper for clean up.
private void OnGameEnd()
{
var sessionWrapper = AccelByteSDK.GetClientRegistry().GetApi().GetSession();
sessionWrapper.LeaveGameSession("sessionIdFromMatchmaking", result =>
{
if (result.IsError)
{
Debug.Log($"Error- [{result.Error.Code}] {result.Error.Message}");
// Handle session API error here
return;
}
});
}
Lobby disconnection
Once connected, disconnect the Lobby service only when the user logs out or the game is exiting or crashing. Use this code:
User userWrapper = AccelByteSDK.GetClientRegistry().GetApi().GetUser();
Lobby lobbyWrapper = AccelByteSDK.GetClientRegistry().GetApi().GetLobby();
void Logout()
{
userWrapper.Logout(logoutResult =>
{
if (logoutResult.IsError)
{
// Handle logout error here
return;
}
lobby.Disconnect();
});
}
void OnApplicationQuit()
{
lobbyWrapper.Disconnect();
}