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’re making changes to the Unity Runtime Fee pricing policy that we announced on September 12th. Access our latest thread for more information!
    Dismiss Notice
  3. Dismiss Notice

Resolved Best practices for dynamic initialization of network objects

Discussion in 'Netcode for GameObjects' started by moose0847, Jan 27, 2023.

  1. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    29
    In my current code, I'm doing some initial setting of network values before spawning a prefab.

    Code (CSharp):
    1. GamePlayerController controller = Instantiate(_playerControllerPrefab);
    2.  
    3. if (controller && controller.NetworkObject)
    4. {
    5.     controller.AssignToPlayer(joinedPlayer.NetworkObjectId);
    6.     controller.NetworkObject.SpawnWithOwnership(joinedPlayer.OwnerClientId);
    7. }
    In this case, I'm telling the controller object to assign itself to the given player, which is either an AI owned by the server or connected client player. However this is producing the following warning:

    "NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. Are you modifying a NetworkVariable before the NetworkObject is spawned?"

    After a bit of searching, it seems like the recommendation from unity is that we don't write to NetworkVariables before the object has been spawned (see https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues/2159).

    So with all that in mind, what is considered the best practice for doing dynamic network initialization like this? Is there a way to dynamically set the initial state of a network variable without needing to send an additional command like an RPC or having some spawn queueing system?
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,563
    set the NetworkVariable values of the spawned object in its OnNetworkSpawn method to whatever initial values desired

    you can use an instance of a struct or scriptableobject as a temporary value holder if you really really have to assign values right after calling Instantiate and before spawning (but I would question that kind of design … it feels flawed on first thought)
     
  3. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    29
    Thanks for the info! This is what I'm thinking at the moment, but the issue then becomes I need to implement some kind of initialization data caching to apply later.

    For example if I spawn a controllable object for a client, I spawn that object with the remote client as the owner. However, only the server knows the initialization data, so I need to send a second init command after the spawn to get that data across. This feels like it'll be problematic as I'll need to have 2 round trips to create a new object. I'm currently doing all my spawning on the server (maybe this isn't necessary?), but for objects owned by remote clients initialization is pretty cumbersome.

    The other option I'm toying with is doing all my spawning on the server in an initialization step. Then implement some kind of pooling once all the network data has been synced, but that also has issues where I potentially have a bunch of unnecessary data going across the wire.
     
  4. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,563
    No there aren‘t necessarily two round trips. Have you tried the following on the server?

    • Instantiate prefab
    • Spawn object with ownership
    • call an Init method on the spawned object, passing in the init values
    • Init() on object => assigns init values to the corresponding NetworkVariables
    This should in theory synchronize the spawn and the network variables for the client all at once. Give it a try, maybe it works.

    I think this should work if you call Init after spawning but it will fail with the error you mentioned if you call it right after Instantiate.
     
  5. moose0847

    moose0847

    Joined:
    Dec 26, 2019
    Posts:
    29
    Thanks for the tip!

    For anyone curious, here is the solution I ended up with. This function is responsible for spawning a controller object (i.e. character, spectator, etc.) to a player in the game. This logic assumes clients own their controller objects, while the server owns all AI.

    GameMode class
    Code (CSharp):
    1. GamePlayerController controller = Instantiate(_playerControllerPrefab);
    2. if (controller && controller.NetworkObject)
    3. {
    4.     if (joinedPlayer.IsOwnedByServer)
    5.     {
    6.         controller.NetworkObject.Spawn();
    7.     }
    8.     else
    9.     {
    10.         controller.NetworkObject.SpawnWithOwnership(joinedPlayer.NetworkObjectId);
    11.     }
    12.  
    13.     controller.AssignToPlayer(joinedPlayer.NetworkObjectId);
    14. }
    From here, I have logic in the OnGainedOwnership and value changed events of the network variable set in AssignToPlayer to register the controller with the player object.

    GamePlayer class
    Code (CSharp):
    1. public override void OnGainedOwnership()
    2. {
    3.     base.OnGainedOwnership();
    4.  
    5.     if (!IsServer)
    6.     {
    7.         GamePlayer player = GamePlayerManager.GetClientPlayer<GamePlayer>(OwnerClientId);
    8.         player.SetController(this);
    9.     }
    10. }
    11.  
    12. private void OnAssignedToPlayer(ulong previousValue, ulong newValue)
    13. {
    14.     name = string.Format("{0}: {1}", GetType().Name, _assignedPlayerObjectId.Value);
    15.  
    16.     if (GamePlayerManager.GetPlayer(previousValue, out GamePlayer previousPlayer))
    17.     {
    18.         previousPlayer.SetController(null);
    19.     }
    20.  
    21.     if (GamePlayerManager.GetPlayer(newValue, out GamePlayer newPlayer))
    22.     {
    23.         newPlayer.SetController(this);
    24.     }
    25. }
    While I haven't tested extensively yet, it seems to be working. Thanks again for the help!
     
    CodeSmile likes this.