Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Question Spawn player prefab on server only

Discussion in 'Netcode for GameObjects' started by Balphagore, Jan 28, 2022.

  1. Balphagore

    Balphagore

    Joined:
    Jul 18, 2019
    Posts:
    84
    Any client connected to the server instantiates the prefab on the server and all other clients. Is it possible to prevent clients from spawning prefabs for other clients? In my idea, clients should not contact each other directly. Each client-server connection must have only a client and a server, but the server must have the player prefabs of all clients. Is it possible to implement?
     
  2. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    674
    Yes, I have it set up so only the player and relevant network objects are spawned on each client.

    You'll have to handle the spawning of each player yourself, some info here: https://docs-multiplayer.unity3d.com/docs/develop/basics/networkobject/#player-objects

    This is a provisional class I wrote for handling connections which shows one way of doing it:
    Code (CSharp):
    1.     public ConnectionManager()
    2.     {
    3.         NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnectedCallback;
    4.     }
    5.  
    6.     public void StartHost()
    7.     {
    8.         NetworkManager.Singleton.ConnectionApprovalCallback += ConnectionApprovalCallback;
    9.  
    10.         NetworkManager.Singleton.NetworkConfig.ConnectionData = Encoding.ASCII.GetBytes(PlayerPrefs.GetString("name"));
    11.         NetworkManager.Singleton.StartHost();
    12.     }
    13.  
    14.     public void StartClient()
    15.     {
    16.         NetworkManager.Singleton.NetworkConfig.ConnectionData = Encoding.ASCII.GetBytes(PlayerPrefs.GetString("name"));
    17.         NetworkManager.Singleton.StartClient();
    18.     }
    19.  
    20.     private void ConnectionApprovalCallback(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate callback)
    21.     {
    22.         Debug.Log("ConnectionApprovalCallback clientId: " + clientId + " name: " + Encoding.ASCII.GetString(connectionData));
    23.  
    24.         GameSelectionManager.Instance().AddPlayer(clientId, Encoding.ASCII.GetString(connectionData));
    25.  
    26.         callback(false, null, true, null, null);
    27.     }
    28.  
    29.     private void OnClientConnectedCallback(ulong clientId)
    30.     {
    31.         if (NetworkManager.Singleton.IsServer)
    32.         {
    33.             Debug.Log("OnClientConnectedCallback clientId: " + clientId);
    34.  
    35.             PlayerDetail playerDetail = GameSelectionManager.Instance().FindPlayerDetail(clientId);
    36.  
    37.             Debug.Log("OnClientConnectedCallback playerDetail: " + clientId);
    38.         }
    39.     }
    40.  
    41.     public void OnDestroy()
    42.     {
    43.         NetworkManager.Singleton.OnClientConnectedCallback -= OnClientConnectedCallback;
    44.     }
    45. }
    The important part to note is
    Code (csharp):
    1. callback(false, null, true, null, null);
    the first bool being false means the player object won't be automatically spawned.

    There is another option to prevent the player object spawning by default which I haven't used, where you leave the Player Prefab field in the NetworkManager blank in the editor, but add the player prefab to the prefab list and tick Override.

    Either way, after you instantiate the player object add this line before spawning the player:
    Code (csharp):
    1. playerObject.GetComponent<NetworkObject>().CheckObjectVisibility = (clientId) => { return false; };
    after that call SpawnAsPlayerObject rather than Spawn.

    Keep in mind though that object visibility seems a bit hit and miss at the moment so all player objects may spawn on all clients anyway, though it's working for me currently.

    I forgot the part where you'll need to Show the player object to the particular client:
    Code (CSharp):
    1.  playerObject.GetComponent<NetworkObject>().NetworkShow(clientId);
    You might want to make a check first in case the object is already visible (or the host):
    Code (CSharp):
    1. playerObject.GetComponent<NetworkObject>().IsNetworkVisibleTo(clientId);
     
    Last edited: Jan 28, 2022
    Balphagore likes this.
  3. Balphagore

    Balphagore

    Joined:
    Jul 18, 2019
    Posts:
    84
    Hello, I tried to implement what you described, but there is one problem. Each subsequent client sees all the previous ones.

    Code (CSharp):
    1.     private void ApprovalCheck(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate callback)
    2.     {
    3.         string payload = System.Text.Encoding.UTF8.GetString(connectionData);
    4.         var connectionPayload = JsonUtility.FromJson<ConnectionPayload>(payload);
    5.         Debug.Log(connectionPayload.playerName + " connected");
    6.         callback(false, null, true, Vector3.zero, Quaternion.identity);
    7.         GameObject playerObject = Instantiate(playerPrefab);
    8.         NetworkObject networkObject = playerObject.GetComponent<NetworkObject>();
    9.         networkObject.CheckObjectVisibility = (clientId) => { return false; };
    10.         networkObject.SpawnAsPlayerObject(clientId);
    11.         networkObject.NetworkShow(clientId);
    12.     }
     
  4. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    674
    You're right, I'm not entirely sure what's going on there and I'm seeing other strange behaviour, maybe the player object is a special case. Unfortunately the project I have it working in is a bit broken at the moment and I need to fix that so won't get a chance to dig into this.

    I did find another option though while doing some quick testing. You should be able to work out a way to immediately hide players if they do insist on spawning on clients by making use of the class below, or just extract the code you need.
    Code (CSharp):
    1. public static class EntityUtils
    2. {
    3.     public static void ShowEntity<T>(T networkEntity, ulong clientId) where T : NetworkBehaviour
    4.     {
    5.         if (!networkEntity.NetworkObject.IsNetworkVisibleTo(clientId))
    6.         {
    7.             networkEntity.NetworkObject.NetworkShow(clientId);
    8.         }
    9.         else
    10.         {
    11.             Debug.Log("ShowEntity already visible: " + networkEntity.name + " NetId: " + networkEntity.NetworkObjectId);
    12.         }
    13.     }
    14.  
    15.     public static void HideEntityToAllClients<T>(T networkEntity) where T : NetworkBehaviour
    16.     {
    17.  
    18.         foreach(ulong clientId in NetworkManager.Singleton.ConnectedClientsIds)
    19.         {
    20.             if (clientId != NetworkManager.Singleton.ServerClientId)
    21.             {
    22.                 if (networkEntity.NetworkObject.IsNetworkVisibleTo(clientId))
    23.                 {
    24.                     networkEntity.NetworkObject.NetworkHide(clientId);
    25.  
    26.                     Debug.Log("HideEntityToAllClients: " + networkEntity.name + " NetId: "
    27.                             + networkEntity.NetworkObjectId + " hidden to Client: " + clientId);
    28.                 }
    29.             }
    30.         }
    31.     }
    32.  
    33.     public static void HideEntity<T>(T networkEntity, ulong clientId) where T : NetworkBehaviour
    34.     {
    35.         if(networkEntity.NetworkObject.IsNetworkVisibleTo(clientId))
    36.         {
    37.             networkEntity.NetworkObject.NetworkHide(clientId);          
    38.         }
    39.         else
    40.         {
    41.             Debug.Log("HideEntity already hidden: " + networkEntity.name + " NetId: " + networkEntity.NetworkObjectId);
    42.         }
    43.     }
    44. }
    As a test I waited for all clients to join, then on the server got a list of all players and called HideEntityToAllClients on each one. This removed all player objects from all clients (including their own player). With that knowledge it shouldn't take too much work to remove all players from clients except their own player.