Integrate traditional and role-based matchmaking with the lobby system.
PlayFlow’s matchmaking system supports both traditional symmetric matchmaking (like Fortnite, Counter-Strike) and advanced role-based asymmetric matchmaking (like League of Legends, Overwatch, Dead by Daylight). The system seamlessly integrates with lobbies to provide automated matching based on skill, region, roles, and custom criteria.
For role-based modes, also specify your role preferences:
Copy
public class RoleBasedMatchmaking : MonoBehaviour{ // Single specific role (MOBA player who only plays one lane) public void SetSpecificRole(string role, int playerMMR) { var matchmakingData = new Dictionary<string, object> { { "role", role }, // e.g., "jungle", "mid", "support" { "mmr", playerMMR }, { "regions", new List<string> { "us-east", "us-west" } } }; PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(matchmakingData); } // Priority array of roles (flexible fill player) public void SetFlexibleRoles(int playerMMR) { var matchmakingData = new Dictionary<string, object> { { "role", new List<string> { "support", "adc", "mid" } }, // Preference order { "mmr", playerMMR }, { "regions", new List<string> { "us-east", "eu-west" } } }; PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(matchmakingData); } // Spectator role (excluded from matchmaking) public void SetSpectatorRole() { var matchmakingData = new Dictionary<string, object> { { "role", "Spectator" }, // Will be excluded from team requirements { "regions", new List<string> { "us-east" } } }; PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(matchmakingData); }}
Once the player’s data is set, the lobby host can start the search by calling FindMatch. The mode parameter must match the name of the matchmaking mode you created in the dashboard.
Copy
public class MatchmakingStarter : MonoBehaviour{ // Traditional matchmaking (Competitive5v5, Duels, BattleRoyale, etc.) public void StartTraditionalSearch(string mode = "Competitive5v5") { var manager = PlayFlowLobbyManagerV2.Instance; if (!manager.IsInLobby || !manager.IsHost) { Debug.LogWarning("Must be the host of a lobby to start matchmaking."); return; } manager.FindMatch(mode, onSuccess: (lobby) => { Debug.Log($"Successfully entered the '{lobby.matchmakingMode}' queue."); Debug.Log($"Matchmaking ticket: {lobby.matchmakingTicketId}"); // Update your UI to show a "searching..." state }, onError: (error) => { Debug.LogError($"Failed to start matchmaking: {error}"); } ); } // Role-based matchmaking (RankedRoleQueue, AsymmetricHorror, etc.) public void StartRoleBasedSearch(string mode = "RankedRoleQueue") { var manager = PlayFlowLobbyManagerV2.Instance; if (!manager.IsInLobby || !manager.IsHost) { Debug.LogWarning("Must be the host of a lobby to start matchmaking."); return; } // Ensure all players have set their roles var currentLobby = manager.CurrentLobby; bool allPlayersHaveRoles = true; foreach (var playerId in currentLobby.players) { if (currentLobby.lobbyStateRealTime.TryGetValue(playerId, out var playerState)) { if (!playerState.ContainsKey("role")) { Debug.LogWarning($"Player {playerId} has not set their role!"); allPlayersHaveRoles = false; } } } if (!allPlayersHaveRoles) { Debug.LogError("All players must set their role before matchmaking!"); return; } manager.FindMatch(mode, onSuccess: (lobby) => { Debug.Log($"Entered role-based queue: {lobby.matchmakingMode}"); // Show searching UI with role information }, onError: (error) => { Debug.LogError($"Failed to start role-based matchmaking: {error}"); } ); }}
After starting the search, you need to listen for events to know what happens next.
OnMatchmakingStarted: Confirms you have entered the queue
OnMatchFound: The most important event! This fires when a match is found
OnMatchmakingCancelled: Fires if you cancel the search
OnMatchRunning: Fires when the server is ready with connection details
OnMatchServerDetailsReady: Provides full server details including team assignments
Copy
using Newtonsoft.Json;void Start(){ var events = PlayFlowLobbyManagerV2.Instance.Events; events.OnMatchmakingStarted.AddListener(HandleMatchmakingStarted); events.OnMatchFound.AddListener(HandleMatchFound); events.OnMatchRunning.AddListener(HandleMatchRunning); events.OnMatchServerDetailsReady.AddListener(HandleServerDetailsReady); events.OnMatchmakingCancelled.AddListener(HandleMatchmakingCancelled);}private void HandleMatchmakingStarted(Lobby lobby){ Debug.Log($"Entered queue for mode: {lobby.matchmakingMode}"); Debug.Log($"Ticket ID: {lobby.matchmakingTicketId}");}private void HandleMatchFound(Lobby lobby){ Debug.Log("Match has been found! A server is being prepared."); // For role-based matches, you can access matchmaking data if (lobby.matchmakingData != null) { Debug.Log($"Match data: {JsonConvert.SerializeObject(lobby.matchmakingData)}"); } // Update your UI to "Connecting..." state}private void HandleMatchRunning(ConnectionInfo connectionInfo){ Debug.Log($"Server ready at {connectionInfo.Ip}:{connectionInfo.Port}"); // Connect your game client MyNetworkManager.Connect(connectionInfo.Ip, connectionInfo.Port);}private void HandleServerDetailsReady(List<PortMappingInfo> portMappings){ Debug.Log("Full server details received."); var currentLobby = PlayFlowLobbyManagerV2.Instance.CurrentLobby; if (currentLobby?.gameServer?.custom_data != null) { var customData = currentLobby.gameServer.custom_data; // Access team information for role-based matches if (customData.ContainsKey("teams")) { var teams = customData["teams"] as List<object>; foreach (dynamic team in teams) { Debug.Log($"Team {team.team_id}:"); foreach (dynamic lobbyInfo in team.lobbies) { foreach (var playerEntry in lobbyInfo.player_states) { string playerId = playerEntry.Key; dynamic playerState = playerEntry.Value; Debug.Log($" Player {playerId}: Role = {playerState.role}"); } } } } // Check which team you're on var myPlayerId = PlayFlowLobbyManagerV2.Instance.PlayerId; Debug.Log($"My player ID: {myPlayerId}"); }}private void HandleMatchmakingCancelled(Lobby lobby){ Debug.Log("Matchmaking was cancelled."); // Return to lobby screen}
The host can cancel the matchmaking search at any time by calling CancelMatchmaking.
Copy
public void CancelSearch(){ var manager = PlayFlowLobbyManagerV2.Instance; if (!manager.IsHost || manager.CurrentLobby?.status != "in_queue") { return; // Can only cancel if you are the host and in the queue } manager.CancelMatchmaking( onSuccess: (lobby) => { Debug.Log("Successfully cancelled matchmaking."); }, onError: (error) => { Debug.LogError($"Failed to cancel matchmaking: {error}"); } );}
The role-based matchmaking system uses an intelligent priority-based algorithm to assign players to roles. Roles can be provided as either a single string or a string array for flexibility.
Key Principle: Players with fewer role options get priority assignment to ensure everyone gets a suitable role. This is NOT random - it’s deterministic and fair.
1. Specificity First
Players with fewer role options are assigned before flexible players:
Copy
// Assignment priority (highest to lowest):Player A: ["tank"] // 1 option - HIGHEST PRIORITYPlayer B: ["tank", "healer"] // 2 options - medium priorityPlayer C: ["tank", "healer", "dps"] // 3 options - lowest priority// Player A gets first choice, then B, then C
2. Preference Order Matters
When assigned, the system tries roles left-to-right in your array:
Copy
// Player's preference array:["support", "adc", "mid"]// System tries:// 1st: Can they be support? → If yes, assigned!// 2nd: If not, can they be adc? → If yes, assigned!// 3rd: If not, can they be mid? → If yes, assigned!
3. Tie-Breaking
When players have identical preferences, first-come-first-served applies:
Copy
// Both want the same roles:Player1: ["support", "adc"] // Joined lobby firstPlayer2: ["support", "adc"] // Joined lobby second// Result:Player1 → Gets "support" (first preference)Player2 → Gets "adc" (support taken, gets second choice)
public class RolePreferenceManager : MonoBehaviour{ // Specific player - highest priority but risky public void SetOneRoleOnly(string role) { var data = new Dictionary<string, object> { { "role", role } // Single string }; PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(data); } // Flexible player - lower priority but guarantees match public void SetMultipleRoles(List<string> roles) { var data = new Dictionary<string, object> { { "role", roles } // String array in preference order }; PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(data); } // Ultra-flexible - can play anything public void SetFillPlayer() { var allRoles = new List<string> { "top", "jungle", "mid", "adc", "support" }; var data = new Dictionary<string, object> { { "role", allRoles } }; PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(data); }}
Edge Case: If too many players are inflexible (single role only) and want the same role, the match will fail. Always encourage some flexibility in your player base!
Spectators are excluded from team requirements but still receive game server access for observation.
Copy
public class SpectatorManager : MonoBehaviour{ public void JoinAsSpectator() { // Set spectator role var spectatorData = new Dictionary<string, object> { { "role", "Spectator" }, { "regions", new List<string> { "us-east" } } }; PlayFlowLobbyManagerV2.Instance.UpdatePlayerState(spectatorData); } private void OnMatchFound(Lobby lobby) { // Spectators still get notified and can connect if (IsSpectator()) { Debug.Log("Joined match as spectator!"); // Connect to server in spectator mode } }}
Players can specify multiple acceptable regions. The system finds matches where players have overlapping regions.
Copy
// Specify regions in order of preferencevar regionList = new List<string> { "us-east", "us-west", "eu-west" };UpdatePlayerState(new Dictionary<string, object> { { "regions", regionList }});
The lobby-based approach naturally supports parties. Friends can join the same lobby and queue together:
Copy
public class PartyManager : MonoBehaviour{ public void CreatePartyLobby() { // Create lobby for your party PlayFlowLobbyManagerV2.Instance.CreateLobby( name: "My Party", maxPlayers: 4, // Your party size isPrivate: true, onSuccess: (lobby) => { Debug.Log($"Party lobby created! Share code: {lobby.inviteCode}"); } ); } public void QueueWithParty(string mode) { // Everyone in the lobby queues together PlayFlowLobbyManagerV2.Instance.FindMatch(mode); }}