Matchmaking

Allow game makers to create multi-user experiences

Overview

The Beamable Multiplayer feature allows game makers to create real-time and turn-based multi-user game experiences. For a project to offer Multiplayer, it must first offer matchmaking.

In multiplayer gaming, matchmaking is the process of creating/finding a Match (e.g. Multiplayer 'Room') based on criteria. E.g. "Give me a match to play in with 2 total players of any skill level". Beamable supports matchmaking through its MatchmakingService.

Here is a custom, game-specific implementation which matches any 2 players without filtering. Depending on game design needs, the service can be extended to filter and match players with similar attributes; e.g. game skill level, network latency, spoken language, or geographic location.

Here is a partial code snippet. Download the project to see the complete code.

Start Matchmaking

private async void SetupBeamable()
{
   // Partial code

    var myMatchmaking = new MyMatchmaking (
        _beamableAPI.Experimental.MatchmakingService,
        simGameType,
        _beamableAPI.User.id);

    myMatchmaking.OnProgress.AddListener(MyMatchmaking_OnProgress);
    myMatchmaking.OnComplete.AddListener(MyMatchmaking_OnComplete);
    myMatchmaking.OnError.AddListener(MyMatchmaking_OnError);

    await myMatchmaking.StartMatchmaking();
}

Handle Matchmaking Success

private void MyMatchmaking_OnComplete(MyMatchmakingResult myMatchmakingResult)
{
    Debug.Log($"MyMatchmaking_OnComplete()...\n\n" +
              $"MatchId = {myMatchmakingResult.MatchId}\n" +
              $"LocalPlayer = {myMatchmakingResult.LocalPlayer}\n" +
              $"Players = {string.Join(",", myMatchmakingResult.Players)}\n");
}

API

📘

Learning Fundamentals

Game makers who are new to Unity and C# can review the fundamentals here.

• See Beamable: Asynchronous Programming for more info

Here are API highlights for MatchmakingService.

Method Name

Detail

StartMatchmaking

Starts the matchmaking process

CancelMatchmaking

Stops the matchmaking process

MatchmakingHandle

Handles events from the matchmaking process

• OnUpdate - Process is in progress, with success
• OnMatchReady - Process complete with success
• OnMatchTimeout - Process complete with failure

Content

The content type related to Matchmaking is the SimGameType. It defines the parameters for the match to be created.

Name

Detail

  1. Teams

Name - Arbitrary name
MaxPlayers - Maximum players allowed
MinPlayers - Minimum players allowed

  1. Numeric Rules

Match to players who have an approximate game.private.player stat value

Property - The stat name
Max Delta - Largest stat value difference which will allow a match
Default - Value used if no stat value exists

  1. String Rules

Match to players who have an exact game.private.player stat value

Property - The stat name
Value - Required value to allow a match

  1. Leaderboard Updates

Leaderboard - The reference to update
Scoring Algorithm - (Advanced use cases)

  1. Rewards

Start Rank - The best rank to be rewarded
End Rank - The worst rank to be rewarded
Reward - The type and amount of the reward

Misc

MaxPlayers - This is deprecated
WaitAfterMinReachedSecs - Duration to wait if match found (with just the minimum players)
MaxWaitDurationSecs - Duration to wait if no match found

Examples

📘

Beamable SDK Examples

This and all examples are available for download at GitHub.com/Beamable_SDK_Examples

Code
The following MatchmakingServiceExample.cs is built on top of the custom MyMatchmaking.cs class shown further below. It is provided as inspiration to game makers to create custom solutions.

using System.Collections.Generic;
using Beamable.Common.Content;
using UnityEngine;
using UnityEngine.Events;

namespace Beamable.Examples.Services.MatchmakingService
{
    public enum SessionState
    {
        None,
        Connecting,
        Connected,
        Disconnecting,
        Disconnected

    }

    
    /// <summary>
    /// Holds data for use in the <see cref="MatchmakingServiceExampleUI"/>.
    /// </summary>
    [System.Serializable]
    public class MatchmakingServiceExampleData
    {
        public SessionState SessionState = SessionState.None;
        public bool CanStart { get { return SessionState == SessionState.Disconnected;}} 
        public bool CanCancel { get { return SessionState == SessionState.Connected;}} 
        public List<string> MainLogs = new List<string>();
        public List<string> MatchmakingLogs = new List<string>();
        public List<string> InstructionsLogs = new List<string>();
    }
   
    [System.Serializable]
    public class RefreshedUnityEvent : UnityEvent<MatchmakingServiceExampleData> { }
    
    /// <summary>
    /// Demonstrates the creation of and joining to a
    /// Multiplayer game match with Beamable Multiplayer.
    /// </summary>
    public class MatchmakingServiceExample : MonoBehaviour
    {
        //  Events  ---------------------------------------
        [HideInInspector]
        public RefreshedUnityEvent OnRefreshed = new RefreshedUnityEvent();

        
        //  Fields  ---------------------------------------

        /// <summary>
        /// This defines the matchmaking criteria including "NumberOfPlayers"
        /// </summary>
        [SerializeField] private SimGameTypeRef _simGameTypeRef;
        private IBeamableAPI _beamableAPI = null;
        private MyMatchmaking _myMatchmaking = null;
        private SimGameType _simGameType = null;
        private MatchmakingServiceExampleData _data = new MatchmakingServiceExampleData();

        //  Unity Methods  --------------------------------
        protected void Start()
        {
            string startLog = $"Start() Instructions..\n" +
                              $"\n * Play Scene" +
                              $"\n * View UI" +
                              $"\n * Press 'Start Matchmaking' Button \n\n";
         
            Debug.Log(startLog);
            
            _data.InstructionsLogs.Add("View UI");
            _data.InstructionsLogs.Add("Press 'Start Matchmaking' Button");
            
            SetupBeamable();
        }


        //  Methods  --------------------------------------
        private async void SetupBeamable()
        {
            _beamableAPI = await Beamable.API.Instance;
            Debug.Log($"beamableAPI.User.id = {_beamableAPI.User.id}\n\n");

            _data.SessionState = SessionState.Disconnected;
            
            _simGameType = await _simGameTypeRef.Resolve();
            _data.MainLogs.Add($"beamableAPI.User.id = {_beamableAPI.User.id}");
            _data.MainLogs.Add($"SimGameType.Teams.Count = {_simGameType.teams.Count}");

            _myMatchmaking = new MyMatchmaking(
                _beamableAPI.Experimental.MatchmakingService,
                _simGameType,
                _beamableAPI.User.id);

            _myMatchmaking.OnProgress.AddListener(MyMatchmaking_OnProgress);
            _myMatchmaking.OnComplete.AddListener(MyMatchmaking_OnComplete);
            _myMatchmaking.OnError.AddListener(MyMatchmaking_OnError);

            Refresh();
        }

        public async void StartMatchmaking()
        {
            string log = $"StartMatchmaking()";
            
            //Debug.Log(log);
            _data.SessionState = SessionState.Connecting;
            _data.MatchmakingLogs.Add(log);
            Refresh();
            
            await _myMatchmaking.StartMatchmaking();
        }
        
        public async void CancelMatchmaking()
        {
            string log = $"CancelMatchmaking()";
            //Debug.Log(log);
            
            _data.SessionState = SessionState.Disconnecting;
            _data.MatchmakingLogs.Add(log);
            Refresh();
            
            await _myMatchmaking.CancelMatchmaking();
            
            _data.SessionState = SessionState.Disconnected;
            Refresh();
        }
        

        public void Refresh()
        {
            string refreshLog = $"Refresh() ...\n" +
                                $"\n * MainLogs.Count = {_data.MainLogs.Count}" +
                                $"\n * MatchmakingLogs.Count = {_data.MatchmakingLogs.Count}\n\n";
         
            //Debug.Log(refreshLog);
         
            // Send relevant data to the UI for rendering
            OnRefreshed?.Invoke(_data);
        }
        
        //  Event Handlers  -------------------------------
        private void MyMatchmaking_OnProgress(MyMatchmakingResult myMatchmakingResult)
        {
            string log = $"OnProgress(), Players = {myMatchmakingResult.Players.Count} / {myMatchmakingResult.PlayerCountMax}";
            _data.MatchmakingLogs.Add(log);
            Refresh();
        }


        private void MyMatchmaking_OnComplete(MyMatchmakingResult myMatchmakingResult)
        {
            string log = $"OnComplete()...\n" + 
                         $"\tMatchId = {myMatchmakingResult.MatchId}\n " +
                         $"\tLocalPlayer = {myMatchmakingResult.LocalPlayer}\n" +
                         $"\tPlayers = {string.Join(",", myMatchmakingResult.Players)}\n";
            
            //Debug.Log(log);
            _data.SessionState = SessionState.Connected;
            _data.MatchmakingLogs.Add(log);
            Refresh();
        }


        private void MyMatchmaking_OnError(MyMatchmakingResult myMatchmakingResult)
        {
            _data.SessionState = SessionState.Disconnected;
            string log = $"OnError(), ErrorMessage = {myMatchmakingResult.ErrorMessage}\n";
            
            //Debug.Log(log);
            _data.MatchmakingLogs.Add(log);
            Refresh();
        }
    }
}

The following MyMatchmaking.cs is built on top of Beamable's MatchmakingService. It is provided as inspiration to game makers to create custom solutions.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Beamable.Common.Content;
using Beamable.Experimental.Api.Matchmaking;
using UnityEngine;
using UnityEngine.Events;

namespace Beamable.Examples.Services.MatchmakingService
{
   public class MyMatchmakingEvent : UnityEvent<MyMatchmakingResult>{}

   /// <summary>
   /// Contains the in-progress matchmaking data.
   /// When the process is complete, this contains
   /// the players list and the MatchId
   /// </summary>
   [Serializable]
   public class MyMatchmakingResult
   {
      //  Properties  -------------------------------------
      public List<long> Players
      {
         get
         {
            if (_matchmakingHandle == null || _matchmakingHandle.Status == null ||
                _matchmakingHandle.Status.Players == null)
            {
               return new List<long>();
            }
            return _matchmakingHandle.Status.Players.Select(i => long.Parse(i)).ToList();
         }
      }
      
      public int PlayerCountMin
      {
         get
         {
            int playerCountMin = 0;
            foreach (TeamContent teamContent in _simGameType.teams)
            {
               if (teamContent.minPlayers.HasValue)
               {
                  playerCountMin += teamContent.minPlayers.Value;
               }
            }
            return playerCountMin;
         }
      }
      
      public int PlayerCountMax
      {
         get
         {
            int playerCountMax = 0;
            foreach (TeamContent teamContent in _simGameType.teams)
            {
               playerCountMax += teamContent.maxPlayers;
            }
            return playerCountMax;
         }
      }
      
      public string MatchId
      {
         get
         {
            return _matchmakingHandle?.Match?.matchId;
         }
      }
      
      public long LocalPlayer { get { return _localPlayer; } }
      public SimGameType SimGameType { get { return _simGameType; } }
      public MatchmakingHandle MatchmakingHandle { get { return _matchmakingHandle; } set { _matchmakingHandle = value;} }
      public bool IsInProgress { get { return _isInProgress; } set { _isInProgress = value;} }

      //  Fields  -----------------------------------------
      public string ErrorMessage = "";
      private long _localPlayer;
      private bool _isInProgress = false;
      private MatchmakingHandle _matchmakingHandle = null;
      private SimGameType _simGameType;

      //  Constructor  ------------------------------------
      public MyMatchmakingResult(SimGameType simGameType, long localPlayer)
      {
         _simGameType = simGameType;
         _localPlayer = localPlayer;
      }

      //  Other Methods  ----------------------------------
      public override string ToString()
      {
         return $"[MyMatchmakingResult (" +
            $"MatchId = {MatchId}, " +
            $"Teams = {_matchmakingHandle?.Match?.teams}, " +
            $"players.Count = {Players?.Count})]";
      }
   }

   /// <summary>
   /// This example is for reference only. Use as
   /// inspiration for usage in production.
   /// </summary>
   public class MyMatchmaking
   {
      //  Events  -----------------------------------------
      public MyMatchmakingEvent OnProgress = new MyMatchmakingEvent();
      public MyMatchmakingEvent OnComplete = new MyMatchmakingEvent();
      public MyMatchmakingEvent OnError = new MyMatchmakingEvent();
      
      
      //  Properties  -------------------------------------
      public MyMatchmakingResult MyMatchmakingResult { get { return _myMatchmakingResult; } }

      
      //  Fields  -----------------------------------------
      private MyMatchmakingResult _myMatchmakingResult = null;
      private Experimental.Api.Matchmaking.MatchmakingService _matchmakingService = null;
      public const string TimeoutErrorMessage = "Timeout";
      

      //  Constructor  ------------------------------------
      public MyMatchmaking(Experimental.Api.Matchmaking.MatchmakingService matchmakingService,
         SimGameType simGameType, long localPlayerDbid)
      {
         _matchmakingService = matchmakingService;
         _myMatchmakingResult = new MyMatchmakingResult(simGameType, localPlayerDbid);
      }

      
      //  Other Methods  ----------------------------------
      public async Task StartMatchmaking()
      {
         if (_myMatchmakingResult.IsInProgress)
         {
            Debug.LogError($"MyMatchmaking.StartMatchmaking() failed. " +
                           $"IsInProgress must not be {_myMatchmakingResult.IsInProgress}.\n\n");
            return;
         }
         
         _myMatchmakingResult.IsInProgress = true;
         
         _myMatchmakingResult.MatchmakingHandle =  await _matchmakingService.StartMatchmaking(
            _myMatchmakingResult.SimGameType.Id,
            maxWait: TimeSpan.FromSeconds(10),
            updateHandler: handle =>
            {
               OnUpdateHandler(handle);
            },
            readyHandler: handle =>
            {
               // Call both
               OnUpdateHandler(handle);
               OnReadyHandler(handle);
            },
            timeoutHandler: handle =>
            {
               // Call both
               OnUpdateHandler(handle);
               OnTimeoutHandler(handle);
            });
      }


      public async Task CancelMatchmaking()
      {
         await _matchmakingService.CancelMatchmaking(_myMatchmakingResult.MatchmakingHandle.Tickets[0].ticketId);
      }
      
      
      //  Event Handlers  ----------------------------------
      private void OnUpdateHandler(MatchmakingHandle handle)
      {
         OnProgress.Invoke(_myMatchmakingResult);
      }
      
      private void OnReadyHandler(MatchmakingHandle handle)
      {
         Debug.Assert(handle.State == MatchmakingState.Ready);
         _myMatchmakingResult.IsInProgress = false;
         OnComplete.Invoke(_myMatchmakingResult);
      }
      
      private void OnTimeoutHandler(MatchmakingHandle handle)
      {
         _myMatchmakingResult.IsInProgress = false;
         _myMatchmakingResult.ErrorMessage = TimeoutErrorMessage;
         OnError?.Invoke(_myMatchmakingResult);
      }
   }
}

Did this page help you?