Search Unity

Question Syncing Players over Relay and Lobbies

Discussion in 'Multiplayer' started by Devology, May 7, 2022.

  1. Devology

    Devology

    Joined:
    Feb 27, 2019
    Posts:
    2
    Hey guys,

    multiplayer coding is quite new for me, so I tried to implement it into my VR Game. I used this tutorial.


    I build one version for Windows which hosts the server with this bit of code.

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Threading.Tasks;
    5. using TMPro;
    6. using Unity.Netcode;
    7. using Unity.Netcode.Transports.UTP;
    8. using Unity.Services.Authentication;
    9. using Unity.Services.Core;
    10. using Unity.Services.Lobbies;
    11. using Unity.Services.Lobbies.Models;
    12. using Unity.Services.Relay;
    13. using Unity.Services.Relay.Models;
    14. using UnityEngine;
    15. using UnityEngine.Events;
    16. using UnityEngine.SceneManagement;
    17. using static ServerManager;
    18.  
    19. public class ServerManager : MonoBehaviour
    20. {
    21.  
    22.    public TMP_Text debugAdress;
    23.    public TMP_Text debugPort;
    24.    public TMP_Text debugLobby;
    25.    public TMP_Text debugLobbyId;
    26.    public TMP_Text debugJoinCode;
    27.    public TMP_Text consoleText;
    28.  
    29.    public GameObject debugPlayerMenu;
    30.    public GameObject normalMenu;
    31.  
    32.    private RuntimePlatform platform;
    33.  
    34.  
    35.    public static ServerManager _instance;
    36.    public static ServerManager Instance => _instance;
    37.    private string _lobbyId;
    38.    private RelayHostData _hostData;
    39.    public UnityAction<string> UpdateState;
    40.    public UnityAction MatchFound;
    41.    public List<string> lobbyPlayers = new List<string>();
    42.  
    43.    private void AppendTextToConsole(string text)
    44.    {
    45.       string date = DateTime.Now.Day + "." + DateTime.Now.Month + "." + DateTime.Now.Year + " " + DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second;
    46.       consoleText.text += date+" | "+text + "\n";
    47.    }
    48.  
    49.    private void Awake()
    50.    {
    51.       platform = Application.platform;
    52.  
    53.       if (_instance is null)
    54.       {
    55.          _instance = this;
    56.          return;
    57.       }
    58.  
    59.       Destroy(this);
    60.    }
    61.  
    62.    async void Start()
    63.    {
    64.       if(platform == RuntimePlatform.WindowsPlayer || platform == RuntimePlatform.LinuxPlayer)
    65.       {
    66.          AppendTextToConsole("Start logging");
    67.          AppendTextToConsole("Windows application detected.");
    68.          AppendTextToConsole("Disable client functions");
    69.          debugPlayerMenu.SetActive(false);
    70.          normalMenu.SetActive(false);
    71.  
    72.          // Initialize unity services
    73.          AppendTextToConsole("Initialize Unity Services...");
    74.          await UnityServices.InitializeAsync();
    75.  
    76.          // Unity Login
    77.          AppendTextToConsole("Start login process...");
    78.          await SignInAnonymouslyAsync();
    79.  
    80.          AppendTextToConsole("Create new server...");
    81.          CreateMatch();
    82.  
    83.          // Subscribe to NetworkManager events
    84.          NetworkManager.Singleton.OnClientConnectedCallback += ClientConnected;
    85.          NetworkManager.Singleton.OnClientDisconnectCallback += ClientDisconnected;
    86.       }
    87.    }
    88.  
    89.    private void ClientConnected(ulong id)
    90.    {
    91.      AppendTextToConsole("New Player with id "+id+" connected to the server...");
    92.       lobbyPlayers.Add(id.ToString());
    93.    }
    94.  
    95.    private void ClientDisconnected(ulong id)
    96.    {
    97.       AppendTextToConsole("Player with id " + id + " disconnected from the server...");
    98.       lobbyPlayers.Remove(id.ToString());
    99.       try
    100.       {
    101.          Lobbies.Instance.RemovePlayerAsync(_lobbyId, id.ToString());
    102.       }
    103.       catch (Exception)
    104.       {
    105.       }
    106.    }
    107.  
    108.    async Task SignInAnonymouslyAsync()
    109.    {
    110.       try
    111.       {
    112.          await AuthenticationService.Instance.SignInAnonymouslyAsync();
    113.          AppendTextToConsole("Login as "+AuthenticationService.Instance.PlayerId+" successfull...");
    114.       }
    115.       catch (Exception ex)
    116.       {
    117.          AppendTextToConsole("Login failed: "+ex.Message);
    118.          Debug.LogException(ex);
    119.       }
    120.    }
    121.  
    122.    private async void CreateMatch()
    123.    {
    124.       AppendTextToConsole("Start new scene...");
    125.       SceneManager.LoadSceneAsync("TestScene");
    126.  
    127.       // External connections
    128.       int maxConnections = 16;
    129.  
    130.       try
    131.       {
    132.          // Create RELAY object
    133.          Allocation allocation = await Relay.Instance.CreateAllocationAsync(maxConnections);
    134.          _hostData = new RelayHostData
    135.          {
    136.             Key = allocation.Key,
    137.             Port = (ushort)allocation.RelayServer.Port,
    138.             AllocationID = allocation.AllocationId,
    139.             AllocationIDBytes = allocation.AllocationIdBytes,
    140.             ConnectionData = allocation.ConnectionData,
    141.             IPv4Address = allocation.RelayServer.IpV4
    142.          };
    143.  
    144.          // Retrieve JoinCode
    145.          _hostData.JoinCode = await Relay.Instance.GetJoinCodeAsync(allocation.AllocationId);
    146.  
    147.          string lobbyName = AuthenticationService.Instance.PlayerId;
    148.  
    149.          debugLobby.text = lobbyName;
    150.          AppendTextToConsole("Create new lobby...");
    151.  
    152.  
    153.          int maxPlayers = 16;
    154.          AppendTextToConsole("Set max players to "+maxPlayers+"...");
    155.  
    156.          CreateLobbyOptions options = new CreateLobbyOptions();
    157.          options.IsPrivate = false;
    158.          AppendTextToConsole("Set server private mode to "+options.IsPrivate.ToString()+"...");
    159.  
    160.          // Put the JoinCode in the lobby data, visible by every member
    161.          options.Data = new Dictionary<string, DataObject>()
    162.             {
    163.                 {
    164.                     "joinCode", new DataObject(
    165.                         visibility: DataObject.VisibilityOptions.Member,
    166.                         value: _hostData.JoinCode)
    167.                 },
    168.             };
    169.  
    170.          // Create the lobby
    171.          var lobby = await Lobbies.Instance.CreateLobbyAsync(lobbyName, maxPlayers, options);
    172.  
    173.          // Save Lobby ID for later uses
    174.          _lobbyId = lobby.Id;
    175.          AppendTextToConsole("New lobby id is "+_lobbyId+"...");
    176.  
    177.          debugJoinCode.text = lobby.Data["joinCode"].Value;
    178.          AppendTextToConsole("Lobby join code generated "+debugJoinCode.text+"...");
    179.  
    180.  
    181.          debugLobbyId.text = lobby.Id;
    182.  
    183.          // Heartbeat the lobby every 15 seconds.
    184.          AppendTextToConsole("Start pinging server each 15 seconds ...");
    185.          StartCoroutine(HeartbeatLobbyCoroutine(lobby.Id, 15));
    186.  
    187.          // Now that RELAY and LOBBY are set...
    188.  
    189.          // Set Transports data
    190.          NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(
    191.              _hostData.IPv4Address,
    192.              _hostData.Port,
    193.              _hostData.AllocationIDBytes,
    194.              _hostData.Key,
    195.              _hostData.ConnectionData);
    196.  
    197.          // Finally start host
    198.          AppendTextToConsole("Starting server as host...");
    199.  
    200.          NetworkManager.Singleton.StartHost();
    201.  
    202.          debugAdress.text = NetworkManager.Singleton.GetComponent<UnityTransport>().ConnectionData.Address;
    203.          debugPort.text = NetworkManager.Singleton.GetComponent<UnityTransport>().ConnectionData.Port.ToString();
    204.       }
    205.       catch (LobbyServiceException e)
    206.       {
    207.          Console.WriteLine(e);
    208.          throw;
    209.       }
    210.    }
    211.  
    212.    IEnumerator HeartbeatLobbyCoroutine(string lobbyId, float waitTimeSeconds)
    213.    {
    214.       var delay = new WaitForSecondsRealtime(waitTimeSeconds);
    215.       while (true)
    216.       {
    217.          AppendTextToConsole("Ping successfull...");
    218.          AppendTextToConsole("Active players");
    219.  
    220.          UpdatePlayerOptions options = new UpdatePlayerOptions();
    221.  
    222.          foreach (string playerID in lobbyPlayers)
    223.          {
    224.             AppendTextToConsole(playerID);
    225.             Lobbies.Instance.UpdatePlayerAsync(lobbyId, playerID, options);
    226.  
    227.          }
    228.  
    229.          Lobbies.Instance.SendHeartbeatPingAsync(lobbyId);
    230.          yield return delay;
    231.       }
    232.    }
    233.  
    234.    private void OnDestroy()
    235.    {
    236.       // We need to delete the lobby when we're not using it
    237.       Lobbies.Instance.DeleteLobbyAsync(_lobbyId);
    238.    }
    239.  
    240.    public struct RelayHostData
    241.    {
    242.       public string JoinCode;
    243.       public string IPv4Address;
    244.       public ushort Port;
    245.       public Guid AllocationID;
    246.       public byte[] AllocationIDBytes;
    247.       public byte[] ConnectionData;
    248.       public byte[] Key;
    249.    }
    250. }
    251.  
    and the other version which should connect to this server for android (Occulus Quest 2) with this code:

    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Threading.Tasks;
    5. using TMPro;
    6. using Unity.Netcode;
    7. using Unity.Netcode.Transports.UTP;
    8. using Unity.Services.Authentication;
    9. using Unity.Services.Core;
    10. using Unity.Services.Lobbies;
    11. using Unity.Services.Lobbies.Models;
    12. using Unity.Services.Relay;
    13. using Unity.Services.Relay.Models;
    14. using UnityEngine;
    15. using UnityEngine.Events;
    16. using UnityEngine.SceneManagement;
    17. using static ServerManager;
    18.  
    19. public class ClientManager : MonoBehaviour
    20. {
    21.    public TMP_Text debugPlayerName;
    22.    public TMP_Text debugPlayerToken;
    23.    public TMP_Text consoleText;
    24.  
    25.    public GameObject debugServerMenu;
    26.  
    27.    private RuntimePlatform platform;
    28.  
    29.  
    30.    public static ClientManager _instance;
    31.    public static ClientManager Instance => _instance;
    32.    private string _lobbyId;
    33.    private RelayJoinData _joinData;
    34.    public UnityAction<string> UpdateState;
    35.    public UnityAction MatchFound;
    36.  
    37.    private void AppendTextToConsole(string text)
    38.    {
    39.       string date = DateTime.Now.Day + "." + DateTime.Now.Month + "." + DateTime.Now.Year + " " + DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":" + DateTime.Now.Second;
    40.       consoleText.text += date + " | " + text + "\n";
    41.    }
    42.  
    43.    private void Awake()
    44.    {
    45.       platform = Application.platform;
    46.  
    47.       if (_instance is null)
    48.       {
    49.          _instance = this;
    50.          return;
    51.       }
    52.  
    53.       Destroy(this);
    54.    }
    55.  
    56.    async void Start()
    57.    {
    58.       if (platform == RuntimePlatform.Android || platform == RuntimePlatform.WindowsEditor)
    59.       {
    60.          AppendTextToConsole("Start logging");
    61.          AppendTextToConsole("Android application detected.");
    62.          AppendTextToConsole("Disable server functions");
    63.          debugServerMenu.SetActive(false);
    64.  
    65.          // Initialize unity services
    66.          AppendTextToConsole("Initialize Unity Services...");
    67.          await UnityServices.InitializeAsync();
    68.  
    69.          // Unity Login
    70.          AppendTextToConsole("Start login process...");
    71.          await SignInAnonymouslyAsync();
    72.  
    73.          AppendTextToConsole("Search for lobby...");
    74.          FindMatch();
    75.       }
    76.    }
    77.  
    78.    async Task SignInAnonymouslyAsync()
    79.    {
    80.       try
    81.       {
    82.          await AuthenticationService.Instance.SignInAnonymouslyAsync();
    83.          AppendTextToConsole("Login as " + AuthenticationService.Instance.PlayerId + " successfull...");
    84.          debugPlayerName.text = AuthenticationService.Instance.PlayerId;
    85.          debugPlayerToken.text = AuthenticationService.Instance.AccessToken.ToString().Substring(0,10)+"...";
    86.       }
    87.       catch (Exception ex)
    88.       {
    89.          AppendTextToConsole("Login failed: " + ex.Message);
    90.          Debug.LogException(ex);
    91.       }
    92.    }
    93.  
    94.    public async void FindMatch()
    95.    {
    96.       if (platform == RuntimePlatform.Android || platform == RuntimePlatform.WindowsEditor)
    97.       {
    98.          try
    99.          {
    100.             // Looking for a lobby
    101.  
    102.             // Add options to the matchmaking (mode, rank, etc..)
    103.             QuickJoinLobbyOptions options = new QuickJoinLobbyOptions();
    104.  
    105.             // Quick-join a random lobby
    106.             Lobby lobby = await Lobbies.Instance.QuickJoinLobbyAsync(options);
    107.  
    108.             // Retrieve the Relay code previously set in the create match
    109.             string joinCode = lobby.Data["joinCode"].Value;
    110.  
    111.             JoinAllocation allocation = await Relay.Instance.JoinAllocationAsync(joinCode);
    112.  
    113.             // Create Object
    114.             _joinData = new RelayJoinData
    115.             {
    116.                Key = allocation.Key,
    117.                Port = (ushort)allocation.RelayServer.Port,
    118.                AllocationID = allocation.AllocationId,
    119.                AllocationIDBytes = allocation.AllocationIdBytes,
    120.                ConnectionData = allocation.ConnectionData,
    121.                HostConnectionData = allocation.HostConnectionData,
    122.                IPv4Address = allocation.RelayServer.IpV4
    123.             };
    124.  
    125.             // Set transport data
    126.             NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(
    127.                 _joinData.IPv4Address,
    128.                 _joinData.Port,
    129.                 _joinData.AllocationIDBytes,
    130.                 _joinData.Key,
    131.                 _joinData.ConnectionData,
    132.                 _joinData.HostConnectionData);
    133.  
    134.             AppendTextToConsole("Found lobby: "+lobby.Name);
    135.             AppendTextToConsole("Found lobbyID: "+lobby.Id);
    136.             AppendTextToConsole("Found lobby join code: "+ lobby.Data["joinCode"].Value);
    137.             AppendTextToConsole("Connected to server: "+NetworkManager.Singleton.GetComponent<UnityTransport>().ConnectionData.Address+":"+ NetworkManager.Singleton.GetComponent<UnityTransport>().ConnectionData.Port.ToString());
    138.  
    139.  
    140.             // Finally start the client
    141.             NetworkManager.Singleton.StartClient();
    142.  
    143.          }
    144.          catch (LobbyServiceException e)
    145.          {
    146.             AppendTextToConsole("Lobby search failed: " + e.Message);
    147.          }
    148.       }
    149.  
    150.    }
    151.  
    152.    public struct RelayJoinData
    153.    {
    154.       public string JoinCode;
    155.       public string IPv4Address;
    156.       public ushort Port;
    157.       public Guid AllocationID;
    158.       public byte[] AllocationIDBytes;
    159.       public byte[] ConnectionData;
    160.       public byte[] HostConnectionData;
    161.       public byte[] Key;
    162.    }
    163. }
    164.  
    Creating a lobby, joining a lobby, leaving a lobby is not a problem, but I dont know how to sync the players. Did I miss anything?

    The world should be hosted by the server and the server should sync the players input. The calculation of each player is made by each occulus quest, so the server should only host the map and do the syncing.

    I hope anyone can point me in the right direction.

    Greetings Max
     

    Attached Files:

    Last edited: May 7, 2022
  2. Jicefrost

    Jicefrost

    Joined:
    May 13, 2019
    Posts:
    40
    What exactly you want to sync? coordinates? speeds? Health ?