Matchmaking Guide

Getting into a room to play with (or against) someone is very easy with Photon. There are basically three approaches: Either tell the server to find a matching room, follow a friend into her room, or fetch a list of rooms to let the user pick one. All three variants are supported by Photon and you can even roll your own.

We think, for most games it's best to use a quick and simple matchmaking, so we suggest to use Random Matchmaking and maybe filters for skills, levels and such.

Matchmaking Checklist

If you are having issues matching players, here is a quick checklist:

  • Verify that you are using same AppId in all clients.
  • Verify that clients are connected to the same Region. Only players connected to same region can play with each other no matter what device or platform they're using. Make sure to read about "Best Region Considerations" if you use "Best Region".
  • Verify that you are using same AppVersion in all clients. More information can be found here.
  • Verify that players have different unique UserIDs. Players with same UserID cannot join the same room.
  • Before trying to join a room by name, make sure that this room is created. Alternatively use JoinOrCreateRoom.
  • If you are trying to join a random room, make sure to choose the same lobby (name and type) used when creating it.
  • If you are doing random matchmaking using room properties as a filter make sure to set the keys of those properties to be visible from the lobby when creating the room.
  • If you are doing random matchmaking with SQL filters make sure to set the reserved filtering properties keys used to be visible from the lobby. It is also important to relax the SQL filter with each random matchmaking attempt or use chained filters or create new rooms at some point after a number of failed attempts.
  • If you are implementing asynchronous matchmaking, make sure to use webhooks with proper configuration (enable "AsyncJoin") or use AsyncRandomLobby.

Quick Match

Most players nowadays just want to jump directly into matches. They want to dive into the game right away. That is why most games offer Quick Match as the first mode.

The suggested workflow described here gets players into rooms without asking them to pick one (randomly) from a long list of rooms.

If you just want to get players into a room quickly, do the following:

JoinRandomOrCreateRoom

Simply call JoinRandomOrCreateRoom (exact function or method name may vary depending on your client SDK). If a room is found it will be joined, otherwise a new room will be created.

C#

using Photon.Realtime;
using System.Collections.Generic;

public class QuickMatchExample : IMatchmakingCallbacks
{
    private LoadBalancingClient loadBalancingClient;

    private void QuickMatch()
    {
        loadBalancingClient.OpJoinRandomOrCreateRoom(null, null);;
    }

    // do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
    // also deregister via loadBalancingClient.RemoveCallbackTarget
    #region IMatchmakingCallbacks

    void IMatchmakingCallbacks.OnJoinedRoom()
    {
        // joined a room successfully
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endif
}

JoinRandomRoom or CreateRoom

  • Try JoinRandomRoom (exact function or method name may vary depending on your client SDK).
    • In best case, that's it. Your client will join a room successfully.
    • In worst case, no room is existing or no room can be joined (closed, invisible or full).
  • If this doesn't find a room instantly, create one.
    • If you never show room names (and why should you), don't make up a name. Let the server do this. Set null or empty string as "room name" when creating the room. The room gets a GUID which is unique.
    • Apply a value for "max players". This way, the server eventually stops adding players when the room is full.
  • If your client is alone in the room (players count == 1): Wait. Show a screen you're waiting for opponents.
  • When enough players are in the room, you might "start" the game. To keep new players out, "close" the room. The server stops filling up the room, even if it's not full yet.
    • Note: When you close the room, there is a short time where players maybe are already on the way in. Don't be surprised if someone joins even 'after' a client closes the room.

C#

using Photon.Realtime;
using System.Collections.Generic;

public class QuickMatchExample : IMatchmakingCallbacks
{
    [SerializeField]
    private maxPlayers = 4;
    private LoadBalancingClient loadBalancingClient;

    private void CreateRoom()
    {
        RoomOptions roomOptions = new RoomOptions();
        roomOptions.MaxPlayers = maxPlayers;
        EnterRoomParams enterRoomParams = new EnterRoomParams();
        enterRoomParams.RoomOptions = roomOptions;
        loadBalancingClient.OpCreateRoom(enterRoomParams);
    }

    private void QuickMatch()
    {
        loadBalancingClient.OpJoinRandomRoom();
    }

    // do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
    // also deregister via loadBalancingClient.RemoveCallbackTarget
    #region IMatchmakingCallbacks

    void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
    {
        CreateRoom();
    }

    void IMatchmakingCallbacks.OnJoinedRoom()
    {
        // joined a room successfully
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endif
}

Using this workflow, joining a game is a breeze for your players.

Random Matchmaking

Sometimes players want more than a Quick Match, they want to play a certain map or mode (e.g. two versus two, etc.).

You can set arbitrary "Custom Room Properties" and use them as filter in JoinRandomRoom request (exact function or method name may vary depending on your client SDK).

Exposing Some Properties In The Lobby

Custom room properties are synced to all players in the room and can be useful to keep track of the current map, game mode, difficulty, turn, round, start-time, etc. They are handled as Hashtable with string keys.

By default, to keep things lean, these properties are accessible only inside rooms and are not sent to the Master Server (where lobbies exist). You can choose some custom room properties to expose in the lobby. Those properties will be used as filters for random matchmaking and they will be visible in the lobby (sent as part of the room info in the rooms list, only lobbies of default type send rooms lists).

Example:

To make "map" and "gm" available for for matchmaking, you can set a list of "room properties visible in the lobby" when you create a room. Tip: Brief names are better, so use "gm" instead of "GameMode".

C#

using Photon.Realtime;
using System.Collections.Generic;
using Hashtable = ExitGames.Client.Photon.Hashtable;

public class CreateRoomWithLobbyPropertiesExample : IMatchmakingCallbacks
{
    public const string MAP_PROP_KEY = "map";
    public const string GAME_MODE_PROP_KEY = "gm";
    public const string AI_PROP_KEY = "ai";

    private LoadBalancingClient loadBalancingClient;

    private void CreateRoom()
    {
        RoomOptions roomOptions = new RoomOptions();
        roomOptions.CustomRoomPropertiesForLobby = { MAP_PROP_KEY, GAME_MODE_PROP_KEY, AI_PROP_KEY };
        roomOptions.CustomRoomProperties = new Hashtable { { MAP_PROP_KEY, 1 }, { GAME_MODE_PROP_KEY, 0 } };
        EnterRoomParams enterRoomParams = new EnterRoomParams();
        enterRoomParams.RoomOptions = roomOptions;
        loadBalancingClient.OpCreateRoom(enterRoomParams);
    }

    // do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
    // also deregister via loadBalancingClient.RemoveCallbackTarget
    #region IMatchmakingCallbacks

    void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message)
    {
       // log error message and code
    }

    void IMatchmakingCallbacks.OnCreatedRoom()
    {
    }

    void IMatchmakingCallbacks.OnJoinedRoom()
    {
        // joined a room successfully, OpCreateRoom leads here on success
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endif
}

Note that "ai" has no value initially. It won't show up in the lobby until it's set in the room (in C# SDKs, this is done via Room.SetCustomProperties). When you change the values for "map" or "gm" or "ai", they will be updated in the lobby with a short delay, too.

Later (post room creation), you could also change the room properties keys visible to the lobby (add or remove) (in C# SDKs, this is done via Room.PropertiesListedInLobby) Tip: Keep the list of lobby properties short to make sure your clients performance doesn't suffer from loading them either when joining the room or joining the lobby (only lobbies of default type send rooms lists).

Again: You don't have to join the lobby (and get the awfully long room list) to make use of this. When you set some for the lobby, they become available as filter, too.

Filtering Room Properties in Join Random

This section excludes SQL Lobby type. Read more about SQL matchmaking here.

When trying to find a random room, you could optionally choose the expected room properties or the expected max players. These work as filters when the server selects a "fitting" room for you.

Example:

C#

using Photon.Realtime;
using System.Collections.Generic;
using Hashtable = ExitGames.Client.Photon.Hashtable;

public class RandomMatchmakingExample : IMatchmakingCallbacks
{
    public const string MAP_PROP_KEY = "map";

    private LoadBalancingClient loadBalancingClient;

    public void JoinRandomRoom(byte mapCode, byte expectedMaxPlayers)
    {
        Hashtable expectedCustomRoomProperties = new Hashtable { { MAP_PROP_KEY, mapCode } };
        OpJoinRandomRoomParams opJoinRandomRoomParams = new OpJoinRandomRoomParams();
        opJoinRandomRoomParams.ExpectedMaxPlayers = expectedMaxPlayers;
        opJoinRandomRoomParams.ExpectedCustomRoomProperties = expectedCustomRoomProperties:
        loadBalancingClient.OpJoinRandomRoom(opJoinRandomRoomParams);
    }

    // do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
    // also deregister via loadBalancingClient.RemoveCallbackTarget
    #region IMatchmakingCallbacks

    void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
    {
        // log error code and message
        // here usually you create a new room
    }

    void IMatchmakingCallbacks.OnJoinedRoom()
    {
        // joined a room successfully, OpJoinRandomRoom leads here on success
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endif
}

If you pass more filter properties, chances are lower that a room matches them. Better limit the options.

Make sure you always filter using properties visible to the lobby as shown here.

Play with Your Friends

If your users communicate with friends (e.g. with Photon Chat), they can easily make up a room name and everyone just uses JoinOrCreateRoom (exact function or method name may vary depending on your client SDK) to get into that room.

Example:

A unique room name could be composed (e.g.) as: "friendName1 + friendName2 + randomInteger".

To avoid anyone else joining, create the room invisible like so:

C#

using Photon.Realtime;
using System.Collections.Generic;

public class PrivateRoomExample : IMatchmakingCallbacks
{
    private LoadBalancingClient loadBalancingClient;

    public void JoinOrCreatePrivateRoom(string nameEveryFriendKnows)
    {
        RoomOptions roomOptions = new RoomOptions();
        roomOptions.IsVisible = false;
        EnterRoomParams enterRoomParams = new EnterRoomParams();
        enterRoomParams.RoomName = nameEveryFriendKnows;
        enterRoomParams.RoomOptions = roomOptions;
        loadBalancingClient.OpJoinOrCreateRoom(enterRoomParams);
    }

    // do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
    // also deregister via loadBalancingClient.RemoveCallbackTarget
    #region IMatchmakingCallbacks

    void IMatchmakingCallbacks.OnJoinRoomFailed(short returnCode, string message)
    {
      // log error code and message
    }

    void IMatchmakingCallbacks.OnJoinedRoom()
    {
        // joined a room successfully, OpJoinOrCreateRoom leads here on success
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endif
}

You can also look for your friends using FindFriends (exact function or method name may vary depending on your client SDK) if you use unique UserIDs which you should.

Publishing UserIDs in a Room

Photon uses a UserID in various places. For example, you can find friends only with a suitable UserID per player. We added an option to Photon, which makes the UserID of players known per room. In C# SDKs, set RoomOptions.PublishUserId to true, when you create a room. The server will then provide the UserID and you can access it on the client. In C# SDKs, it's done via Player.UserId.

Notes:

  • UserIDs are broadcasted, with player properties, in the Photon join event.
  • The UserID for a client can be set in three ways:
    1. Client sets UserID before connecting.
    2. Returned by an external web service using Custom Authentication. It will override the value sent by the client.
    3. Photon will make up UserIDs (GUID) for users that don't explicitly set theirs.
  • Generally, UserIDs, are not intended to be displayed.

Matchmaking Slot Reservation

Sometimes, a player joins a room, knowing that a friend should join as well. With Slot Reservation, Photon can block a slot for specific users and take that into account for matchmaking. To reserve slots there is an expectedUsers parameter (exact parameter or argument name may vary depending on your client SDK) in the methods that get you in a room (JoinRoom, JoinOrCreateRoom, JoinRandomRoom and CreateRoom. Exact functions or methods names may vary depending on your client SDK).

C#

EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.ExpectedUsers = expectedUsers;
// create room example
loadBalancingClient.OpCreateRoom(enterRoomParams);
// join room example
loadBalancingClient.OpJoinRoom(enterRoomParams);
// join or create room example
loadBalancingClient.OpJoinOrCreateRoom(enterRoomParams);
// join random room example
OpJoinRandomRoomParams opJoinRandomRoomParams = new OpJoinRandomRoomParams();
opJoinRandomRoomParams.ExpectedUsers = expectedUsers;
loadBalancingClient.OpJoinRandomRoom(opJoinRandomRoomParams);

When you know someone should join, pass an array of UserIDs. For JoinRandomRoom, the server will attempt to find a room with enough slots for you and your expected players (plus all active and expected players already in the room). The server will update clients in a room with the current expectedUsers, should they change.

You can update the list of expected users inside a room (add or remove one or more users), this is done via a well known room property. (In C# SDKs, you can get and set Room.ExpectedUsers).

To support Slot Reservation, you need to enable publishing UserIDs inside rooms.

Example Use Case: Teams Matchmaking

You can use this to support teams in matchmaking. The leader of a team does the actual matchmaking. He/She can join a room and reserve slots for all members:

Try to find a random room:

C#

OpJoinRandomRoomParams opJoinRandomRoomParams = new OpJoinRandomRoomParams();
opJoinRandomRoomParams.ExpectedUsers = teamMembersUserIds;
loadBalancingClient.OpJoinRandomRoom(opJoinRandomRoomParams);

Create a new one if none found:

C#

EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.ExpectedUsers = teamMembersUserIds;
loadBalancingClient.OpCreateRoom(enterRoomParams);

The others don't have to do any matchmaking but instead repeatedly call ('periodic poll', every few frames/(milli)seconds):

C#

loadBalancingClient.OpFindFriends(new string[1]{ leaderUserId });

When the leader arrives in a room, the FindFriends operation will reveal that room's name and everyone can join it:

C#

EnterRoomParams enterRoomParams = new EnterRoomParams();
enterRoomParams.RoomName = roomNameWhereTheLeaderIs;
loadBalancingClient.OpJoinRoom(enterRoomParams);

Lobbies

Photon is organizing your rooms in so called "lobbies". So all rooms belong to lobbies. Lobbies are identified using their name and type. The name can be any string, however there are only 3 types of lobbies: Default, SQL and Async. Each one has a unique capability which suits specific use cases.

All applications start with a preexisting lobby: The Default Lobby. Most applications won't need other lobbies. However, clients can create other lobbies on the fly. Lobbies begin to exist when you specify a new lobby definition in operation requests: JoinLobby, CreateRoom or JoinOrCreateRoom.

Like rooms, lobbies can be joined and you can leave them. In a lobby, the clients only get the room list of that lobby when applicable. Nothing else. There is no way to communicate with others in a lobby.

When a client is joined to a lobby and tries to create (or JoinOrCreate) a room without explicitly setting a lobby, if the creation succeeds/happens, the room will be added to the currently joined lobby. When a client is not joined to a lobby and tries to create (or JoinOrCreate) a room without explicitly setting a lobby, if the creation succeeds/happens, the room will be added to the default lobby. When a client is joined to a lobby and tries to create (or JoinOrCreate) a room by explicitly setting a lobby, if the creation succeeds/happens:

  • if the lobby name is null or empty: the room will be added to the currently joined lobby. This means you cannot create rooms in the default lobby when you are joined to a custom/different one.
  • if the lobby name is not null nor empty: the room will be added to the lobby specified by the room creation request.

When a client is joined to a lobby and tries to join a random room without explicitly setting a lobby, the server will look for the room in the currently joined lobby. When a client is not joined to a lobby and tries to join a random room without explicitly setting a lobby, the server will look for the room in the default lobby. When a client is joined to a lobby and tries to join a random room a room by explicitly setting a lobby:

  • if the lobby name is null or empty: the server will look for the room in the currently joined lobby. This means you cannot join random rooms in the default lobby when you are joined to a custom/different one.
  • if the lobby name is not null nor empty: the server will look for the room in the lobby specified by the room creation request.

When a client is joined to a lobby and wants to switch to a different one, you can call JoinLobby directly and no need to leave the first one by calling LeaveLobby explicitly.

Default Lobby Type

The most suited type for synchronous random matchmaking. Probably the less sophisticated and most used type.

While joined to a default lobby type, the client will receive periodic room list updates.

When the client joins a lobby of default type, it instantly gets an initial list of available rooms. After that the client will receive periodic room list updates.

The list is sorted using two criteria: open or closed, full or not. So the list is composed of three groups, in this order:

  • first group: open and not full (joinable).
  • second group: full but not closed (not joinable).
  • third group: closed (not joinable, could be full or not).

In each group, entries do not have any particular order (random).

The list of rooms (or rooms' updates) is also limited in number, see Lobby Limits.

C#

using Photon.Realtime;
using System.Collections.Generic;

public class RoomListCachingExample : ILobbyCallbacks, IConnectionCallbacks
{
    private TypedLobby customLobby = new TypedLobby("customLobby", LobbyType.Default);
    private LoadBalancingClient loadBalancingClient;

    private Dictionary<string, RoomInfo> cachedRoomList = new Dictionary<string, RoomInfo>();

    public void JoinLobby()
    {
        loadBalancingClient.JoinLobby(customLobby);
    }

    private void UpdateCachedRoomList(List<RoomInfo> roomList)
    {
        for(int i=0; i<roomList.Count; i++)
        {
            RoomInfo info = roomList[i];
            if (info.RemovedFromList)
            {
                cachedRoomList.Remove(info.Name);
            }
            else
            {
                cachedRoomList[info.Name] = info;
            }
        }
    }

    // do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
    // also deregister via loadBalancingClient.RemoveCallbackTarget
    #region ILobbyCallbacks

    void ILobbyCallbacks.OnJoinedLobby()
    {
        cachedRoomList.Clear();
    }

    void ILobbyCallbacks.OnLeftLobby()
    {
        cachedRoomList.Clear();
    }
  
    void ILobbyCallbacks.OnRoomListUpdate(List<RoomInfo> roomList)
    {
        // here you get the response, empty list if no rooms found
        UpdateCachedRoomList(roomList);
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endif

    #region IConnectionCallbacks

    void IConnectionCallbacks.OnDisconnected(DisconnectCause cause)
    {
        cachedRoomList.Clear();
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endregion
}

The Default Lobby

It has a null name and its type is Default Lobby Type. In C# SDKs, it's defined in TypedLobby.Default. The default lobby's name is reserved: only the default lobby can have a null name, all other lobbies need to have a name string that is not null nor empty. If you use a string empty or null as a lobby name it will point to the default lobby nomatter the type specified.

We encourage everyone to skip joining lobbies unless abolutely necessary. If needed, when you want rooms to be added to specific or custom lobbies, the client can specify the lobby when creating new rooms.

Joining lobbies of default type will get you the list of rooms, but it's not useful in most cases:

  • there is no difference in terms of ping between the entries of the list
  • usually players are looking for a quick match
  • receiving rooms list adds an extra delay and consumes traffic
  • a long list with too much information can have a bad effect on the user experience

Instead, to give your players more control over the matchmaking, use filters for random matchmaking. Multiple lobbies can still be useful, as they are also used in (server-side) random matchmaking and you could make use of lobby statistics.

SQL Lobby Type

In SQL lobby type, string filters in JoinRandomRoom replace the default expected lobby properties. Also, in SQL lobby type, only one MatchmakingMode is supported: FillRoom (default, 0). Besides "Custom Room Listing" replaces the automatic periodic rooms listing which exists only in the default lobby type.

This lobby type adds a more elaborate matchmaking filtering which could be used for a server-side skill-based matchmaking that's completely client-driven.

Internally, SQL lobbies save rooms in a SQLite table with up to 10 special "SQL filtering properties". The naming of those SQL properties is fixed as: "C0", "C1" up to "C9". Only integer-typed and string-typed values are allowed and once a value was assigned to any column in a specific lobby, this column is locked to values of that type. Despite the static naming, clients have to define which ones are needed in the lobby. Be careful as SQL properties are case sensitive when you define them as lobby properties or set their values but are not case sensitive inside SQL filters.

You can still use custom room properties other than the SQL properties, visible or invisible to the lobby, during room creation or after joining it. Those will not be used for matchmaking however.

Queries can be sent in JoinRandomRoom operation. The filtering queries are basically SQL WHERE conditions based on the "C0" .. "C9" values. Find the list of all SQLite supported operators and how to use them here. Take into consideration the excluded keywords.

Example:

C#

using Photon.Realtime;
using System.Collections.Generic;
using Hashtable = ExitGames.Client.Photon.Hashtable;

public class RandomMatchmakingExample : IMatchmakingCallbacks
{
    public const string ELO_PROP_KEY = "C0";
    public const string MAP_PROP_KEY = "C1";
    private TypedLobby sqlLobby = new TypedLobby("customSqlLobby", LobbyType.SqlLobby);
    private LoadBalancingClient loadBalancingClient;

    private void CreateRoom()
    {
        RoomOptions roomOptions = new RoomOptions();
        roomOptions.CustomRoomProperties = new Hashtable { { ELO_PROP_KEY, 400 }, { MAP_PROP_KEY, "Map3" } };
        roomOptions.CustomRoomPropertiesForLobby = { ELO_PROP_KEY, MAP_PROP_KEY }; // makes "C0" and "C1" available in the lobby
        EnterRoomParams enterRoomParams = new EnterRoomParams();
        enterRoomParams.RoomOptions = roomOptions;
        enterRoomParams.Lobby = sqlLobby;
        loadBalancingClient.OpCreateRoom(enterRoomParams);
    }

    private void JoinRandomRoom()
    {
        string sqlLobbyFilter = "C0 BETWEEN 345 AND 475 AND C1 = 'Map2'";
        //string sqlLobbyFilter = "C0 > 345 AND C0 < 475 AND (C1 = 'Map2' OR C1 = \"Map3\")";
        //string sqlLobbyFilter = "C0 >= 345 AND C0 <= 475 AND C1 IN ('Map1', 'Map2', 'Map3')";
        OpJoinRandomRoomParams opJoinRandomRoomParams = new OpJoinRandomRoomParams();
        opJoinRandomRoomParams.SqlLobbyFilter = sqlLobbyFilter;
        loadBalancingClient.OpJoinRandomRoom(opJoinRandomRoomParams);
    }

    // do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
    // also deregister via loadBalancingClient.RemoveCallbackTarget
    #region IMatchmakingCallbacks

    void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
    {
        CreateRoom();
    }

    void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message)
    {
        Debug.LogErrorFormat("Room creation failed with error code {0} and error message {1}", returnCode, message);
    }

    void IMatchmakingCallbacks.OnJoinedRoom()
    {
        // joined a room successfully, both JoinRandomRoom or CreateRoom lead here on success
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endif
}

Chained Filters

You can send up to 3 comma separated filters at once in a single JoinRandomRoom operation. These are called chained filters. Photon servers will try to use the filters in order. A room will be joined if any of the filters matches a room. Otherwise a NoMatchFound error will be returned to the client.

Chained filters could help save matchmaking requests and speed up its process. It could be useful especially for skill-based matchmaking where you need to 'relax' the filter after failed attempt.

Possible filters string formats:

  • 1 (min) filter value: {filter1} (or {filter1};)
  • 2 filter values: {filter1};{filter2} (or {filter1};{filter2};)
  • 3 (max) filter values: {filter1};{filter2};{filter3} (or {filter1};{filter2};{filter3};)

Examples:

  • C0 BETWEEN 345 AND 475
  • C0 BETWEEN 345 AND 475;C0 BETWEEN 475 AND 575
  • C0 BETWEEN 345 AND 475;C0 BETWEEN 475 AND 575;C0 >= 575

Custom Room Listing

Client can also request a custom list of rooms from an SqlLobby using SQL-like queries. This method will return up to 100 rooms that fit the conditions. The returned rooms are joinable (i.e. open and not full) and visible.

C#

using Photon.Realtime;
using System.Collections.Generic;

public class GetCustomRoomListExample : ILobbyCallbacks
{
    private TypedLobby sqlLobby = new TypedLobby("customSqlLobby", LobbyType.SqlLobby);

    public void GetCustomRoomList(string sqlLobbyFilter)
    {
      loadBalancingClient.OpGetGameList(sqlLobby, sqlLobbyFilter);
    }

    // do not forget to register callbacks via loadBalancingClient.AddCallbackTarget
    // also deregister via loadBalancingClient.RemoveCallbackTarget
    #region ILobbyCallbacks

    void ILobbyCallbacks.OnRoomListUpdate(List<RoomInfo> roomList)
    {
        // here you get the response, empty list if no rooms found
    }

    // [..] Other callbacks implementations are stripped out for brevity, they are empty in this case as not used.

    #endif
}

Skill-based Matchmaking

You can use lobbies of the SQL-type to implement your own skill-based matchmaking.

First of all, each room gets a fixed skill that players should have to join it. This value should not change, or else it will basically invalidate any matching the players in it did before.

As usual, players should try to get into a room by JoinRandomRoom. The filter should be based on the user's skill. The client can easily filter for rooms of "skill +/- X".

JoinRandomRoom will get a response as usual but if it didn't find a match right away, the client should wait a few seconds and then try again. You can do as many or few requests as you like. If you use SQL lobby type, you could make use of Chained Filters. Best of all: The client can begin to relax the filter rule over time.

It's important to relax the filters after a moment. Granted: A room might be joined by a player with not-so-well-fitting skill but obviously no other room was a better fit and it's better to play with someone.

You can define a max deviation and a timeout. If no room was found, this client has to open a new room with the skill this user has. Then it has to wait for others doing the same.

Obviously, this workflow might take some time when few rooms are available. You can rescue your players by checking the "application stats" which tell you how many rooms are available. See Matchmaking For Low CCU. You can adjust the filters and the timing for "less than 100 rooms" and use different settings for "100 to 1000 rooms" and again for "even more".

Excluded SQL Keywords

SQL filters will not accept the following keywords:

  • ALTER
  • CREATE
  • DELETE
  • DROP
  • EXEC
  • EXECUTE
  • INSERT
  • INSERT INTO
  • MERGE
  • SELECT
  • UPDATE
  • UNION
  • UNION ALL

If you use any of these words in the SQL filter string the corresponding operation will fail.

Asynchronous Random Lobby Type

This lobby is similar to the default lobby type with two major differences:

  1. Room entries stay in the lobby list (available for matchmaking) for one hour after they are removed from game servers. Rooms need to be visible and open to be considered in the asynchronous matchmaking.
  2. Rooms lists are not sent to clients.
This type of lobby should be combined with webhooks or any other mean of persisting room states in order to get asynchronous (re)join fully working.

Lobby Types Comparison

LobbyType Periodic Rooms List Updates SQL Filter Max Players Filter Custom Room Properties Filter Matchmaking Modes Removed Rooms Entries TTL (minutes)
Default 0
SQL 0
Asynchronous 60

Matchmaking For Low CCU

For really good matchmaking, a game needs a couple hundred players online. With less players online, it will become harder to find a worthy opponent and at some point it makes sense to just accept almost any match.

You have to take this into account when you build a more elaborate matchmaking on the client side. To do so, the Photon Master Server provides the count of connected users, rooms and players (in a room), so you can adjust the client-driven matchmaking at runtime.

The number of rooms should be a good, generic indicator of how busy the game currently is. You could obviously also fine tune the matchmaking by on how many players are not in a room. Whoever is not in a room might be looking for one.

For example, you could define a low CCU situation as less than 20 rooms. So, if the count of rooms is below 20, your clients use no filtering and instead run the Quick Match routine.

Testing Matchmaking Early In Development

If you are testing matchmaking early in development phase and try to join a random room from two clients at about the same time, there is a chance both clients end up on different rooms: this happens because join random room will not return a match for both clients and each one will probably create a new room as none found. So this is expected and OK. To avoid this use JoinRandomOrCreateRoom (see Quick Match) instead of JoinRandomRoom then CreateRoom. Otherwise, a possible workaround (for development purposes only) would be to add a random delay before (or after) attempting to join a room or retry again. You could also listen for application or lobby statistics to make sure a room at least exists or has been created.

Other Matchmaking Options

If you want to roll your own matchmaking, make sure that most of that is done server side. The clients don't have perfect information about how full rooms are as the room-list update frequency from server to client is low (~1..2 seconds).

When you have thousands of players, several will send their "join" requests at the very same time. If a room gets full quickly, your players will frequently fail to join rooms and matchmaking will take longer and longer.

On the other hand, the server can distribute players perfectly, preferring almost full rooms and respecting your filtering.

What you could do is to make matchmaking external to Photon, via HTTP based web service maybe and then use Photon to create and join rooms (or one call to JoinOrCreate). Such matchmaking service could make use of (combined with) Photon's "native HTTP modules" (Custom Authentication / WebRPCs / WebHooks) or even a custom plugin to report rooms availability to your web service. Things to consider in matchmaking (keywords): Region (and Cluster when applicable), AppVersion, AppId, UserId, RoomName/GameId, Auth Cookie (Custom Auth), URL tags (WebHooks), etc.

Another option is to modify LoadBalancing server application, MasterServer specifically, the matchmaking part. This option is for self-hosted only of course.

That said, using a room as 'lobby' or a 'matchmaking place', before the actual gameplay starts, is in most cases not a good idea for popular games.

Lobby v1 Applications

Some old Photon applications that are not on the free 20 CCU plan may have different lobbies behaviour:

  • SQL lobby types used to send rooms lists. Now they don't.
  • Rooms' lists were not limited. Now they are capped.
  • Rooms' lists were not sorted by joinability. Now the joinable ones are first on the list.

Lobby Limits

Photon has the following lobbies related default limits:

  • Maximum number of lobbies per application: 10000.
  • Maximum number of room list entries in GameList events (initial list when you join lobbies of type Default): 500.
  • Maximum number of updated rooms entries in GameListUpdate events (when joined to lobbies of type Default): 500. This limit does not account for removed rooms entries (corresponding to rooms no longer visible or simply gone).
  • Maximum number of room list entries in GetGameList operation response (SQL Lobby): 100.

Notes:

In lobby v2 only the number of initial/updated room entries sent to the clients joined to the same respective lobby of default type, in GameList or GameListUpdate events are limited to 500. For GameList event, the length of the received array by the client will not exceed 500. For GameListUpdate event, the length of the received array by the client may exceed 500 as we do not limit the number of removed rooms entries (which are also sent here), only the updated ones.

The new limits in Lobbies v2 do not affect anything else server-side: the rooms still exist in the lobby. We only limit what we broadcast to clients joined to lobbies for bandwidth reasons and to not overwhelm clients which could be limited in specs. And 500 is quite a big number, no player would scroll to see the full list. Besides, in theory, a client that stays long enough in the lobby may end up with the full list of rooms available on the server because the server adds all updates to a queue and send them in batches of max length 500. Of course, if a room exists in the lobby and does not change for a while the client may miss it.

So on the servers, there is no limit of the number of rooms per lobby. FindFriends, CreateRoom, JoinRoom or JoinOrCreateRoom are not affected by the move to Lobbies v2 and are not limited, meaning clients can create room indefinitely or join or find friends in rooms not sent to the client in the lobbies list updates.

Back to top