The game from 2 devices android - android, windows - windows work properly. All objects spawn and are visible to everyone.
The game from 2 devices android - windows does not work. The server spawns its objects properly, but the client does not see them. The client will not spawn anything.
In which direction to look for a solution?
The scene is loaded when 2 players connect. This happens all the time.
For quick search methods: FindMatch, CreateMatch.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Netcode;
using Unity.Netcode.Transports.UTP;
using Unity.Services.Authentication;
using Unity.Services.Core;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using Unity.Services.Relay;
using Unity.Services.Relay.Models;
using UnityEngine;
using UnityEngine.Events;
public class LobbyController : MonoBehaviour
{
[SerializeField] private int _connectedClientsForStart = 2;
private static LobbyController _instance;
public static LobbyController Instance => _instance;
private string _lobbyId;
private RelayHostData _hostData;
private RelayJoinData _joinData;
private bool _authentificationSucceeded;
// Setup events
// Notify state update
public UnityAction<string> UpdateState;
// Notify Match found
public UnityAction MatchFound;
public UnityAction<string> PrivateLobbyCreated;
private void Awake()
{
if (_instance is null) { _instance = this; }
else {Destroy(this); }
}
async void Start()
{
await UnityServices.InitializeAsync();
SetupEvents();
await SignInAnonymouslyAsync();
NetworkManager.Singleton.OnClientConnectedCallback += ClientConnected;
}
#region Network events
private void ClientConnected(ulong id)
{
// Player with id connected to our session
Debug.Log("Connected player with id: " + id);
UpdateState?.Invoke("Match found!");
MatchFound?.Invoke();
if (NetworkManager.Singleton.ConnectedClients.Count >= _connectedClientsForStart)
{
NetworkManager.Singleton.SceneManager.LoadScene("CemeteryMultiplayerLightNet", UnityEngine.SceneManagement.LoadSceneMode.Single);
}
}
#endregion
#region UnityLogin
void SetupEvents()
{
AuthenticationService.Instance.SignedIn += () => {
// Shows how to get a playerID
Debug.Log($"PlayerID: {AuthenticationService.Instance.PlayerId}");
// Shows how to get an access token
Debug.Log($"Access Token: {AuthenticationService.Instance.AccessToken}");
};
AuthenticationService.Instance.SignInFailed += (err) => {
Debug.LogError(err);
};
AuthenticationService.Instance.SignedOut += () => {
Debug.Log("Player signed out.");
};
}
async Task SignInAnonymouslyAsync()
{
try
{
await AuthenticationService.Instance.SignInAnonymouslyAsync();
_authentificationSucceeded = true;
Debug.Log("Sign in anonymously succeeded!");
UpdateState?.Invoke("Authentication succeeded. Click play!");
}
catch (Exception ex)
{
// Notify the player with the proper error message
Debug.LogException(ex);
UpdateState?.Invoke("Authentification failed");
}
}
#endregion
#region Lobby
public async void FindMatch(string lobbyCode = "")
{
if(!_authentificationSucceeded) { return; }
UpdateState?.Invoke("Looking for a match...");
try
{
// Looking for a lobby
// Add options to the matchmaking (mode, rank, etc..)
QuickJoinLobbyOptions options = new QuickJoinLobbyOptions();
Lobby lobby = lobbyCode.Equals("") ? await Lobbies.Instance.QuickJoinLobbyAsync(options) : await Lobbies.Instance.JoinLobbyByCodeAsync(lobbyCode);
// Retrieve the Relay code previously set in the create match
string joinCode = lobby.Data["joinCode"].Value;
JoinAllocation allocation = await Relay.Instance.JoinAllocationAsync(joinCode);
_joinData = new RelayJoinData
{
Key = allocation.Key,
Port = (ushort)allocation.RelayServer.Port,
AllocationID = allocation.AllocationId,
AllocationIDBytes = allocation.AllocationIdBytes,
ConnectionData = allocation.ConnectionData,
HostConnectionData = allocation.HostConnectionData,
IPv4Address = allocation.RelayServer.IpV4
};
NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(
_joinData.IPv4Address,
_joinData.Port,
_joinData.AllocationIDBytes,
_joinData.Key,
_joinData.ConnectionData,
_joinData.HostConnectionData);
NetworkManager.Singleton.StartClient();
UpdateState?.Invoke("Match found!");
MatchFound?.Invoke();
return;
}
catch (LobbyServiceException e)
{
if (lobbyCode.Equals("")) { CreateMatch(); }
else { UpdateState?.Invoke("Failed lobby code"); }
}
}
public async void CreateMatch(bool isPrivateLobby = false)
{
UpdateState?.Invoke("Creating a new match...");
// External connections
int maxConnections = 1;
try
{
// Create RELAY object
Allocation allocation = await Relay.Instance.CreateAllocationAsync(maxConnections);
_hostData = new RelayHostData
{
Key = allocation.Key,
Port = (ushort)allocation.RelayServer.Port,
AllocationID = allocation.AllocationId,
AllocationIDBytes = allocation.AllocationIdBytes,
ConnectionData = allocation.ConnectionData,
IPv4Address = allocation.RelayServer.IpV4
};
// Retrieve JoinCode
_hostData.JoinCode = await Relay.Instance.GetJoinCodeAsync(allocation.AllocationId);
string lobbyName = "game_lobby";
int maxPlayers = 2;
CreateLobbyOptions options = new CreateLobbyOptions();
options.IsPrivate = isPrivateLobby;
// Put the JoinCode in the lobby data, visible by every member
options.Data = new Dictionary<string, DataObject>()
{
{
"joinCode", new DataObject(
visibility: DataObject.VisibilityOptions.Member,
value: _hostData.JoinCode)
},
};
// Create the lobby
var lobby = await Lobbies.Instance.CreateLobbyAsync(lobbyName, maxPlayers, options);
// Save Lobby ID for later uses
_lobbyId = lobby.Id;
// Heartbeat the lobby every 15 seconds.
StartCoroutine(HeartbeatLobbyCoroutine(lobby.Id, 15));
// Now that RELAY and LOBBY are set...
// Set Transports data
NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(
_hostData.IPv4Address,
_hostData.Port,
_hostData.AllocationIDBytes,
_hostData.Key,
_hostData.ConnectionData);
// Finally start host
NetworkManager.Singleton.StartHost();
UpdateState?.Invoke("Waiting for players...");
if(isPrivateLobby) { PrivateLobbyCreated?.Invoke(lobby.LobbyCode); }
}
catch (LobbyServiceException e)
{
Console.WriteLine(e);
}
}
IEnumerator HeartbeatLobbyCoroutine(string lobbyId, float waitTimeSeconds)
{
var delay = new WaitForSecondsRealtime(waitTimeSeconds);
while (true)
{
Lobbies.Instance.SendHeartbeatPingAsync(lobbyId);
Debug.Log("Lobby Heartbit");
yield return delay;
}
}
public void ExitLobby()
{
Lobbies.Instance.DeleteLobbyAsync(_lobbyId);
UpdateState?.Invoke("");
}
private void OnDestroy()
{
// We need to delete the lobby when we're not using it
Lobbies.Instance.DeleteLobbyAsync(_lobbyId);
}
#endregion
/// <summary>
/// RelayHostData represents the necessary informations
/// for a Host to host a game on a Relay
/// </summary>
public struct RelayHostData
{
public string JoinCode;
public string IPv4Address;
public ushort Port;
public Guid AllocationID;
public byte[] AllocationIDBytes;
public byte[] ConnectionData;
public byte[] Key;
}
/// <summary>
/// RelayHostData represents the necessary informations
/// for a Host to host a game on a Relay
/// </summary>
public struct RelayJoinData
{
public string JoinCode;
public string IPv4Address;
public ushort Port;
public Guid AllocationID;
public byte[] AllocationIDBytes;
public byte[] ConnectionData;
public byte[] HostConnectionData;
public byte[] Key;
}
}