Last Updated: 11/9/2022, 8:40:46 AM

# Matchmaking

# Quick Reference

References
using AccelByte.Api;
using AccelByte.Core;
using AccelByte.Models;
Matchmaking Notification Events
AccelBytePlugin.GetLobby().MatchmakingCompleted += result => {};
AccelBytePlugin.GetLobby().ReadyForMatchConfirmed += result => {};
AccelBytePlugin.GetLobby().DSUpdated += result =>{};
AccelBytePlugin.GetLobby().RematchmakingNotif += result =>{};
Start Matchmaking
string gameMode;

AccelBytePlugin.GetLobby().StartMatchmaking(gameMode, result =>
{
    //Check this is not an error
    if (!result.IsError)
    {
        Debug.Log("Started matchmaking is successful");
    }
});
Cancel Matchmaking
string gameMode;

AccelBytePlugin.GetLobby().CancelMatchmaking(gameMode, result =>
{
    //Check this is not an error
    if (!result.IsError)
    {
        Debug.Log("Canceled matchmaking is successful");
    }
});
Ready Matchmaking
string matchId;

AccelBytePlugin.GetLobby().ConfirmReadyForMatch(matchId, result =>
{
    //Check this is not an error
    if (!result.IsError)
    {
        Debug.Log("Ready for match is successful");
    }
});

# Quickstart Guide

In this tutorial, you will learn how to use the Matchmaking services. This guide assumes that you already implemented the Lobby (opens new window), Friends (opens new window), and Party (opens new window) services.

Since Matchmaking implementation can vary for each game, you can familiarize yourself with other concepts and classes in the LobbyModels.cs file inside the plugin SDK.

We will start by adding simple matchmaking logic into the game.

  1. Ensure you already have a Matchmaking Ruleset set up in the Admin Portal, or follow this guide (opens new window) to make a Matchmaking Ruleset now.

  2. Create a new script called MatchmakingHandler.cs and attach it to the AccelByteHandler gameObject.

  3. Add the following AccelByte libraries to the top of the script:

    using AccelByte.Api;
    using AccelByte.Models;
    using AccelByte.Core;
    
  4. Store the game mode’s name based on the existing Matchmaking Ruleset. In this tutorial, we will use 1vs1.

    // Current selected game mode
    private string gameMode = "1vs1";
    
  5. Add some basic Matchmaking functionalities in MatchmakingHandler.cs:

    • Start Matchmaking
      This function requires gameMode to start matchmaking. Ensure you have already created a party with at least one player before adding this function.

      private void FindMatch()
      {
          AccelBytePlugin.GetLobby().StartMatchmaking(gameMode, result =>
          {
                  // Check this is an error
                  if (result.IsError)
                  {
                      Debug.Log($"Unable to start matchmaking: error code: {result.Error.Code} message: {result.Error.Message}");
                  }
                  else
                  {
                      Debug.Log("Started matchmaking is successful");
                  }
          });
      
      }
      
    • Cancel Matchmaking
      This function also requires gameMode to cancel the current matchmaking action.

      private void CancelMatchmaking()
      {
          AccelBytePlugin.GetLobby().CancelMatchmaking(gameMode, result =>
          {
                  //Check this is an error
                  if (result.IsError)
                  {
                      Debug.Log($"Unable to cancel matchmaking: error code: {result.Error.Code} message: {result.Error.Message}");
                  }
                  else
                  {
                      Debug.Log("Canceled matchmaking is successful");
                  }
          });
      }
      
    • Ready Matchmaking
      This function requires matchId to confirm if a player is ready for a match. You can find matchId in the MatchmakingCompleted notification event.

      private void ReadyMatchmaking()
      {
          AccelBytePlugin.GetLobby().ConfirmReadyForMatch(matchId, result =>
          {
                  //Check this is an error
                  if (result.IsError)
                  {
                      Debug.Log($"Unable to ready for match: error code: {result.Error.Code} message: {result.Error.Message}");
                  }
                  else
                  {
                      Debug.Log("Ready for match is successful");
                  }
          });
      }
      
  6. To update any matchmaking changes, use the Lobby service notification events. In this section, we will add events in LobbyHandler.cs on the ConnectToLobby() function, and add a Debug.Log to confirm that everything is functional. Each event is explained in more detail below.

    • Matchmaking Completed

      This notification event occurs when the player finds a match, or the party leader starts or cancels matchmaking. You can differentiate these events by using the matchmaking status, such as in the following example:

        ```
        _lobby.MatchmakingCompleted += result =>
        {
            // Called when party leader start a match
            if (result.Value.status == "start")
            {
                Debug.Log($"Party leader start matchmaking.");
            }
            // Called when party leader cancel a match
            else if (result.Value.status == "cancel")
            {
                Debug.Log($"Party leader cancel matchmaking.");
            }
            // Called when found a match
            else if (result.Value.status == "done")
            {
                Debug.Log($"Found a match. Match id: {result.Value.matchId}");
            }
        };
      
        ```
      
    • start: party members receive this notification event when the party leader starts matchmaking.

    • cancel: party members receive this notification event when the party leader cancels matchmaking.

    • done: party members receive this notification event when a match is found.

    • Ready for Match Confirmed

      This notification event occurs when a player in the lobby declares they are ready to play the match. You can follow this to get the log when the player is ready to play.

        ```
        `_lobby.ReadyForMatchConfirmed += result => { Debug.Log($"User {result.Value.userId} is ready!");};`
        ```
      
    • DSUpdated

      This notification event occurs when all players are ready to play in the current match, and this prompts an update from the Dedicated Server. To connect to the server, you must wait until the status from the DSUpdated notification event becomes ready or busy, such as in the following code:

        ```
        _lobby.DSUpdated += result =>
        {
            if (result.Value.status == "READY" || result.Value.status == "BUSY")
            {
                Debug.Log($"Game is started");
            }
        };
      
        ```
      
    • RematchmakingNotif

      You may also want to retry matchmaking if the first matchmaking attempt fails. Use this event below to do this.

        ```
        _lobby.RematchmakingNotif += result => { Debug.Log($"Find another match");};
        ```
      

      The result data only contains ban durations. Based on this, there are two rematchmaking behaviors:

    • banDuration == 0 (seconds): matchmaking will restart automatically. The banDuration will be set to 0 if all the members in the party declared themselves ready to play during the last matchmaking attempt.

    • banDuration == 30 (seconds): the players will need to restart the matchmaking manually from the Lobby. This ban duration will take effect if one or more party members were not ready to play in the last matchmaking attempt.

Congratulations! You have learnt how to use the Matchmaking service!

Continue on for a step by step example of the UI and code implementation. Otherwise, you are now ready to move on to Armada.

# Step by Step Guide

UI Implementation
  1. Open your Lobby panel/scene and add the following UI objects to the same panel as your Friends Management button:

    • In the Game Mode dropdown, make sure the following items are listed:

      Unity

    • Find Match button

      Unity

  2. Create a new panel or scene for Matchmaking and add the following matchmaking-related panels and UI elements:

    • Find Match panel
    • Header text
    • Loading image/bar (or any other object to indicate loading progress)
    • Count Up (Time Elapsed) text
    • Cancel button

    Unity

  3. Ready Match panel

    • Exit button (optional)
    • Header text
    • Player Box panel with Username text. The maximum number of players is 10, so duplicate this object until you have created 10.
    • Countdown text
    • Ready button

    Unity

  4. Game panel

    You can create an empty panel as this will be a temporary panel for now. You will change this to a real game scene later.

    Unity

Code Implementation

Now that you have some basic Matchmaking functionalities, you can implement these with your UI.

  1. Before continuing, ensure you have the necessary Matchmaking Ruleset in place to accommodate two different game modes. In this tutorial, we use 1vs1 and 2vs2 game modes. Follow this guide to add or adjust your own Matchmaking Ruleset.

  2. Prepare your gameMode. Create a separate script called GameModeHandler.cs and add the following enum to the top of the script:

    public enum GameMode
    {
        None,
        versusOne,
        versusTwo
    }
    
  3. Create a new static class called GameModeHandler. Add the following static functions to handle anything related to Game Mode and its Total Players:

    public static class GameModeHandler
    {
        // This string is based on mode in Admin Portal
        private const string None = "None"; // This is default and not registered in AP
        private const string VersusOne = "1vs1";
        private const string VersusTwo = "2vs2";
    
        /// <summary>
        /// Parse Game Mode from enum to string
        /// </summary>
        /// <param name="mode"> Game Mode enum that will be parsed into string</param>
        /// <returns></returns>
        public static string GetString(this GameMode mode)
        {
            switch (mode)
            {
                case GameMode.versusOne:
                    return VersusOne;
                case GameMode.versusTwo:
                    return VersusTwo;
    
                case GameMode.None:
                default:
                    return None;
            }
        }
    
        /// <summary>
        /// Parse Game Mode to return total players
        /// </summary>
        /// <param name="mode"> Game Mode enum that will be parsed into total players</param>
        /// <returns></returns>
        public static int GetTotalPlayers(this GameMode mode)
        {
            switch (mode)
            {
                case GameMode.versusOne:
                    return 2;
                case GameMode.versusTwo:
                    return 4;
    
                case GameMode.None:
                default:
                    return 0;
            }
        }
    
        /// <summary>
        /// Parse Game Mode from string to GameMode
        /// </summary>
        /// <param name="mode"> Game Mode string that will be parsed into enum</param>
        /// <returns></returns>
        public static GameMode ToGameMode(this string mode)
        {
            switch (mode)
            {
                case VersusOne:
                    return GameMode.versusOne;
                case VersusTwo:
                    return GameMode.versusTwo;
    
                case None:
                default:
                    return GameMode.None;
            }
        }
    }
    
  4. Create a script called ConnectionHandler.cs and add ip, port, and uport. Create this as a static class so you can get and set these variables from anywhere.

    using System;
    
    public static class ConnectionHandler
    {
        // Get/ set ip in string
        public static string ip = "localhost";
        // Get/ set port in integer
        public static int port = 7777;
    
        // Get port in ushort format
        public static ushort uPort => Convert.ToUInt16(port);
    }
    
    
  5. Create a script to obtain a command line argument. You will use this later to switch between environments in order to test your game on your local PC or using AccelByte’s server.

    public static bool GetLocalArgument()
    {
        bool isLocal = false;
    
        // Get Local Argument from the system
        // You can run local by adding -local when executing the game/ server
        string[] args = System.Environment.GetCommandLineArgs();
        foreach (var arg in args)
        {
            if (arg.Contains("local"))
            {
                isLocal = true;
                break;
            }
        }
    
        return isLocal;
    }
    
    
  6. Go to the MatchmakingHandler.cs script and add the following code after the libraries at the top of the script:

    ...
    using UnityEngine.UI;
    
    using UI = UnityEngine.UI;
    
  7. Change the gameMode variable’s default value to gameMode (null).

    // Current selected game mode
    private GameMode gameMode;
    
  8. Add the following variables. These will be used later to store Matchmaking-related data.

    private const string DefaultCountUp = "Time Elapsed: 00:00";
    // This default count down time must be similar with the Lobby Config in the Admin Portal
    private const string DefaultCountDown = "20";
    
    // Current active match id
    private string matchId;
    
  9. Add the following Matchmaking UI references:

    #region UI
    [SerializeField]
    private GameObject matchmakingWindow;
    [SerializeField]
    private GameObject findMatchWindow;
    [SerializeField]
    private GameObject readyMatchWindow;
    [SerializeField]
    private GameObject gameWindow;
    
    [SerializeField]
    private Dropdown gameModeDropdown;
    
    #region Button
    
    [SerializeField]
    private Button findMatchButton;
    [SerializeField]
    private Button readyButton;
    [SerializeField]
    private Button cancelButton;
    [SerializeField]
    private Button exitButton;
    #endregion
    
    [SerializeField]
    private Text countUpText;
    [SerializeField]
    private Text countDownText;
    
    [SerializeField]
    private MatchmakingUsernamePanel[] usernameList;
    #endregion
    
  10. Add the following functions to reset the Username text, frame color, and all the timers:

    /// Clean up the username text and reset frame color
    /// After that, populate current party members to the username text
    private void CleanAndPopulateUsernameText()
    {
        // Emptying username text in the matchmaking window
        foreach (var username in usernameList)
        {
            username.SetUsernameText("");
            username.SetUsernameFrameColor(Color.white);
        }
    
        // Populate party members
        int count = 0;
        foreach (var username in GetPartyHandler().partyMembers)
        {
            Debug.Log($"Populate party member with username: {username.Value}");
    
            usernameList[count].SetUsernameText(username.Value);
            count++;
        }
    }
    
    /// Reset the count down and count up into default value
    private void ResetTimerText()
    {
        countDownText.enabled = true;
    
        countDownText.text = DefaultCountDown;
        countUpText.text = DefaultCountUp;
    }
    
  11. Since the Matchmaking panel contains multiple windows (views), create an enum in MatchmakingHandler.cs to help change windows. Add this enum after the UI references:

    private enum MatchmakingWindows
    {
        Lobby,
        FindMatch,
        Matchmaking,
        Game
    }
    
  12. Create a function that states what will happen when you change into the selected window.

    private MatchmakingWindows DisplayWindow
    {
        get => DisplayWindow;
        set
        {
            switch (value)
            {
                case MatchmakingWindows.Lobby:
                    matchmakingWindow.SetActive(false);
                    findMatchWindow.SetActive(false);
                    readyMatchWindow.SetActive(false);
                    gameWindow.SetActive(false);
                    break;
    
                case MatchmakingWindows.FindMatch:
                    ResetTimerText();
    
                    matchmakingWindow.SetActive(true);
                    findMatchWindow.SetActive(true);
                    readyMatchWindow.SetActive(false);
                    gameWindow.SetActive(false);
                    break;
    
                case MatchmakingWindows.Matchmaking:
                    ResetTimerText();
                    CleanAndPopulateUsernameText();
    
                    matchmakingWindow.SetActive(true);
                    findMatchWindow.SetActive(false);
                    readyMatchWindow.SetActive(true);
                    gameWindow.SetActive(false);
                    break;
    
                case MatchmakingWindows.Game:
                    matchmakingWindow.SetActive(true);
                    findMatchWindow.SetActive(false);
                    readyMatchWindow.SetActive(false);
                    gameWindow.SetActive(true);
                    break;
    
                default:
                    break;
            }
        }
    }
    
  13. Since you will frequently need to call party data from PartyHandler.cs_ _ using the Lobby instance, add the following code into MatchmakingHandler.cs to simplify the call:

    // Grab the current Party Handler
    private PartyHandler GetPartyHandler()
    {
        return LobbyHandler.Instance.partyHandler;
    }
    
  14. Create a function that validates all the data needed for Matchmaking:

    /// Validate empty party, game mode, and party members count based on game mode
    private bool ValidateGameModeAndParty()
    {
        // Set game mode based on game mode selector dropdown
        gameMode = gameModeDropdown.options[gameModeDropdown.value].text.ToGameMode();
    
        // Avoid to choose default game mode
        if (gameModeDropdown.value == 0)
        {
            LobbyHandler.Instance.WriteLogMessage("Choose the game mode", Color.red);
            return false;
        }
        // Check if user is not in the party
        if (GetPartyHandler().partyMembers == null || GetPartyHandler().partyMembers?.Count == 0)
        {
            LobbyHandler.Instance.WriteLogMessage("You are not in the party", Color.red);
            return false;
        }
        // Avoid party members exceed from the game mode
        if (gameMode == GameMode.versusOne && GetPartyHandler().partyMembers?.Count == 1 ||
            gameMode == GameMode.versusTwo && GetPartyHandler().partyMembers?.Count <= 2)
        {
            return true;
        }
    
        LobbyHandler.Instance.WriteLogMessage("Party members are exceeding from the selected game mode", Color.red);
        return false;
    }
    
  15. Now that you have set the preceding functions, you can update your basic functionalities. Earlier, you only added debugging to the output; now you can add some code to update the UIs.

    Check the game mode and party. After it passes, start matchmaking
    private void FindMatch()
    {
        // Check if the party is already created,
        // game mode is not empty, and
        // party /// members are not exceeding from the selected game mode
        if (!ValidateGameModeAndParty())
        {
                return;
        }
        AccelBytePlugin.GetLobby().StartMatchmaking(gameMode.GetString()
    , result =>
        {
            if (result.IsError)
            ...
            else
            {
                ...
                DisplayWindow = MatchmakingWindows.FindMatch;
    ...
    
    /// Cancel find match and back to the Lobby
    private void CancelMatchmaking()
    {
        AccelBytePlugin.GetLobby().CancelMatchmaking(gameMode.GetString(), result =>
        {
            //Check this is not an error
            if (result.IsError)
            ...
            else
            {
                ...
                DisplayWindow = MatchmakingWindows.Lobby;
    ...
    
  16. Create a new function to set up Matchmaking UIs when initializing.

    /// Setup UI
    public void SetupMatchmaking()
    {
        // Setup button listener
        findMatchButton.onClick.AddListener(FindMatch);
        readyButton.onClick.AddListener(ReadyMatchmaking);
        cancelButton.onClick.AddListener(CancelMatchmaking);
        exitButton.onClick.AddListener(() => DisplayWindow = MatchmakingWindows.Lobby);
    }
    
  17. Insert the SetupMatchmaking function into MenuHandler.cs. When a player navigates to the Lobby menu, this will trigger the setup.

    LobbyButton.onClick.AddListener(() =>
    {
        GetComponent<MatchmakingHandler>().SetupMatchmaking();
    
        // Other actions
        ...
    });
    
    
  18. Create some functions that will update your UIs in the notification events update in MatchmakingHandler.cs.

    #region Notification
    /// Called when party leader find a match, cancel match, or found a match
    public void MatchmakingCompletedNotification(MatchmakingNotif result)
    {
        // Handle if match id is empty
        if (string.IsNullOrEmpty(result.matchId))
        {
            // Called when party leader find a match
            if (result.status == "start")
            {
                DisplayWindow = MatchmakingWindows.FindMatch;
            }
            // Called when party leader cancel a match
            else if (result.status == "cancel")
            {
                DisplayWindow = MatchmakingWindows.Lobby;
            }
    
            return;
        }
    
        // Called when found a match
        matchId = result.matchId;
        Debug.Log($"Found a match. Match id: {matchId}");
    
        DisplayWindow = MatchmakingWindows.Matchmaking;
    }
    
    // Called when there is a player who set ready to play
    public void ReadyForMatchConfirmedNotification(ReadyForMatchConfirmation result)
    {
        // Display unknown player who ready in the current match into right panel
        if (!GetPartyHandler().partyMembers.ContainsKey(result.userId))
        {
            for (int i = GetPartyHandler().partyMembers.Count; i < usernameList.Length; i++)
            {
                if (string.IsNullOrEmpty(usernameList[i].GetUsernameText()))
                {
                    AccelBytePlugin.GetUser().GetUserByUserId(result.userId, getUserResult =>
                    {
                        usernameList[i].SetUsernameText(getUserResult.Value.displayName);
                    });
    
                    usernameList[i].SetUsernameFrameColor(Color.green);
                    break;
                }
            }
        }
        // Display party member who ready in the current match into left panel
        else
        {
            for (int i = 0; i < GetPartyHandler().partyMembers.Count; i++)
            {
                if (usernameList[i].GetUsernameText() == GetPartyHandler().partyMembers[result.userId])
                {
                    usernameList[i].SetUsernameFrameColor(Color.green);
                    break;
                }
            }
        }
    }
    
    /// Called when all player in the current match is already ready to play
    public void DSUpdatedNotification(DsNotif result)
    {
        countDownText.enabled = false;
        ConnectionHandler.ip = result.ip;
        ConnectionHandler.port = result.port;
    
        if (result.status != "READY" && result.status != "BUSY") return;
    
        Debug.Log($"Game is started");
    
        DisplayWindow = MatchmakingWindows.Game;
    }
    
    /// Called when matchmaking is canceled when there are not enough players ready to play
    public void RematchmakingNotif(RematchmakingNotification result)
    {
        // Find another match if the ban duration is zero
        if (result.banDuration == 0)
        {
            Debug.Log($"Find another match");
    
            DisplayWindow = MatchmakingWindows.FindMatch;
            return;
        }
    
        // Display ban duration to party notification
        LobbyHandler.Instance.WriteLogMessage($"You must wait for {result.banDuration} s to start matchmaking", Color.red);
    
        DisplayWindow = MatchmakingWindows.Lobby;
    }
    #endregion
    
  19. Create the following new functions in NotificationHandler.cs to call the functions that you created earlier:

    // Collection of friend notifications
    #region Matchmaking
    /// Called when matchmaking is found
    /// <param name="result"> Contains data of status and match id</param>
    public void OnMatchmakingCompleted(Result<MatchmakingNotif> result)
    {
    GetComponent<MatchmakingHandler>().MatchmakingCompletedNotification(result.Value);
    }
    
    /// <summary>
    /// Called when user send ready for match confirmation
    /// </summary>
    /// <param name="result"> Contains data of user id and match id</param>
    public void OnReadyForMatchConfirmed(Result<ReadyForMatchConfirmation> result)
    {
    GetComponent<MatchmakingHandler>().ReadyForMatchConfirmedNotification(result.Value);
    }
    
    /// <summary>
    /// Called when all user is already confirmed the readiness
    /// </summary>
    /// <param name="result"> Contains data of ds notification</param>
    public void OnDSUpdated(Result<DsNotif> result)
    {
        GetComponent<MatchmakingHandler>().DSUpdatedNotification(result.Value);
    }
    
    /// <summary>
    /// Called when there is user who not confirm the match
    /// - The party that has a user who did not confirm the match will get banned and need to start matchmaking again
    /// - The other party will start matchmaking automatically if ban duration is zero
    /// </summary>
    /// <param name="result"> Contains data of ban duration</param>
    public void OnRematchmakingNotif(Result<RematchmakingNotification> result)
    {
        GetComponent<MatchmakingHandler>().RematchmakingNotif(result.Value);
    }
    #endregion
    
  20. Update the notification events in LobbyHandler.cs:

    public void ConnectToLobby()
    {
        ...
        //Matchmaking
        _lobby.MatchmakingCompleted += notificationHandler.OnMatchmakingCompleted;
        _lobby.ReadyForMatchConfirmed += notificationHandler.OnReadyForMatchConfirmed;
        _lobby.RematchmakingNotif += notificationHandler.OnRematchmakingNotif;
        _lobby.DSUpdated += notificationHandler.OnDSUpdated;
    }
    ...
    
    public void RemoveLobbyListeners()
    {
        ...
    
        //Matchmaking
        _lobby.MatchmakingCompleted -= notificationHandler.OnMatchmakingCompleted;
        _lobby.ReadyForMatchConfirmed -= notificationHandler.OnReadyForMatchConfirmed;
        _lobby.RematchmakingNotif -= notificationHandler.OnRematchmakingNotif;
        _lobby.DSUpdated -= notificationHandler.OnDSUpdated;
    }
    ...
    
  21. In the Unity Editor, on the AccelByteHandler gameobject, drag the necessary objects to the exposed variables of the script component.

    Unity

    Expand the MatchmakingPanel and drag the objects to the remaining exposed variables:

    Unity

    In the Script component, expand the Username list and drag the objects to their corresponding variables:

    Unity

  22. Create a new script called MatchmakingManagementPanel.cs. Attach it to the MatchmakingPanel gameObject and add the following UI library to the top of the script:

    using UnityEngine.UI;
    
    1. Add the following UI references and local variables to hold temporary data:
    [SerializeField]
    private Image loadingImage;
    
    [SerializeField]
    private Text countUpText;
    
    [SerializeField]
    private Text countDownText;
    
    private bool isWaiting;
    private float deltaTime;
    
  23. Create a coroutine that will update the loading (timer) animation every 0.1 seconds.

    /// Animate the loading bar each 0.1 seconds
    private IEnumerator StartLoadingAnimation()
    {
        // Avoid the loading animation is being called when the Async is not finished yet
        isWaiting = true;
    
        // Animate the loading image
        loadingImage.transform.Rotate(0, 0, -45);
    
        // Wait 0.1 seconds before next animation is executed
        yield return new WaitForSeconds(0.1f);
    
        // Set boolean so it can be called again in the Update
        isWaiting = false;
    }
    
  24. Add the following code to create some functions that will update the Find Match and Ready countdown timers.

    /// Start count up timer to show time elapsed for waiting to find a match
    private void StartCountup()
    {
        if (!countUpText.isActiveAndEnabled) return;
    
        // Parse the time elapsed text into minutes and seconds (int)
        string time = countUpText.text.Substring(countUpText.text.IndexOf(':') + 2);
        int minutes = int.Parse(time.Substring(0, time.IndexOf(':')));
        int seconds = int.Parse(time.Substring(time.IndexOf(':') + 1));
    
        // Add 1 second increment
        int timer = minutes * 60 + seconds + 1;
    
        // Parse the time into text with format mm:ss
        countUpText.text = $"Time Elapsed: {string.Format("{0:00}", (timer / 60))}:{string.Format("{0:00}", (timer % 60))}";
    }
    
    /// Start the countdown timer to show time remaining to set ready to play
    private void StartCountdown()
    {
        // Do nothing if the count down text is not active
        if (!countDownText.isActiveAndEnabled) return;
    
        // Parse from text into time (int)
        int timer = int.Parse(countDownText.text);
    
        // Decrease 1 second and parse into text
        countDownText.text = Mathf.Round(timer - 1).ToString();
    }
    
  25. Add OnEnable() and Update() into MatchmakingManagementPanel.cs after the UI references:

    private void OnEnable()
    {
        // Reset boolean when object changes into enable
        isWaiting = false;
    }
    
    private void Update()
    {
        // Start loading animation
        if (!isWaiting)
        {
            StartCoroutine(StartLoadingAnimation());
        }
    
        // Start count down and count up timer
        if (countUpText.isActiveAndEnabled || countDownText.isActiveAndEnabled)
        {
            if (deltaTime >= 1)
            {
                deltaTime = 0;
                StartCountup();
                StartCountdown();
            }
            else
            {
                deltaTime += Time.deltaTime;
            }
        }
    }
    
  26. In the Unity Editor, in your scene, on the MatchmakingPanel gameObject, drag the necessary objects to the exposed variables.

    Unity

  27. Create a new script called MatchmakingUsernamePanel.cs and attach it to all PlayerObject gameObjects.

    Add the following UI library to the top of the MatchmakingUsernamePanel.cs script:

    using UnityEngine.UI;
    
  28. Add the following UI references and local variables to hold temporary data:

    [SerializeField]
    private Text UsernameText;
    [SerializeField]
    private Image UsernameFrameImage;
    
  29. Add the following code to update the PlayerBox UI:

    public string GetUsernameText()
    {
        return UsernameText.text;
    }
    
    public void SetUsernameText(string text)
    {
        if (UsernameText == null) return;
    
        UsernameText.text = text;
    }
    
    public void SetUsernameFrameColor(Color color)
    {
        UsernameFrameImage.color = color;
    }
    
  30. In the Unity Editor, open the PlayerBox object and drag the following objects to their variables:

    Unity

    NOTE

    Save time by attaching the MatchmakingUsernamePanel.cs script to a prefab and then duplicating it for each gameObject.

Congratulations! You have now fully implemented the Matchmaking service.

Proceed to the next section to learn how to implement Armada.

# Full Code

MatchmakingHandler.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using AccelByte.Api;
using AccelByte.Models;
using AccelByte.Core;
using UnityEngine;
using UnityEngine.UI;

using UI = UnityEngine.UI;


public class MatchmakingHandler : MonoBehaviour
{
    private const string DefaultCountUp = "Time Elapsed: 00:00";
    // This default count down time must be similar with the Lobby Config in the Admin Portal
    private const string DefaultCountDown = "20";

    // Current selected game mode
    private GameMode gameMode;
    // Current active match id
    private string matchId;

    #region UI
    [SerializeField]
    private GameObject matchmakingWindow;
    [SerializeField]
    private GameObject findMatchWindow;
    [SerializeField]
    private GameObject readyMatchWindow;
    [SerializeField]
    private GameObject gameWindow;

    [SerializeField]
    private Dropdown gameModeDropdown;

    #region Button

    [SerializeField]
    private Button findMatchButton;
    [SerializeField]
    private Button readyButton;
    [SerializeField]
    private Button cancelButton;
    [SerializeField]
    private Button exitButton;
    #endregion

    [SerializeField]
    private Text countUpText;
    [SerializeField]
    private Text countDownText;

    [SerializeField]
    private UI.Image loadingImage;

    [SerializeField]
    private MatchmakingUsernamePanel[] usernameList;
    #endregion

    private enum MatchmakingWindows
    {
        Lobby,
        FindMatch,
        Matchmaking,
        Game
    }

    private MatchmakingWindows DisplayWindow
    {
        get => DisplayWindow;
        set
        {
            switch (value)
            {
                case MatchmakingWindows.Lobby:
                    matchmakingWindow.SetActive(false);
                    findMatchWindow.SetActive(false);
                    readyMatchWindow.SetActive(false);
                    gameWindow.SetActive(false);
                    break;

                case MatchmakingWindows.FindMatch:
                    ResetTimerText();

                    matchmakingWindow.SetActive(true);
                    findMatchWindow.SetActive(true);
                    readyMatchWindow.SetActive(false);
                    gameWindow.SetActive(false);
                    break;

                case MatchmakingWindows.Matchmaking:
                    ResetTimerText();
                    CleanAndPopulateUsernameText();

                    matchmakingWindow.SetActive(true);
                    findMatchWindow.SetActive(false);
                    readyMatchWindow.SetActive(true);
                    gameWindow.SetActive(false);
                    break;

                case MatchmakingWindows.Game:
                    matchmakingWindow.SetActive(true);
                    findMatchWindow.SetActive(false);
                    readyMatchWindow.SetActive(false);
                    gameWindow.SetActive(true);
                    break;

                default:
                    break;
            }
        }
    }

    /// <summary>
    /// Grab the current Party Handler
    /// </summary>
    private PartyHandler GetPartyHandler()
    {
        return LobbyHandler.Instance.partyHandler;
    }

    /// <summary>
    /// Setup UI
    /// </summary>
    public void SetupMatchmaking()
    {
        // Setup button listener
        findMatchButton.onClick.AddListener(FindMatch);
        readyButton.onClick.AddListener(ReadyMatchmaking);
        cancelButton.onClick.AddListener(CancelMatchmaking);
        exitButton.onClick.AddListener(() => DisplayWindow = MatchmakingWindows.Lobby);
    }

    /// <summary>
    /// Check the game mode and party. After it passes, start matchmaking
    /// </summary>
    private void FindMatch()
    {
        // Check if the party is already created,
        // game mode is not empty, and
        // party members are not exceeding from the selected game mode
        if (!ValidateGameModeAndParty())
        {
            return;
        }

        AccelBytePlugin.GetLobby().StartMatchmaking(gameMode, result =>
        {
            //Check this is not an error
            if (result.IsError)
            {
                Debug.Log($"Unable to start matchmaking: error code: {result.Error.Code} message: {result.Error.Message}");
            }
            else
            {
                Debug.Log("Started matchmaking is successful");

                DisplayWindow = MatchmakingWindows.FindMatch;
            }
        });
    }

    /// <summary>
    /// Cancel find match and back to the Lobby
    /// </summary>
    private void CancelMatchmaking()
    {
        AccelBytePlugin.GetLobby().CancelMatchmaking(gameMode, result =>
        {
            //Check this is not an error
            if (result.IsError)
            {
                Debug.Log($"Unable to cancel matchmaking: error code: {result.Error.Code} message: {result.Error.Message}");
            }
            else
            {
                Debug.Log("Canceled matchmaking is successful");

                DisplayWindow = MatchmakingWindows.Lobby;
            }
        });
    }

    /// <summary>
    /// Set ready to play when matchmaking
    /// </summary>
    private void ReadyMatchmaking()
    {
        AccelBytePlugin.GetLobby().ConfirmReadyForMatch(matchId, result =>
        {
            //Check this is not an error
            if (result.IsError)
            {
                    Debug.Log($"Unable to ready for match: error code: {result.Error.Code} message: {result.Error.Message}");
            }
            else
            {
                    Debug.Log("Ready for match is successful");
            }
        });
    }

    #region Notification
    /// <summary>
    /// Called when party leader find a match, cancel match, or found a match
    /// </summary>
    /// <param name="result"> Contains data of status and match id</param>
    public void MatchmakingCompletedNotification(MatchmakingNotif result)
    {
        // Handle if match id is empty
        if (string.IsNullOrEmpty(result.matchId))
        {
            // Called when party leader find a match
            if (result.status == "start")
            {
                    DisplayWindow = MatchmakingWindows.FindMatch;
            }
            // Called when party leader cancel a match
            else if (result.status == "cancel")
            {
                    DisplayWindow = MatchmakingWindows.Lobby;
            }

            return;
        }

        // Called when found a match
        matchId = result.matchId;
        Debug.Log($"Found a match. Match id: {matchId}");

        DisplayWindow = MatchmakingWindows.Matchmaking;
    }

    /// <summary>
    /// Called when there is a player who set ready to play
    /// </summary>
    /// <param name="result"> Contains data of match id and user id</param>
    public void ReadyForMatchConfirmedNotification(ReadyForMatchConfirmation result)
    {
        // Display unknown player who ready in the current match into right panel
        if (!GetPartyHandler().partyMembers.ContainsKey(result.userId))
        {
            for (int i = GetPartyHandler().partyMembers.Count; i < usernameList.Length; i++)
            {
                if (string.IsNullOrEmpty(usernameList[i].GetUsernameText()))
                {
                    AccelBytePlugin.GetUser().GetUserByUserId(result.userId, getUserResult =>
                    {
                        usernameList[i].SetUsernameText(getUserResult.Value.displayName);
                    });

                    usernameList[i].SetUsernameFrameColor(Color.green);
                    break;
                }
            }
        }
        // Display party member who ready in the current match into left panel
        else
        {
            for (int i = 0; i < GetPartyHandler().partyMembers.Count; i++)
            {
                if (usernameList[i].GetUsernameText() == GetPartyHandler().partyMembers[result.userId])
                {
                    usernameList[i].SetUsernameFrameColor(Color.green);
                    break;
                }
            }
        }
    }

    /// <summary>
    /// Called when all player in the current match is already ready to play
    /// </summary>
    /// <param name="result"> contains data of status, match id, ip, port, etc</param>
    public void DSUpdatedNotification(DsNotif result)
    {
        countDownText.enabled = false;

        Debug.Log($"Game is started");

        DisplayWindow = MatchmakingWindows.Game;
    }

    /// <summary>
    /// Called when matchmaking is canceled due to not enough players being ready to play
    /// </summary>
    /// <param name="result"> contains data of ban duration </param>
    public void RematchmakingNotif(RematchmakingNotification result)
    {
        // Find another match if the ban duration is zero
        if (result.banDuration == 0)
        {
            Debug.Log($"Find another match");

            DisplayWindow = MatchmakingWindows.FindMatch;
            return;
        }

        // Display ban duration to party notification
        GetPartyHandler().WritePartyMessage($"[Matchmaking] You must wait for {result.banDuration} s to start matchmaking", Color.red);

        DisplayWindow = MatchmakingWindows.Lobby;
    }
    #endregion

    /// <summary>
    /// Validate empty party, game mode, and party members count based on game mode
    /// </summary>
    /// <returns> Return true value if the validation is passed and vice versa</returns>
    private bool ValidateGameModeAndParty()
    {
        // Set game mode based on game mode selector dropdown
        gameMode = gameModeDropdown.options[gameModeDropdown.value].text.ToGameMode();

        // Avoid to choose default game mode
        if (gameModeDropdown.value == 0)
        {
            LobbyHandler.Instance.WriteLogMessage("Choose the game mode", Color.red);
            return false;
        }
        // Check if user is not in the party
        if (GetPartyHandler().partyMembers == null || GetPartyHandler().partyMembers?.Count == 0)
        {
            LobbyHandler.Instance.WriteLogMessage("You are not in the party", Color.red);
            return false;
        }
        // Avoid party members exceed from the game mode
        if (gameMode == GameMode.versusOne && GetPartyHandler().partyMembers?.Count == 1 ||
        gameMode == GameMode.versusTwo && GetPartyHandler().partyMembers?.Count <= 2)
        {
            return true;
        }

        LobbyHandler.Instance.WriteLogMessage("Party members are exceeding from the selected game mode", Color.red);
        return false;
    }

    /// <summary>
    /// Clean up the username text and reset frame color
    /// After that, populate current party members to the username text
    /// </summary>
    private void CleanAndPopulateUsernameText()
    {
        // Emptying username text in the matchmaking window
        foreach (var username in usernameList)
        {
            username.SetUsernameText("");
            username.SetUsernameFrameColor(Color.white);
        }

        // Populate party members
        int count = 0;
        foreach (var username in GetPartyHandler().partyMembers)
        {
            Debug.Log($"Populate party member with username: {username.Value}");

            usernameList[count].SetUsernameText(username.Value);
            count++;
        }
    }

    /// <summary>
    /// Reset the count down and count up into default value
    /// </summary>
    private void ResetTimerText()
    {
        countDownText.enabled = true;

        countDownText.text = DefaultCountDown;
        countUpText.text = DefaultCountUp;
    }
}
MatchmakingManagementPanel.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class MatchmakingManagementPanel : MonoBehaviour
{
    [SerializeField]
    private Image loadingImage;

    [SerializeField]
    private Text countUpText;

    [SerializeField]
    private Text countDownText;

    private bool isWaiting;
    private float deltaTime;

    private void OnEnable()
    {
        // Reset boolean when object changes into enable
        isWaiting = false;
    }

    private void Update()
    {
        // Start loading animation
        if (!isWaiting)
        {
            StartCoroutine(StartLoadingAnimation());
        }

        // Start count down and count up timer
        if (countUpText.isActiveAndEnabled || countDownText.isActiveAndEnabled)
        {
            if (deltaTime >= 1)
            {
                deltaTime = 0;
                StartCountup();
                StartCountdown();
            }
            else
            {
                deltaTime += Time.deltaTime;
            }
        }
    }

    /// <summary>
    /// Animate the loading bar each 0.1 seconds
    /// </summary>
    private IEnumerator StartLoadingAnimation()
    {
        // Avoid the loading animation is being called when the Async is not finished yet
        isWaiting = true;

        // Animate the loading image
        loadingImage.transform.Rotate(0, 0, -45);

        // Wait 0.1 seconds before next animation is executed
        yield return new WaitForSeconds(0.1f);

        // Set boolean so it can be called again in the Update
        isWaiting = false;
    }

    /// <summary>
    /// Start count up timer to show time elapsed for waiting to find a match
    /// </summary>
    private void StartCountup()
    {
        if (!countUpText.isActiveAndEnabled) return;

        // Parse the time elapsed text into minutes and seconds (int)
        string time = countUpText.text.Substring(countUpText.text.IndexOf(':') + 2);
        int minutes = int.Parse(time.Substring(0, time.IndexOf(':')));
        int seconds = int.Parse(time.Substring(time.IndexOf(':') + 1));

        // Add 1 second increment
        int timer = minutes * 60 + seconds + 1;

        // Parse the time into text with format mm:ss
        countUpText.text = $"Time Elapsed: {string.Format("{0:00}", (timer / 60))}:{string.Format("{0:00}", (timer % 60))}";
    }

    /// <summary>
    /// Start the count down timer to show time remaining to set ready to play
    /// </summary>
    private void StartCountdown()
    {
        // Do nothing if the count down text is not active
        if (!countDownText.isActiveAndEnabled) return;

        // Parse from text into time (int)
        int timer = int.Parse(countDownText.text);

        // Decrease 1 second and parse into text
        countDownText.text = Mathf.Round(timer - 1).ToString();
    }
}
MatchmakingUsernamePanel.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using UnityEngine;
using UnityEngine.UI;

public class MatchmakingUsernamePanel : MonoBehaviour
{
    [SerializeField]
    private Text UsernameText;
    [SerializeField]
    private Image UsernameFrameImage;

    public string GetUsernameText()
    {
        return UsernameText.text;
    }

    public void SetUsernameText(string text)
    {
        UsernameText.text = text;
    }

    public void SetUsernameFrameColor(Color color)
    {
        UsernameFrameImage.color = color;
    }
}
LobbyHandler.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using UnityEngine;
using AccelByte.Api;

public class LobbyHandler : MonoBehaviour
{
    /// <summary>
    /// Private Instance
    /// </summary>
    static LobbyHandler _instance;

    /// <summary>
    /// The Instance Getter
    /// </summary>
    public static LobbyHandler Instance => _instance;

    /// <summary>
    /// The Instance Getter
    /// </summary>
    private Lobby _lobby;

    public GameObject LobbyWindow;

    #region Notification Box
    [Header("Notification Box")]
    [SerializeField]
    private Transform notificationBoxContentView;
    [SerializeField]
    private GameObject logMessagePrefab;
    #endregion

    [HideInInspector]
    public NotificationHandler notificationHandler;


    private void Awake()
    {
        // Check if another Instance is already created, and if so delete this one, otherwise destroy the object
        if (_instance != null && _instance != this)
        {
            Destroy(this);
            return;
        }
        else
        {
            _instance = this;
        }

        // Get the the object handler
        notificationHandler = gameObject.GetComponent<NotificationHandler>();
    }

    /// <summary>
    /// Connect to the <see cref="Lobby"/> and setup CallBacks
    /// </summary>
    public void ConnectToLobby()
    {
        //Get a reference to the instance of the Lobby
        _lobby = AccelBytePlugin.GetLobby();

        //Init menu handler
        GetComponent<MenuHandler>().Create();
        GetComponent<MenuHandler>().Menu.gameObject.SetActive(true);

        //Connection
        _lobby.Connected += notificationHandler.OnConnected;
        _lobby.Disconnecting += notificationHandler.OnDisconnecting;
        _lobby.Disconnected += notificationHandler.OnDisconnected;

        //Friends
        _lobby.FriendsStatusChanged += notificationHandler.OnFriendsStatusChanged;
        _lobby.FriendRequestAccepted += notificationHandler.OnFriendRequestAccepted;
        _lobby.OnIncomingFriendRequest += notificationHandler.OnIncomingFriendRequest;
        _lobby.FriendRequestCanceled += notificationHandler.OnFriendRequestCanceled;
        _lobby.FriendRequestRejected += notificationHandler.OnFriendRequestRejected;
        _lobby.OnUnfriend += notificationHandler.OnUnfriend;

        //Party
        _lobby.InvitedToParty += notificationHandler.OnInvitedToParty;
        _lobby.JoinedParty += notificationHandler.OnJoinedParty;
        _lobby.KickedFromParty += notificationHandler.OnKickedFromParty;
        _lobby.LeaveFromParty += notificationHandler.OnLeaveFromParty;
        _lobby.RejectedPartyInvitation += notificationHandler.OnRejectedPartyInvitation;
        _lobby.PartyDataUpdateNotif += notificationHandler.OnPartyDataUpdateNotif;

        //Matchmaking
        _lobby.MatchmakingCompleted += notificationHandler.OnMatchmakingCompleted;
        _lobby.ReadyForMatchConfirmed += notificationHandler.OnReadyForMatchConfirmed;
        _lobby.RematchmakingNotif += notificationHandler.OnRematchmakingNotif;
        _lobby.DSUpdated += notificationHandler.OnDSUpdated;

        //Connect to the Lobby
        if (!_lobby.IsConnected)
        {
            _lobby.Connect();
        }
    }

    public void RemoveLobbyListeners()
    {
        //Remove delegate from Lobby
        //Connection
        _lobby.Connected -= notificationHandler.OnConnected;
        _lobby.Disconnecting -= notificationHandler.OnDisconnecting;
        _lobby.Disconnected -= notificationHandler.OnDisconnected;

        //Friends
        _lobby.FriendsStatusChanged -= notificationHandler.OnFriendsStatusChanged;
        _lobby.FriendRequestAccepted -= notificationHandler.OnFriendRequestAccepted;
        _lobby.OnIncomingFriendRequest -= notificationHandler.OnIncomingFriendRequest;
        _lobby.FriendRequestCanceled -= notificationHandler.OnFriendRequestCanceled;
        _lobby.FriendRequestRejected -= notificationHandler.OnFriendRequestRejected;
        _lobby.OnUnfriend -= notificationHandler.OnUnfriend;

        //Party
        _lobby.InvitedToParty -= notificationHandler.OnInvitedToParty;
        _lobby.JoinedParty -= notificationHandler.OnJoinedParty;
        _lobby.KickedFromParty -= notificationHandler.OnKickedFromParty;
        _lobby.LeaveFromParty -= notificationHandler.OnLeaveFromParty;
        _lobby.RejectedPartyInvitation -= notificationHandler.OnRejectedPartyInvitation;
        _lobby.PartyDataUpdateNotif -= notificationHandler.OnPartyDataUpdateNotif;

        //Matchmaking
        _lobby.MatchmakingCompleted -= notificationHandler.OnMatchmakingCompleted;
        _lobby.ReadyForMatchConfirmed -= notificationHandler.OnReadyForMatchConfirmed;
        _lobby.RematchmakingNotif -= notificationHandler.OnRematchmakingNotif;
        _lobby.DSUpdated -= notificationHandler.OnDSUpdated;
    }

    public void DisconnectFromLobby()
    {
        if (AccelBytePlugin.GetLobby().IsConnected)
        {
            AccelBytePlugin.GetLobby().Disconnect();
        }
    }

    private void OnApplicationQuit()
    {
        // Attempt to Disconnect from the Lobby when the Game Quits
        DisconnectFromLobby();
    }

    /// <summary>
    /// Write the log message on the notification box
    /// </summary>
    /// <param name="text"> text that will be shown in the party notification</param>
    public void WriteLogMessage(string text, Color color)
    {
        LogMessagePanel logPanel = Instantiate(logMessagePrefab, notificationBoxContentView).GetComponent<LogMessagePanel>();
        logPanel.UpdateNotificationUI(text, color);
    }
}
MenuHandler.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class MenuHandler : MonoBehaviour
{
    public Transform Menu;

    public Button LobbyButton;
    public Button FriendsButton;

    private bool isInitialized = false;

    public void Create()
    {
        if (isInitialized) return;

        isInitialized = true;

        LobbyButton.onClick.AddListener(() =>
        {
            GetComponent<PartyHandler>().SetupParty();
            GetComponent<MatchmakingHandler>().SetupMatchmaking();

            Menu.gameObject.SetActive(false);
            GetComponent<LobbyHandler>().LobbyWindow.SetActive(true);
        });

        FriendsButton.onClick.AddListener(() =>
        {
            GetComponent<FriendsManagementHandler>().Setup(FriendsManagementHandler.ExitMode.Menu);
            Menu.gameObject.SetActive(false);
            GetComponent<FriendsManagementHandler>().FriendsManagementWindow.SetActive(true);
        });
    }  
}
NotificationHandler.cs
// Copyright (c) 2021 - 2022 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

using UnityEngine;
using AccelByte.Models;
using AccelByte.Core;

public class NotificationHandler : MonoBehaviour
{
    #region Notifications

    // Collection of connection notifications
    #region Connections
    /// <summary>
    /// Called when lobby is connected
    /// </summary>
    public void OnConnected()
    {
        Debug.Log("Lobby Connected");
    }

    /// <summary>
    /// Called when connection is disconnecting
    /// </summary>
    /// <param name="result"> Contains data of message</param>
    public void OnDisconnecting(Result<DisconnectNotif> result)
    {
        Debug.Log($"Lobby Disconnecting {result.Value.message}");
    }

    /// <summary>
    /// Called when connection is being disconnected
    /// </summary>
    /// <param name="result"> Contains data of websocket close code</param>
    public void OnDisconnected(WsCloseCode result)
    {
        Debug.Log($"Lobby Disconnected: {result}");
    }
    #endregion

    // Collection of friend notifications
    #region Friends
    /// <summary>
    /// Called when friend status is changed
    /// </summary>
    /// <param name="result"> Contains data of user id, availability, status, etc</param>
    public void OnFriendsStatusChanged(Result<FriendsStatusNotif> result)
    {
        GetComponent<FriendsManagementHandler>().UpdateFriends(result.Value);
    }

    /// <summary>
    /// Called when friend request is accepted
    /// </summary>
    /// <param name="result"> Contains data of friend's user id</param>
    public void OnFriendRequestAccepted(Result<Friend> result)
    {
        Debug.Log($"Accepted a Friend Request from user {result.Value.friendId}");
    }

    /// <summary>
    /// Called when there is incoming friend request
    /// </summary>
    /// <param name="result"> Contains data of friend's user id</param>
    public void OnIncomingFriendRequest(Result<Friend> result)
    {
        Debug.Log($"Received a Friend Request from user {result.Value.friendId}");
    }

    /// <summary>
    /// Called when friend is unfriend
    /// </summary>
    /// <param name="result"> Contains data of friend's user id</param>
    public void OnUnfriend(Result<Friend> result)
    {
        Debug.Log($"Unfriended User {result.Value.friendId}");
    }

    /// <summary>
    /// Called when friend request is canceled
    /// </summary>
    /// <param name="result"> Contains data of sender user id</param>
    public void OnFriendRequestCanceled(Result<Acquaintance> result)
    {
        Debug.Log($"Cancelled a Friend Request from user {result.Value.userId}");
    }

    /// <summary>
    /// Called when friend request is rejected
    /// </summary>
    /// <param name="result"> Contains data of rejector user id</param>
    public void OnFriendRequestRejected(Result<Acquaintance> result)
    {
        Debug.Log($"Rejected a Friend Request from user {result.Value.userId}");
    }
    #endregion

    // Collection of party notifications
    #region Party
    /// <summary>
    /// Called when user gets party invitation
    /// </summary>
    /// <param name="result"> Contains data of inviter, party id, and invitation token</param>
    public void OnInvitedToParty(Result<PartyInvitation> result)
    {
        GetComponent<PartyHandler>().InvitePartyNotification(result.Value);
    }

    /// <summary>
    /// Called when user joins to the party
    /// </summary>
    /// <param name="result"> Contains data of joined user id</param>
    public void OnJoinedParty(Result<JoinNotification> result)
    {
        GetComponent<PartyHandler>().JoinedPartyNotification(result.Value);
    }

    /// <summary>
    /// Called when user is kicked by party leader
    /// </summary>
    /// <param name="result"> Contains data of party leader's user id, party id, and kicked user id</param>
    public void OnKickedFromParty(Result<KickNotification> result)
    {
        GetComponent<PartyHandler>().KickPartyNotification();
    }

    /// <summary>
    /// Called when user leaves from the party
    /// </summary>
    /// <param name="result"> Contains data of party leader's user id and leaver user id</param>
    public void OnLeaveFromParty(Result<LeaveNotification> result)
    {
        GetComponent<PartyHandler>().LeavePartyNotification(result.Value);
    }

    /// <summary>
    /// Called when user rejects party invitation
    /// </summary>
    /// <param name="result"> Contains data of party id, party leader's user id, and rejector user id</param>
    public void OnRejectedPartyInvitation(Result<PartyRejectNotif> result)
    {
        Debug.Log("[Party-Notification] Invitee rejected a party invitation");
    }

    /// <summary>
    /// Called when party data is updated
    /// </summary>
    /// <param name="result"> Contains data of updated party</param>
    public void OnPartyDataUpdateNotif(Result<PartyDataUpdateNotif> result)
    {
        GetComponent<PartyHandler>().DisplayPartyData(result);
    }
    #endregion

    // Collection of friend notifications
    #region Matchmaking
    /// <summary>
    /// Called when matchmaking is found
    /// </summary>
    /// <param name="result"> Contains data of status and match id</param>
    public void OnMatchmakingCompleted(Result<MatchmakingNotif> result)
    {
        GetComponent<MatchmakingHandler>().MatchmakingCompletedNotification(result.Value);
    }

    /// <summary>
    /// Called when user send ready for match confirmation
    /// </summary>
    /// <param name="result"> Contains data of user id and match id</param>
    public void OnReadyForMatchConfirmed(Result<ReadyForMatchConfirmation> result)
    {
        GetComponent<MatchmakingHandler>().ReadyForMatchConfirmedNotification(result.Value);
    }

    /// <summary>
    /// Called when all user is already confirmed the readiness
    /// </summary>
    /// <param name="result"> Contains data of ds notification</param>
    public void OnDSUpdated(Result<DsNotif> result)
    {
        GetComponent<MatchmakingHandler>().DSUpdatedNotification(result.Value);
    }

    /// <summary>
    /// Called when there is user who not confirm the match
    /// - The party that has a user who did not confirm the match will get banned and need to start matchmaking again
    /// - The other party will start matchmaking automatically if ban duration is zero
    /// </summary>
    /// <param name="result"> Contains data of ban duration</param>
    public void OnRematchmakingNotif(Result<RematchmakingNotification> result)
    {
        GetComponent<MatchmakingHandler>().RematchmakingNotif(result.Value);
    }
    #endregion

    #endregion
}