Unity-Technologies / com.unity.netcode.gameobjects

Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.
MIT License
2.12k stars 430 forks source link

How to disable "NetworkVariable is written to, but doesn't know its NetworkBehaviour yet" LogWarning? #2561

Open andreyshade opened 1 year ago

andreyshade commented 1 year ago

I'm using NetworkVariables for configuring some NetworkObjects before spawning and a lot of LogWarnings:

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

in my game server logs.

I tried to set log level in the NetworkManager component to error level but it didn't help me and warnings still appear in the logs. image

NoelStephensUnity commented 1 year ago

@andreyshade Setting the value directly before the associated NetworkObject is spawned will typically yield this error message. It is recommended to set the value when it is instantiated or when NetworkBehaviour.OnNetworkSpawn is invoked.

As an example:

    public class MyNetworkBehaviour : NetworkBehaviour
    {
        [HideInInspector]
        public NetworkVariable<int> SomeNetworkVariable;

        [HideInInspector]
        public NetworkVariable<int> SomeOtherNetworkVariable = new NetworkVariable<int>();

        public int SomeValue;
        public int SomeOtherValue;

        private void Awake()
        {
            SomeNetworkVariable = new NetworkVariable<int>(SomeValue);
        }

        public override void OnNetworkSpawn()
        {
            if (IsServer)
            {
                SomeOtherNetworkVariable.Value = SomeOtherValue;
            }
            base.OnNetworkSpawn();
        }
    }

The first approach (SomeNetworkVariable), the value is set when it is instantiated on all instances (clients or server). The second approach (SomeOtherNetworkVarialbe), the property is instantiated when it is declared (defaults to server write permissions) but is set when the server spawns the associated NetworkObject. Since the server is authority over spawning, it will always spawn (locally) first on the server-host instance and then the spawn message is generated and sent to the clients. Because of this, having the server set the value during OnNetworkSpawn still guarantees that any value set will be replicated when spawned on the clients.

Let me know if this helps you with the issue you are experiencing?

CodeSmile-0000011110110111 commented 6 months ago

Glad this can be worked around but it adds unnecessary overhead to a component and complicates the Spawn/Init process by having to have more code. I would expect setting initial NetworkVariables from outside the component to be a common use-case. I frequently see user questions asking how to initialize NetworkVariable with a value, respectively wondering why the initial value is 0.

I would expect to be able to do the following without raising a warning, in line with my feature suggestions to add a pre-spawn callback to InstantiateAndSpawn:

Server:

To write the above without causing the warning, component T would need to have an additional temporary value property that holds the value so it can be assigned to the NetworkVariable in the component's OnNetworkSpawn by the server - which compared to the code above adds unnecessary complexity.

Overall working around this warning complicates the code, adds more chance for creating initialization bugs, and adds memory overhead to affected components due to temporary value fields.

Alternatively the OnNetworkSpawn could look up the initial value elsewhere, but this may add undesirable coupling and may simply shift the complexity elsewhere.

NoelStephensUnity commented 6 months ago

This has been something we are looking into fixing. Using the following example script:

public class InitializeNetworkVariable : NetworkBehaviour
{
    private NetworkVariable<int> m_NetworkVariableInitA = new NetworkVariable<int>(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
    public NetworkVariable<int> NetworkVariableInitB;

    public void SetValue(int value)
    {
        m_NetworkVariableInitA.Initialize(this);
        m_NetworkVariableInitA.Value = value;
    }

    public override void OnNetworkSpawn()
    {
        Debug.Log($"NetVarA: {m_NetworkVariableInitA.Value}");
        Debug.Log($"NetVarB: {NetworkVariableInitB.Value}");
        base.OnNetworkSpawn();
    }
}

Then your code to spawn would look like this:

        if (Input.GetKeyDown(KeyCode.S))
        {
            var instance = Instantiate(Prefab);
            var networkVarInit = instance.GetComponent<InitializeNetworkVariable>();
            networkVarInit.SetValue(100);
            networkVarInit.NetworkVariableInitB = new NetworkVariable<int>(200, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);

            instance.GetComponent<NetworkObject>().SpawnWithOwnership(someClientId);
        }

Where there are two options currently to apply a value prior to spawning:

Taking your expected behavior usage pattern, it would look like this with the above approach:

I think the approach you might be looking for would be more like:

CodeSmile-0000011110110111 commented 6 months ago

Thanks for the detailed explanation! Works perfectly!
I tried both ways and prefer the Init method which makes it clearer that this is init-only behaviour and the property setter remains trivial (no branching).

Nikmazza commented 4 months ago

This has been something we are looking into fixing. Using the following example script:

public class InitializeNetworkVariable : NetworkBehaviour
{
    private NetworkVariable<int> m_NetworkVariableInitA = new NetworkVariable<int>(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
    public NetworkVariable<int> NetworkVariableInitB;

    public void SetValue(int value)
    {
        m_NetworkVariableInitA.Initialize(this);
        m_NetworkVariableInitA.Value = value;
    }

    public override void OnNetworkSpawn()
    {
        Debug.Log($"NetVarA: {m_NetworkVariableInitA.Value}");
        Debug.Log($"NetVarB: {NetworkVariableInitB.Value}");
        base.OnNetworkSpawn();
    }
}

Then your code to spawn would look like this:

        if (Input.GetKeyDown(KeyCode.S))
        {
            var instance = Instantiate(Prefab);
            var networkVarInit = instance.GetComponent<InitializeNetworkVariable>();
            networkVarInit.SetValue(100);
            networkVarInit.NetworkVariableInitB = new NetworkVariable<int>(200, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);

            instance.GetComponent<NetworkObject>().SpawnWithOwnership(someClientId);
        }

Where there are two options currently to apply a value prior to spawning:

  • Setting it in a method after initializing with the associated NetworkBehaviour component instance
  • Constructing the NetworkVariable prior to spawning and passing in the default value.

Taking your expected behavior usage pattern, it would look like this with the above approach:

  • var obj = Instantiate(prefab)
  • var t = obj.GetComponent
  • Either option to set variable value prior to spawning:

    • Setter method
    • Construct NetworkVariable
  • SpawnWithOwnership(obj, owner)

I think the approach you might be looking for would be more like:

  • var obj = Instantiate(prefab)
  • var t = obj.GetComponent
  • t.SomeNetworkVariable.Initialize(t); <---- pre-initialize with the NetworkBehaviour component
  • t.SomeNetworkVariable.Value = 123; <---- then set the value directly
  • SpawnWithOwnership(obj, owner)

Hi Noel! how are you? I tried this approach and whilst the log does dissapear, syncronization between server and client does not occur. I fear it might be due to our pipeline working against us. We are trying to allow our modders to add network behaviours in their mods so they can add complex mechanics into the game

Our mod has a folder which contains a network object prefab
when mod is loaded, we load the prefab into the network manager network prefab list using
NetworkManager.Singleton.PrefabHandler.AddNetworkPrefab(prefab);
when client presses button 1, we send an rpc to server
server then instantiates and spawns the network object loaded from the mod
it then tries to update a network variable from the spawned network object 

i read somewhere that networkbehaviours cannot be added during runtime, and while we are not specifically adding NB during runtime, maybe importing them and their network objects/prefabs from a mod is a similar case?