Use the SDK for peer-to-peer matchmaking - Quick match with peer-to-peer - (Unity module)
Browsing P2P sessions is not supported in WebGL builds due to the AccelByte Gaming Services (AGS) SDK for Unity lacking the P2P functionality for WebGL.
Unwrap the wrapper
After setting up a match pool, you will call the AGS Game SDK for peer-to-peer (P2P) matchmaking. This is done in the MatchmakingSessionP2PWrapper
class in the StartP2PMatchmaking
function to initiate the matchmaking process using P2P, since you will use the match pool name you set up previously with the P2P in its session template.
The MatchmakingSessionP2PWrapper
is a subclass of MatchmakingSessionWrapper
, and it uses functions that are predefined in the MatchmakingSessionWrapper
parent class.
What's in the starter pack
The P2PHelper.cs
class provides several functions for starting as a P2P host and joining a P2P session.
- P2PHelper.cs: A C# script that contains most of the P2P implementation.
- CS file:
Assets/Resources/Modules/MatchmakingSessionP2P/Scripts/P2PHelper.cs
- CS file:
There are two variables in P2PHelper.cs
: transportManager
is a custom network transport from the AGS network transport, and networkManager
to hold a reference to the NetworkManager
class.
private static AccelByteNetworkTransportManager transportManager;
private static NetworkManager networkManager;
The Init
function initializes the custom network transport.
private static void Init()
{
if (transportManager != null)
{
return;
}
networkManager = NetworkManager.Singleton;
transportManager = networkManager.gameObject.AddComponent<AccelByteNetworkTransportManager>();
ApiClient apiClient = AccelByteSDK.GetClientRegistry().GetApi();
transportManager.Initialize(apiClient);
}
To set up the network transport, the SetP2PNetworkTransport
function is used. In this function, you will initialize the AccelByteNetworkTransportManager
and use it as a network transport to establish a P2P connection. You need the match session ID in connection data.
private static void SetP2PNetworkTransport(InGameMode gameMode, string matchSessionId)
{
Init();
InitialConnectionData data = new InitialConnectionData() { inGameMode = gameMode, serverSessionId = matchSessionId };
networkManager.NetworkConfig.ConnectionData = GameUtility.ToByteArray(data);
networkManager.NetworkConfig.NetworkTransport = transportManager;
}
To start as a P2P host, StartAsHost
is used. This function will start the network manager that has been set up with the in-game mode and match session ID, and then it starts the network manager as a host.
public static async void StartAsHost(InGameMode gameMode, string matchSessionId)
{
await GameManager.ShowTravelingLoading();
GameManager.Instance.ResetCache();
GameData.ServerType = ServerType.OnlinePeer2Peer;
SetP2PNetworkTransport(gameMode, matchSessionId);
networkManager.StartHost();
BytewarsLogger.Log($"Start P2P Host");
GameManager.StartListenNetworkSceneEvent();
}
To start as a client in a P2P network, the StartAsP2PClient
function has been defined. This function needs the host user ID, which it will get from the game session.
public static async void StartAsP2PClient(string hostUserId, InGameMode gameMode, string matchSessionId)
{
await GameManager.ShowTravelingLoading();
GameManager.Instance.ResetCache();
GameData.ServerType = ServerType.OnlinePeer2Peer;
SetP2PNetworkTransport(gameMode, matchSessionId);
transportManager.SetTargetHostUserId(hostUserId);
networkManager.StartClient();
BytewarsLogger.Log($"Start P2P Client hostUserId: {hostUserId}");
}
What is in MatchmakingSessionWrapper
The MatchmakingSessionWrapper
has been prepared as a parent class containing predefined code that leverages the AGS Game SDK to perform various matchmaking-related functions.
MatchmakingV2
class is from the 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 the match status. When a match is discovered, you will be ready to join the game session.
protected void BindMatchFoundNotification()
{
lobby.MatchmakingV2MatchFound += OnMatchmakingFound;
}
Implement peer-to-peer matchmaking
You will continue the P2p matchmaking implementation in the MatchmakingSessionP2PWrapper_Starter.cs
file.
You need to create a matchmaking ticket based on the match pool name. Go to the
StartP2PMatchmaking
then update the function in the code below. In this function, you will get the match pool name fromGameSessionConfig
that contains the configuration of game session based on game mode and server type. This function also usesStartMatchmakingAsync
fromMatchmakingSessionWrapper
that is responsible to create the match ticket.protected internal async void StartP2PMatchmaking(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");
return;
}
if (!matchTypeDict.TryGetValue(gameSessionServerType, out var request))
{
BytewarsLogger.LogWarning("SessionV2GameSessionCreateRequest Not Found");
return;
}
ConnectionHandler.Initialization();
await StartMatchmakingAsync(request.matchPool, ConnectionHandler.IsUsingLocalDS());
GameManager.Instance.OnClientLeaveSession += OnClientLeave;
}To cancel matchmaking, use this function:
protected internal void CancelP2PMatchmaking()
{
if (isMatchTicketExpired)
{
BytewarsLogger.Log($"match ticket : {matchTicket} is expired {isMatchTicketExpired}");
return;
}
CancelMatchmaking(matchTicket);
GameManager.Instance.OnClientLeaveSession -= OnClientLeave;
}You will bind and unbind all the events in this class:
private void RegisterMatchmakingEventListener()
{
BindMatchmakingStartedNotification();
BindMatchFoundNotification();
BindMatchmakingExpiredNotification();
BindOnInviteToGameSessionNotification();
BindOnUserJoinedSessionNotification();
OnMatchStarted += OnMatchStartedCallback;
OnMatchFound += OnMatchFoundCallback;
OnMatchExpired += OnMatchExpiredCallback;
OnMatchTicketCreated += OnMatchTicketCreatedCallback;
OnMatchTicketDeleted += OnMatchTicketDeletedCallback;
OnInviteToGameSession += OnInviteToGameSessionCallback;
OnJoinedGameSession += OnJoinedGameSessionCallback;
}private void DeRegisterMatchmakingEventListener()
{
UnBindMatchmakingStartedNotification();
UnBindMatchFoundNotification();
UnBindMatchmakingExpiredNotification();
UnBindOnInviteToGameSessionNotification();
UnBindOnUserJoinedSessionNotification();
OnMatchStarted -= OnMatchStartedCallback;
OnMatchFound -= OnMatchFoundCallback;
OnMatchExpired -= OnMatchExpiredCallback;
OnMatchTicketCreated -= OnMatchTicketCreatedCallback;
OnMatchTicketDeleted -= OnMatchTicketDeletedCallback;
OnInviteToGameSession -= OnInviteToGameSessionCallback;
OnJoinedGameSession -= OnJoinedGameSessionCallback;
}Once you receive the
OnMatchfound
notification from the lobby, you will 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;
OnMatchmakingWithP2PJoinSessionStarted?.Invoke();
// Wait a moment to ensure the host information is received before join the game session.
await Task.Delay(1000);
JoinSession(sessionId);
}You will leave the session once the game has ended or when the game client does not receive any P2P 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);
}To handle the player joining the game session, create a callback function. Within this function, check the P2P status. You can initiate the P2P connection once you receive the status
NEED_TO_REQUEST
from the backend.private async void OnJoiningSessionCompletedAsync(Result<SessionV2GameSession> result)
{
// Wait a moment to ensure the host information is received.
await Task.Delay(1000);
if (result.IsError)
{
BytewarsLogger.Log($"Error : {result.Error.Message}");
OnMatchmakingWithP2PError?.Invoke(result.Error.Message);
return;
}
OnJoinSessionCompleteEvent -= OnJoiningSessionCompletedAsync;
OnGetSessionDetailsCompleteEvent -= OnJoiningSessionCompletedAsync;
BytewarsLogger.Log($"Joined to session : {cachedSessionId} - Waiting DS Status");
bool isLeader = result.Value.leaderId == GameData.CachedPlayerState.playerId;
OnMatchmakingWithP2PJoinSessionCompleted?.Invoke(isLeader);
GetP2PStatus(result.Value);
}private async void GetP2PStatus(SessionV2GameSession session)
{
SessionV2DsInformation dsInfo = session.dsInformation;
switch (dsInfo.status)
{
case SessionV2DsStatus.NEED_TO_REQUEST:
BytewarsLogger.LogWarning($"DS Status: {dsInfo.status}");
// Wait a moment to ensure the host information is received before connect to the host.
await Task.Delay(1000);
StartP2PConnection(GameData.CachedPlayerState.playerId, session);
UnbindMatchmakingEvent();
break;
}
}Handling the P2P connection requires distinguishing between the host and client roles. To address this, create a function called
StartP2PConnection
. Within this function, designate the leader as the host in the P2P connection. Substitute the function's current code with this revised version.private void StartP2PConnection(string currentUserId, SessionV2GameSession gameSession)
{
if (string.IsNullOrWhiteSpace(gameSession.leaderId))
{
OnMatchmakingWithP2PError?.Invoke($"GameSession.id {gameSession.id} has empty/null leader id: {gameSession.leaderId}");
Reset(false);
return;
}
if (currentUserId.Equals(gameSession.leaderId))
{
GameData.ServerSessionID = gameSession.id;
P2PHelper.StartAsHost(selectedInGameMode, gameSession.id);
}
else
{
P2PHelper.StartAsP2PClient(gameSession.leaderId, selectedInGameMode, gameSession.id);
}
Reset(false);
}
Resources
- The file used in this tutorial section is available in the ByteWars GitHub repository.