I got it working, it was way more work than I anticipated. the way everyone goes around thi sproblem by using relay and lobbies is very mediocre, it’s basically a series of workarounds that require a lot of network bandwidth, creates race conditions also, in summary, it’s a mess. I hope you guys manage to get the multiplay sorted out because using tools like lobby to get around sharing the relay code forces developer to build a terrible network architecture, not scalable at all. Basically, if you think of the architecture of this thing, it’s using relay as a reverse proxy and lobby as an improvised messaging queue. WebGL is a very prominent format for many things, and let’s not set aside the amount of technical debt this adds to the users of Unity. There is a ridiculous amount of posts about this very subject to treat this as a nice to have.
To anyone in the future having htis issue, this is how you setup the server in Multiplay to use relay and WSS:
protected async Task<string> InitializeRelayServer()
{
await UnityServices.InitializeAsync();
await AuthenticationService.Instance.SignInAnonymouslyAsync();
Allocation allocation = await RelayService.Instance.CreateAllocationAsync(3);
string joinCode = await RelayService.Instance.GetJoinCodeAsync(allocation.AllocationId);
Debug.Log($"Relay server started with join code: {joinCode}");
//If you use Relay SDK instead of Multiplayer, this method changes, there are many examples in the internet of that specific case.
var relayServerData = AllocationUtils.ToRelayServerData(allocation, "wss");
NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(relayServerData);
NetworkManager.Singleton.StartServer();
return joinCode;
}
...
string relayJoinCode = await InitializeRelayServer();
var lobbyData = new Dictionary<string, DataObject>();
lobbyData.Add("relayCode", new DataObject(
visibility: DataObject.VisibilityOptions.Public,
value: relayJoinCode,
//Store it in a free string slot in your lobby
index: DataObject.IndexOptions.S3));
var serverConfig = MultiplayService.Instance.ServerConfig;
try
{
//treat your multiplayer data, decide on game modes, or do whatever you do with it
...
CreateLobbyOptions createLobbyOptions = new CreateLobbyOptions();
createLobbyOptions.Data = lobbyData;
var matchmakingTicket = JsonConvert.DeserializeObject<MatchData>(payloadJson);
lobby = await LobbyService.Instance.CreateLobbyAsync(matchmakingTicket.MatchId, 3, createLobbyOptions);
serverQueryHandler = await MultiplayService.Instance.StartServerQueryHandlerAsync(2, "WarringTriad", "Random", "", "");
NetworkManager.Singleton.OnClientDisconnectCallback += OnClientDisconnect;
NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnect;
if (serverConfig.AllocationId != "")
{
Debug.Log("Server Started!");
}
}
await MultiplayService.Instance.ReadyServerForPlayersAsync();
On the client side, you need a retry method to get around the race condition of clients tryuign to access the lobby before it has been created:
//You call this method on the update to receive updates from the matchmaking. I won't go into detail of how to use matchmaking, there are other tutos for that
public async void PollMatchmakerTicket()
{
TicketStatusResponse ticketStatusResponse =
await MatchmakerService.Instance.GetTicketAsync(createTicketResponse.Id);
if (ticketStatusResponse != null)
{
if (ticketStatusResponse.Type == typeof(MultiplayAssignment))
{
MultiplayAssignment multiplayAssignment = ticketStatusResponse.Value as MultiplayAssignment;
switch (multiplayAssignment.Status)
{
case MultiplayAssignment.StatusOptions.Found:
{
Debug.Log("Found!");
createTicketResponse = null;
QueryForServerLobby(multiplayAssignment.MatchId);
break;
}
case MultiplayAssignment.StatusOptions.InProgress:
break;
case MultiplayAssignment.StatusOptions.Failed:
{
Debug.Log("Failed!");
createTicketResponse = null;
waitingForOpponetGameObject.SetActive(false);
break;
}
case MultiplayAssignment.StatusOptions.Timeout:
{
Debug.Log("Timed out...");
createTicketResponse = null;
waitingForOpponetGameObject.SetActive(false);
break;
}
}
}
}
}
private void QueryForServerLobby(string matchId)
{
//This is my retry mechanism, the loading screen shows a spinner and then retries an awaitable method with a delay of 2 seconds between each attempt, receiving the matchId as parameter for the awaitable method
StartCoroutine(loadingScreenController.ExecuteRetryableActionWithData(ConnectToServerLobby, (response) => { }, new List<string>{matchId}));
}
//Lobby querying, startng relay client and connecting to relay server.
private async Task<bool> ConnectToServerLobby(string matchId)
{
QueryLobbiesOptions options = new QueryLobbiesOptions();
options.Count = 1;
// Filter for lobby with matching Matchmaker ticket
options.Filters = new List<QueryFilter>()
{
new(
field: QueryFilter.FieldOptions.Name,
op: QueryFilter.OpOptions.EQ,
value: matchId)
};
QueryResponse lobbies = await LobbyService.Instance.QueryLobbiesAsync(options);
string lobbyId = lobbies.Results.First().Id;
Lobby lobby = await LobbyService.Instance.JoinLobbyByIdAsync(lobbyId);
string relayCode = lobby.Data["relayCode"].Value;
await JoinRelayServer(relayCode);
return true;
}
private async Task JoinRelayServer(string joinCode)
{
JoinAllocation joinAllocation = await RelayService.Instance.JoinAllocationAsync(joinCode);
// Configure the transport with relay data
var transport = NetworkManager.Singleton.GetComponent<UnityTransport>();
transport.SetRelayServerData(AllocationUtils.ToRelayServerData(joinAllocation, "wss"));
NetworkManager.Singleton.StartClient();
Debug.Log("Client connected to Relay Server.");
}