Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Resolved Problem spawning prefabs with NetCode

Discussion in 'Netcode for GameObjects' started by Croes_C, May 1, 2023.

  1. Croes_C

    Croes_C

    Joined:
    Aug 18, 2015
    Posts:
    18
    Hello everyone,
    I've been trying to figure out the netcode but I stumbled upon something odd that I don't understand (yet?).
    I'm still quite a beginner and haven't found a solution in a quick search through the internet.

    First I made a script to spawn a prefab with the space bar and everything worked fine.
    Now I spawn it by clicking on the a certain object and it'll have to spawn it by Cam.ScreenPointToRay.

    The code is almost identical but for some reason the host spawns it correctly for both host and client but client only spawns it for the host but not for itself. o_O

    Sorry if the answer is obvious... I just can't see it.


    First code:
    Code (CSharp):
    1.  
    2. void Update()
    3. {
    4.     if (IsOwner)
    5.     {
    6.             if (Input.GetKeyDown(KeyCode.Space))
    7.             {
    8.                 Vector3 pos = transform.position;
    9.                 var myType = type;
    10.                 Spawn_ServerRpc(pos, myType);
    11.             }
    12.     }
    13. }
    14.  
    15. [ServerRpc]
    16. private void Spawn_ServerRpc(Vector3 pos, int myType)
    17. {
    18.      Spawn_ClientRpc(pos, myType);
    19.  
    20.      SpawnSomething(pos, myType);
    21. }
    22.  
    23. [ClientRpc]
    24. private void Spawn_ClientRpc(Vector3 pos, int myType)
    25. {
    26.      if (!IsOwner) SpawnSomething(pos, myType);
    27. }
    28.  
    29. void SpawnSomething(Vector3 pos, int myType)
    30. {
    31.      Instantiate(prefab[myType], pos, Quaternion.identity);
    32. }
    33.  
    Second code with mouse button that doesn't seem to work:
    Code (CSharp):
    1.  
    2. void Update()
    3. {
    4.     if (IsOwner)
    5.     {
    6.         if (Physics.Raycast(ray, out hit, Mathf.Infinity) && ray.collider.gameObject == theObject)
    7.         {
    8.             pointer.position = ray.point;
    9.             pointer.rotation = Quaternion.LookRotation(ray.normal);
    10.             buildRot = Quaternion.FromToRotation(pointer.up, ray.normal) * pointer.rotation;
    11.  
    12.             if (Input.GetMouseButtonDown(0))
    13.             {
    14.                 Vector3 pos = pointer.position;
    15.                 Quaternion rot = buildRot;
    16.                 Spawn_ServerRpc(pos, rot);
    17.             }
    18.          }
    19.      }
    20. }
    21.  
    22. [ServerRpc]
    23.     private void Spawn_ServerRpc(Vector3 pos, Quaternion rot)
    24.     {
    25.         Spawn_ClientRpc(pos, rot);
    26.  
    27.         SpawnSomething(pos, rot);
    28.     }
    29.  
    30.     [ClientRpc]
    31.     private void Spawn_ClientRpc(Vector3 pos, Quaternion rot)
    32.     {
    33.         if (!IsOwner) SpawnSomething(pos, rot);
    34.     }
    35.  
    36.     void SpawnSomething(Vector3 pos, Quaternion rot)
    37.     {
    38.         Instantiate(prefab, pos, rot);
    39.     }
    40.  
    41.  
     
  2. Croes_C

    Croes_C

    Joined:
    Aug 18, 2015
    Posts:
    18
    So I kinda fixed it.
    I placed the "SpawnSomething(pos, rot);" in the same part where the Mousebutton(0) is checked.
    and removed it from the ServerRPC function.
    Also, is this solution okay? It sounds bad from hacking/cheating.

    And can someone explain why it was working in my first code but not in de 2nd one? :confused:
     
  3. edin97

    edin97

    Joined:
    Aug 9, 2021
    Posts:
    58
    When spawning something in the network, it's important to know 1 thing, is the prefab you are spawning a NetworkObject (has the component attached) ?

    1) Yes, then you should ONLY "Instantiate" it on the server, (via if (IsServer){..} or calling a ServerRpc), THEN you call "Spawn()" (or SpawnWithOwnership()) and this will handle spawning it to ALL your clients.

    2) No, then you CANNOT use the "Spawn()" property. which means, only the person that runs the "Instantiate(prefab[myType], pos, Quaternion.identity);" line of code will instantiate the prefab on HIS side, which can be 1 reason why you only see the result on your server and not on your clients.

    There is also another issue that can cause not spawning : the script that you showed, is it placed on an object that has a NetworkObject component ? if it doesn't have one, then calling the Rpcs won't do anything (I think) which might be your issue here. Looking at your code, it should work if it is placed on a Player with a NetworkObject, but idk.

    Another issue can be,(which seems to not be your case but I'll write it anyways) the object which holds this script is not spawned with OwnerShip, in that case, you PROBABLY used Instantiate().Spawn() to spawn it. doing so, the owner of THIS script is the server only, so doing if (IsOwner) : only allows the server to read the lines past that. So all clients reading this script won't ever read oast if (IsOwner).


    Anyways ! here is MY correct way of spawning stuff : I rewrote your code here FOR case 1) :

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Netcode;
    5.  
    6. public class SpawnOnClick : NetworkBehaviour
    7. {
    8.     [SerializeField] private GameObject prefab;
    9.     private void Update()
    10.     {
    11.         if (IsOwner)
    12.         {
    13.             if (Physics.Raycast(ray, out hit, Mathf.Infinity) && ray.collider.gameObject == theObject)
    14.             {
    15.                 pointer.position = ray.point;
    16.                 pointer.rotation = Quaternion.LookRotation(ray.normal);
    17.                 buildRot = Quaternion.FromToRotation(pointer.up, ray.normal) * pointer.rotation;
    18.  
    19.                 if (Input.GetMouseButtonDown(0))
    20.                 {
    21.                     Vector3 pos = pointer.position;
    22.                     Quaternion rot = buildRot;
    23.                     //Spawn_ServerRpc(pos, rot);
    24.                     if (IsServer) { Spawn_ByServer(pos, rot); }
    25.                     else { Spawn_ServerRpc(pos, rot); }
    26.                 }
    27.             }
    28.         }
    29.     }
    30.  
    31.     [ServerRpc]
    32.     private void Spawn_ServerRpc(Vector3 pos, Quaternion rot)
    33.     {
    34.         //Spawn_ClientRpc(pos, rot);
    35.  
    36.         Spawn_ByServer(pos, rot);
    37.     }
    38.  
    39.     //[ClientRpc]
    40.     //private void Spawn_ClientRpc(Vector3 pos, Quaternion rot)
    41.     //{
    42.     //    if (!IsOwner) SpawnSomething_Net(pos, rot);
    43.     //}
    44.  
    45.     void Spawn_ByServer(Vector3 pos, Quaternion rot) // ONLY server will execute this method
    46.     {
    47.         Instantiate(prefab, pos, rot).GetComponent<NetworkObject>().Spawn(); // Server first instantiate prefab on his side, THEN spawns it on all clients.
    48.  
    49.     }
    50.  
    51. }
    52.  
    SO, this works only for case 1). if the prefab IS a NetworkObject. (if you need OwnerShip for the spawned object, check SpawnWithOwnership(...) in the doc)

    I also wrote you how I handle the case 2). For your use case I would do something like this, :

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using Unity.Netcode;
    5.  
    6. public class SpawnOnClick : NetworkBehaviour
    7. {
    8.     [SerializeField] private GameObject prefab;
    9.  
    10.     private void Update()
    11.     {
    12.         if (IsOwner)
    13.         {
    14.             if (Physics.Raycast(ray, out hit, Mathf.Infinity) && ray.collider.gameObject == theObject)
    15.             {
    16.                 pointer.position = ray.point;
    17.                 pointer.rotation = Quaternion.LookRotation(ray.normal);
    18.                 buildRot = Quaternion.FromToRotation(pointer.up, ray.normal) * pointer.rotation;
    19.  
    20.                 if (Input.GetMouseButtonDown(0))
    21.                 {
    22.                     Vector3 pos = pointer.position;
    23.                     Quaternion rot = buildRot;
    24.                     //Spawn_ServerRpc(pos, rot);
    25.                     if (NetworkManager.Singleton == null) { return; } // if issue with server, we can't spawn and return.
    26.                     ulong localClientId = NetworkManager.Singleton.LocalClientId;
    27.                     if (IsServer) { Spawn_ByServer(pos, rot, localClientId); }
    28.                     else { Spawn_ByClient(pos, rot, localClientId); }
    29.                 }
    30.             }
    31.         }
    32.     }
    33.  
    34.     [ServerRpc]
    35.     private void Spawn_ServerRpc(Vector3 pos, Quaternion rot, ulong senderClientId)
    36.     {
    37.         Spawn_ByServer(pos, rot);
    38.     }
    39.  
    40.     [ClientRpc]
    41.     private void Spawn_ClientRpc(Vector3 pos, Quaternion rot, ClientRpcParams clientRpcParams = default)
    42.     {
    43.         // Spawn on ALL clients that are included in the clientRpcParams, here it should be EVERY client WITHOUT the server or the client that Clicked to spawn the prefab.
    44.         Spawn_Net(pos, rot);
    45.  
    46.     }
    47.  
    48.     void Spawn_ByClient(Vector3 pos, Quaternion rot, ulong senderClientId) // ONLY Client will execute this method
    49.     {
    50.         // Spawn on client side
    51.         Spawn_Net(pos, rot);
    52.         // Call server Rpc to spawn the prefab on the server AND all the other clients.
    53.         Spawn_ServerRpc(pos, rot, senderClientId);
    54.     }
    55.  
    56.     void Spawn_ByServer(Vector3 pos, Quaternion rot, ulong senderClientId) // ONLY server will execute this method
    57.     {
    58.         // Spawn on server side
    59.         Spawn_Net(pos, rot);
    60.         // Here we have to get the list of client without the server, and without the client that just asked to spawn the prefab, because they both spawned it on their side already.
    61.         // 0 is the server ID, since we call Spawn_Net above, we don't want to send a clientRpc to the server again, senderClientId is the client that wants to Spawn the prefab on click
    62.         // So we both add them to the list of clients we don't want to spawn (again) the prefab on.
    63.         List<ulong> list_PlayerToSendTo = GetListClientsIdToSendTo(new List<ulong> { 0, senderClientId });
    64.         ClientRpcParams clientRpcParams = new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = list_PlayerToSendTo.ToArray() } };
    65.         // Spawn prefab on ALL the other clients, which is NOT the server NOR the client senderClientId
    66.         Spawn_ClientRpc(pos, rot, clientRpcParams);
    67.     }
    68.  
    69.     void Spawn_Net(Vector3 pos, Quaternion rot) // NET, all clients and server will run/execute this method !
    70.     {
    71.         Instantiate(prefab, pos, rot); // the object instantiated will NOT be synced via network. BUT all clients should instantiate this object on their side.
    72.  
    73.     }
    74.  
    75.  
    76.  
    77.     // Get list of ALL clients ids to SEND TO, l_ClientsIdToNOTInclude is a list of ID you DON'T want to send the RPC to.
    78.     public List<ulong> GetListClientsIdToSendTo(List<ulong> l_ClientsIdToNOTInclude)
    79.     {
    80.         List<ulong> l_PlayerToSendTo = new List<ulong>();
    81.         foreach (var connectedClientId in NetworkManager.Singleton.ConnectedClientsIds)
    82.         {
    83.             if (!l_ClientsIdToNOTInclude.Contains(connectedClientId))
    84.             {
    85.                 if (!l_PlayerToSendTo.Contains(connectedClientId))
    86.                 {
    87.                     l_PlayerToSendTo.Add(connectedClientId);
    88.                 }
    89.             }
    90.         }
    91.         return l_PlayerToSendTo;
    92.  
    93.     }
    94. }
    95.  
    96.  
    This will be overwhelming, but it's (at least for me) the best way to spawn stuff for both use case, and I always reuse these lines of code for any stuff I spawn.

    The reason why you would want to use 1) it's for EVERYTHING that needs to be synchronized, NetworkVariables, Transform (movement generally), players, etc...

    The reason why you would want to use 2) is For example : you want to spawn some particles ? but you don't want to use a networkObject because synchronization is not important for a simple Particle effect, this is the best case. Might also be better for bandwidth etc, but that idk.

    (Hopefully , I didn't make a mistake, I copy/pasted my code so it should work, good luck !)
     
    saishruthi_unity likes this.
  4. Croes_C

    Croes_C

    Joined:
    Aug 18, 2015
    Posts:
    18
    Thanks for the response!
    The prefab had no network component and players were spawned without calling a Spawn() function. Just the standard StartHost() and StartClient() functions.

    But that first method you showed looks pretty useful!
    I actually want to sync as less as possible. It's a strategy game I'm trying to make so only spawning prefabs is what you see the other player doing. : )
     
  5. edin97

    edin97

    Joined:
    Aug 9, 2021
    Posts:
    58
    Oh I see ! Be careful with "sync as less as possible" it back fired on me once ! had to rewrite 30% of my project ! Now, I am syncing almost everything and it works well for me !

    Also, I re read your code and I didn't see it first, but you were using if (!IsOwner) SpawnSomething(pos, rot); line 26. "if (!IsOwner)" was your problem !

    When the client clicks to spawn : you call a ServerRpc. The server then calls a ClientRpc (Spawn_ClientRpc) to all connected clients. At this point, the client who clicked, and received this ClientRpc IS the owner ! because before calling the ServerRpc you are checking "if (IsOwner)" line 4. (which is normal here). It's doing something like this :

    Owner Client calls ServerRpc -> Server sends ClientRpc to all connected clients -> Owner Client receives ClientRpc back. -> Owner client reads line : if (!IsOwner){...} -> but this client IS the owner, so it never reads the {spawnSomething } line ! since you are asking only NOT-owners to read the brackets

    So if you test your original code with 3 clients, the 1st client who clicks, should never spawn the prefab but the server and the 2nd client will spawn it ! because they will receive the ClientRpc as well, but they are not the owner of the object that contains this script ! So they will read "if (!IsOwner){...}" line !

    The easiest fix for you should be replacing in your orignal code
    Code (CSharp):
    1. [ClientRpc]
    2. private void Spawn_ClientRpc(Vector3 pos, int myType)
    3. {
    4.      if (!IsOwner) SpawnSomething(pos, myType);
    5. }
    by

    Code (CSharp):
    1.      
    2. [ClientRpc]
    3. private void Spawn_ClientRpc(Vector3 pos, int myType)
    4. {
    5. if (IsServer) { return; } // the client which is also the server, already spawns the prefab before calling the ClientRpc ! So server doesn't need to spawn it again, we return.
    6.         SpawnSomething(pos, rot); // ALL other clients should spawn this since they have not spawned it yet.
    7. }

    ALSO, if you plan on using any of the methods I showed, I really suggest putting the spawning script on an Object WITH a NetworkObject component ! So the Object with the spawning script should be synced ! (Or in general : the script with Rpcs should be synced !)

    Again, the 1st method (which is the recommended version) NEEDS both the original Object (the one with the spawning script) AND the Prefab you are spawning to be synced ( = have the NetworkObject component on both and spawned using Spawn() or SpawnWithOwnership() AND both added to the NetworkManager List of NetworkObjects !)

    The 2nd method(and the 3rd little one I just showed above) allows for less syncing, because the PREFAB spawned doesn't need to be synced ! but it still requires the original object (the one who has this spawning script) to be synced !

    The first 2 codes you showed should work on a synced object with the little change I suggested just above !

    Hope it helps, I am still learning too, but I remember at first, all the Rpcs, IsOwner, IsServer, etc. properties are confusing, it takes time but you'll get it I believe !
     
    Last edited: May 1, 2023
  6. Croes_C

    Croes_C

    Joined:
    Aug 18, 2015
    Posts:
    18
    Thanks again! : D

    I tried a bunch of things but they all give me different issues. setting the SpawnSomething() in an "if (!IsServer)" works well!
    But I couldn't get the prefab with NetworkObject to work correctly with your previous examples using NetworkObject.Spawn()...
    Anyway, it works for now. The game will be 1vs1 turn-based so I'll probably be adding some syncing checks in between while I'm still learning. : )
    Will see how far I get.
    So far it works with this basic code :D
     
  7. fernando-a-cortez

    fernando-a-cortez

    Unity Technologies

    Joined:
    Dec 14, 2020
    Posts:
    9
    Hi,

    I'd highly recommend that you read through the basics of NetworkObject spawning detailed here: https://docs-multiplayer.unity3d.com/netcode/current/basics/object-spawning.

    You shouldn't require Rpcs to synchronize spawns over the network. You will want to instantiate your NetworkObject and invoke NetworkObject.Spawn() only on your server. You will first want to make sure that your NetworkObject is registered under your NetworkManager's list of NetworkPrefabs.

    If that is all true, all connected clients will have that spawn synchronized automatically by the server. I hope that helps!
     
    RikuTheFuffs likes this.
  8. Croes_C

    Croes_C

    Joined:
    Aug 18, 2015
    Posts:
    18
    Thanks for the tip!
    Will give it a try! : )
     
  9. Croes_C

    Croes_C

    Joined:
    Aug 18, 2015
    Posts:
    18
    So I finally came back to my project and tried to spawn it like you said.
    I had to do a lot of fiddling around with different settings and finally got to a working one.
    Only one small bit: I added the prefab to the DefaultNetworkPrefabs and added this to the Network Prefabs List in NetworkManager in the scene. But I'm getting an annoying warning:
    - "[Netcode] Runtime Network Prefabs was not empty at initialization time. Network Prefab registrations made before initialization will be replaced by NetworkPrefabsList."

    Is there a better way to do it, to avoid this warning?
     
  10. fernando-a-cortez

    fernando-a-cortez

    Unity Technologies

    Joined:
    Dec 14, 2020
    Posts:
    9
    Hi,

    Glad to hear spawns are now working on your end.

    Unfortunately, as of v1.4.0, you will encounter this warning when starting a client or host. The warning does not come with any consequence, however.

    I encourage you to create a ticket with the behaviour you're noticing so that the Netcode team can look into this warning.

    You can do so here: https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues. Thanks!