Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only.

    Please, do not make any changes to your username or email addresses at id.unity.com during this transition time.

    It's still possible to reply to existing private message conversations during the migration, but any new replies you post will be missing after the main migration is complete. We'll do our best to migrate these messages in a follow-up step.

    On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live.


    Read our full announcement for more information and let us know if you have any questions.

Request the server to spawn an object, then return a reference to that object to the client?

Discussion in 'Multiplayer' started by LordLulz, Jan 31, 2023.

  1. LordLulz

    LordLulz

    Joined:
    Mar 12, 2020
    Posts:
    8
    Hey everyone, sorry if this is a simple question but I've tried googling everything and cannot find an answer. Using Netcode for GameObjects, how do you request the host/server to spawn some items, then return a reference those items the client that requested them?

    Some more info: I am learning Netcode and am trying to make a simple scene where players can build tileable building pieces. This includes a "ghost" tile that shows where a player is about to build. At network spawn, I (think) I need to request the server to spawn this ghost tiles in, then return them to the client. The client then controls their position using a ClientNetworkTransform and their visibility using an animator. I am not sure if I am approaching this correctly, so any tips would really help me out.
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    7,263
    Right after spawning the new object, the server can take the spawned NetworkObjectId and send it back to the client via ClientRPC. The client can then look up the NetworkSpawnManager's SpawnedObjects(List) in order to find the local instance of that network object.
     
    DownstairsB likes this.
  3. LordLulz

    LordLulz

    Joined:
    Mar 12, 2020
    Posts:
    8
    Ayy I appreciate the response! I thought so, but in practice I am running in to an issue where the host is throwing exceptions when I try to then use those ghost references from the client.

    Here is my code if that helps. This is where it is initialized:


    Code (CSharp):
    1.  [ServerRpc]
    2.     private void IntializeServerRpc(ulong id)
    3.     {
    4.         print("intialize server rpc");
    5.  
    6.         ClientRpcParams clientRpcParams = new ClientRpcParams
    7.         {
    8.             Send = new ClientRpcSendParams
    9.             {
    10.                 TargetClientIds = new ulong[] { id }
    11.             }
    12.         };
    13.  
    14.         List<ulong> spawned = new();
    15.  
    16.         foreach (Transform item in m_GhostPrefabs)
    17.         {
    18.             Transform newObj = Instantiate(item);
    19.  
    20.             NetworkObject newNetworkObj = newObj.GetComponent<NetworkObject>();
    21.          
    22.             newNetworkObj.SpawnWithOwnership(id);
    23.             spawned.Add(newNetworkObj.NetworkObjectId);
    24.         }
    25.  
    26.         UpdateClientInitializedClientRpc(spawned.ToArray(), clientRpcParams);
    27.     }
    28.  
    29.     [ClientRpc]
    30.     private void UpdateClientInitializedClientRpc(ulong[] ghostTiles, ClientRpcParams clientRpcParams)
    31.     {
    32.         m_Ghosts.Clear();
    33.  
    34.         foreach (ulong item in ghostTiles)
    35.         {
    36.             NetworkObject foundObj = NetworkManager.Singleton.SpawnManager.SpawnedObjects[item];
    37.             m_Ghosts.Add(foundObj.transform);
    38.             Animator foundAnimator = foundObj.GetComponent<Animator>();
    39.             m_GhostAnimatorsDictionary.Add(foundObj.transform, foundAnimator);
    40.  
    41.             print("RETURNED TO CLIENT: " + foundObj.gameObject.name);
    42.         }
    43.     }

    and this is where I try to use that to spawn in objects:

    Code (CSharp):
    1. [ServerRpc]
    2.     private void SpawnWallServerRpc(ulong id, PosAndRotData posAndRotData)
    3.     {
    4.         if (m_Ghosts.Count == 0)
    5.         {
    6.             Debug.LogWarning("GHOSTS NOT INTIALIZED. ", gameObject);
    7.         }
    8.  
    9.         string[] ghostExpaned = m_Ghosts[m_CurrentGhostInt].name.Split('_');
    10.         Transform foundTransform = null;
    11.         foreach (Transform item in m_FinishedPrefabs)
    12.         {
    13.             string[] foundExpanded = item.name.Split('_');
    14.  
    15.             if (foundExpanded[0].Equals(ghostExpaned[0]) && foundExpanded[1].Equals(ghostExpaned[1]))
    16.             {
    17.                 foundTransform = item;
    18.             }
    19.         }
    20.  
    21.         Transform newWall = Instantiate(foundTransform);
    22.         newWall.GetComponent<NetworkObject>().SpawnWithOwnership(id, true);
    23.  
    24.         Vector3 newPos = new(posAndRotData._xPos, posAndRotData._yPos, posAndRotData._zPos);
    25.         Quaternion newRot = new(posAndRotData._xRot, posAndRotData._yRot, posAndRotData._zRot, posAndRotData._wRot);
    26.  
    27.         //newWall.SetPositionAndRotation(newPos, newRot);
    28.         NetworkTransform foundNetworkTransform = newWall.GetComponent<NetworkTransform>();
    29.  
    30.         foundNetworkTransform.SetState(newPos, newRot);
    31.     }
    The issue I am having is the host can spawn in things fine, but when the client tries the same thing, the host hits the Debug.LogWarning at the start of SpwnWallServerRpc, saying it has nothing in the list of ghost objects. Checking in editor shows that on the host side, the host's list is serialized correct, but the client has nothing serialized in the m_Ghosts list, and when I check the Client side, it's the opposite. it shows nothing in the host's serialized m_Ghosts list but the client's list is indeed serialized correctly. I must be missing something that is causing the desync, but i can't figure out what.

    I hope that makes sense, thanks again for the help!

    EDIT: Also, these methods are all in the same script, which extends NetworkBehaviour.
     
  4. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    7,263
    Check if the UpdateClientInitializedClientRpc runs in both situation, ie host spawning and client spawning. I think it will not run for the host if the client spawns because you instruct the ClientRpc to only run for the spawning client, thus the host-client won't fill its m_Ghosts list.

    For a similar reason the code will fail for pure server instances since the server will never run ClientRPC methods.

    It's good practice to keep the RPC method body to a minimum. In this case I would move the entire body of UpdateClientInitializedClientRpc to a separate method which is called from IntializeServerRpc if the NetworkManager's IsServer flag is true and always called from the UpdateClientInitializedClientRpc. That way you avoid duplicating a lot of code because both server and client need to run it to fill their respective m_Ghosts lists.
     
    MrBigly likes this.
  5. LordLulz

    LordLulz

    Joined:
    Mar 12, 2020
    Posts:
    8
    Ahhh I thiiiiink things are starting to get clearer now. I will test out everything you mentioned and update the thread after. Thanks a TON for looking over all that, really appreciated.

    So if I understand you the pseudocode for the correct process is:

    • Create a new, non-rpc method method to initialize the
      m_Ghost 
      list, called
      InitializeGhostList

    • Call
      IntializeServerRpc 
      from the
      OnNetworkSpawn()
    • Inside
      InitalizeServerRpc
      , its important to determine whether this is a client or host using
      IsServer
    • If
      IsServer
      , call
      InitailizeGhostList()
      , because this will not execute inside ClientRpc if
      IsServer
      .
    • if
      (!IsServer)
      : Skip calling
      InitailizeGhostList()
      directly, and continue on.
    • end of
      InitializeServerRpc()
      , call the
      UpdateClientInitializedClientRpc()
    • If this
      IsServer
      , code execution stops.
    • if (
      !IsServer
      ), the ClientRpc method calls
      InitializeGhostList 

    Does that sound correct to you?
     
  6. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    7,263
    Sounds good so far! :)
     
  7. LordLulz

    LordLulz

    Joined:
    Mar 12, 2020
    Posts:
    8
    That seemed to do the trick! Really appreciate the help
     
    CodeSmile likes this.
  8. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    7,263
    You‘re welcome! :)