How to guarantee the initial NetworkVariable Value on a Client during OnNetworkSpawn()?

The documentation is rather confusing on NetworkVariable as it doesn’t really explain how to actually initialize them properly. What is the correct pattern to guarantee that a client will spawn the object and read the correct state of a NetworkVariable during OnNetworkSpawn()?

  • Using Awake() throws warnings, so the docs are probably just outdated there.
  • Using OnNetworkSpawn() throws warnings, so that doesn’t seem right.
  • Setting before running Spawn() throws warnings…
  • Setting after running Spawn() throws warnings…
  • Setting them in OnSyncronize() doesn’t even set them, since it’s used for something else entirely.

Erm… When are we supposed to set the initial values of NetworkVariables… ?

The only way I see that doesn’t throw or fail is to just create the object on the network and sit on your hands until everyone is ready, then set the value and wait for it to replicate. If you need to do some init with that value then you need to use OnChanged callbacks to trigger something.

If that’s the case - that seems really bad. This would imply that the initial state is basically guaranteed incorrect, which is some nonsense so I’m assuming I just don’t understand it correctly.

1 Like

It was the case if you set network variable values straight after spawning (in the same frame, perhaps longer) then the network object would spawn on the client with those values. For example this would work:

        public Stack CreateStack(Side side, Vector2Int position)
        {
            Stack stack = SpawnService.Instance().SpawnPrefab<Stack>(SpawnEntityType.Game, false);
            stack.IsControlled = false;
            stack.Side = side;
            stack.Units = 0;
            stack.StackState = StackState.Waiting;
            stack.MovePoints = GameConstants.TIME_ONE_DAY;
            stack.Position = position;
            stack.transform.position = new Vector3(position.x + 0.5f, position.y + 0.5f);

            return stack;
        }

I’d have to re-test though to be sure it still works that way, but this is from an older project and it’s now broken in Netcode 1.4.0 due to some change in CheckObjectVisibility I’m trying to get my head around.

If you’re having no luck I can try something similar in a test project.

It definitely does not seem to work on 1.3.1.

        public void ServerSpawnSomeObject(GameObject prefab, int id, int otherValue)
        {
            GameObject go = Instantiate(prefab);
            StateObject behavior = go.GetComponent<StateObject>();
            behavior.InitializeVars(id, otherValue);
            behavior.NetworkObject.Spawn();

// heres the flow that actually happens...
            // ServerSpawnSomeObject()
            //
            // StateObject.Awake()
            // StateObject.InitializeVars()
            // - NetworkVariable warning about not ready
            // StateObject.OnNetworkSpawn()
            // - NetVars are still default values at this point
            //
            // Client eventually gets the object, and values are default of course.
        }

No matter where I try to initialize the values it doesn’t seem to work. There has to be a way to set the value of a NetworkVariable before shipping off the object. It makes no sense to just ship off network objects assuming that the default state of every NetworkVariable is considered correct, but the API does not offer any actual path to do this. Even setting the variables to dirty doesn’t work because they’re initialized and overwritten after you do that.

Hi,

If you want to read the NetworkVariable’s value on OnNetworkSpawn(), then you may modify the NetworkVariable’s value on the server before you invoke NetworkObject.Spawn().

The following warning is inconsequential: NetworkVariable is written to, but doesn’t know its NetworkBehaviour yet. Are you modifying a NetworkVariable before the NetworkObject is spawned?
If you read the NetworkVariable on OnNetworkSpawn() on clients it will be the server’s set value pre-spawn.

However, yes, the recommended pattern is to subscribe to a NetworkVariable’s OnValueChanged callback, and define your desired behaviour when that callback is fired on clients.

I hope that helps!

2 Likes

I am not seeing this behavior. Values set before Spawn() on the server are still default on Clients during OnNetworkSpawn().

Did this change since 1.3.1?

I just tested this in 1.2.0 and 1.4.0 and it worked with both. It’s the only way I could get it to spawn on the client with the correct value. I’ll have to go over my old project code to see why I thought differently.

Pretty much like this:

public class ColorManager : NetworkBehaviour
    {
        NetworkVariable<Color32> m_NetworkedColor = new NetworkVariable<Color32>();
        Material m_Material;
        void Awake()
        {
            m_Material = GetComponent<Renderer>().material;
        }
        public override void OnNetworkSpawn()
        {
            base.OnNetworkSpawn();
            if (IsClient)
            {
                    /* in this case, you need to manually load the initial Color to catch up with the state of the network variable.
                     * This is particularly useful when re-connecting or hot-joining a session
                    */
                    OnClientColorChanged(m_Material.color, m_NetworkedColor.Value);
                    m_NetworkedColor.OnValueChanged += OnClientColorChanged;
           
            }
        }
        public override void OnNetworkDespawn()
        {
            base.OnNetworkDespawn();
            if (IsClient)
            {
                    m_NetworkedColor.OnValueChanged -= OnClientColorChanged;
            }
        }

        void OnClientColorChanged(Color32 previousColor, Color32 newColor)
        {
            m_Material.color = newColor;
        }
}
1 Like

Thanks for the response! This is exactly the pattern we ended up using. We implemented it as needed and bumped up to 1.4. It has been working well so far.

It would be helpful if the documentation could verify what the system guarantees in terms of data state at critical times, such as when OnNetworkSpawn() occurs. Took me a while to try things until I could make safe assumptions.

2 Likes

Just to add to this, and perhaps a note for future improvement, because I’ve been using the SpawnManager.InstantiateAndSpawn helper method which is really convenient … except it doesn’t allow hooking into the process between Instantiate and Spawn.

Update: posted a feature request on GitHub.

I had to change my code from this:

var spawnedObject = spawnManager.InstantiateAndSpawn(playerPrefab, ownerClientId,
position: spawnTransform.position, rotation: Quaternion.Euler(0f, spawnAngle, 0f));

// assume "LocalPlayerIndex" NetworkVariable change attempts here

To this:

var spawnedObject = Instantiate(playerPrefab);
spawnedObject.transform.position = spawnTransform.position;
spawnedObject.transform.rotation = Quaternion.Euler(0f, spawnAngle, 0f);

var indexer = spawnedObject.GetComponent<NetworkLocalPlayerIndex>();
indexer.LocalPlayerIndex = localPlayerIndex; // <== NetworkVariable

spawnedObject.SpawnWithOwnership(ownerClientId);
2 Likes