Skip to main content

NetworkBehaviour

note

Both the NetworkObject and NetworkBehaviour components require the use of specialized structures to be serialized and used with RPCs and NetworkVariables:

For NetworkObjects use the NetworkObjectReference.

For NetworkBehaviours use the NetworkBehaviourReference.

NetworkBehaviour

NetworkBehaviours can use NetworkVariables and RPCs to synchronize state and send messages over the network. to replicate any netcode aware properties or send/receive RPCs a GameObject must have a NetworkObject component and at least one NetworkBehaviour component. A NetworkBehaviour requires a NetworkObject component on the same relative GameObject or on a parent of the GameObject with the NetworkBehaviour component assigned to it. If you add a NetworkBehaviour to a GameObject that does not have a NetworkObject (or any parent), then Netcode for GameObjects will automatically add a NetworkObject component to the GameObject in which the NetworkBehaviour was added.

NetworkBehaviour is an abstract class that derives from MonoBehaviour and is primarily used to create unique netcode/game logic.

NetworkBehaviours can contain RPC methods and NetworkVariables. When you call an RPC function, the function isn't called locally. Instead a message is sent containing your parameters, the networkId of the NetworkObject associated with the same GameObject (or child) that the NetworkBehaviour is assigned to, and the "index" of the NetworkObject relative NetworkBehaviour (that is, a NetworkObject can have several NetworkBehaviours, the index communicates "which one").

note

It is important that the NetworkBehaviours on each NetworkObject remains the same for the server and any client connected. When using multiple projects, this becomes especially important so the server doesn't try to call a client RPC on a NetworkBehaviour that might not exist on a specific client type (or set a NetworkVariable, etc).

Pre-Spawn and MonoBehaviour Updates

Since NetworkBehaviours derive from MonoBehaviour, the FixedUpdate, Update, and LateUpdate methods, if defined, will still be invoked on NetworkBehaviours even when they're not yet spawned. to "exit early" to avoid executing netcode specific code within the update methods, you can check the local NetworkBehaviour.IsSpawned flag and return if it isn't yet set like the below example:

private void Update()
{
if (!IsSpawned)
{
return;
}
// Netcode specific logic below here
}

Spawning

OnNetworkSpawn is invoked on each NetworkBehaviour associatd with a NetworkObject spawned. This is where all netcode related initialization should occur. You can still use Awake and Start to do things like finding components and assigning them to local properties, but if NetworkBehaviour.IsSpawned is false don't expect netcode distinguishing properties (like IsClient, IsServer, IsHost, etc) to be accurate while within the those two methods (Awake and Start). For reference purposes, below is a table of when NetworkBehaviour.OnNetworkSpawn is invoked relative to the NetworkObject type:

Dynamically SpawnedIn-Scene Placed
AwakeAwake
OnNetworkSpawnStart
StartOnNetworkSpawn

Dynamically Spawned NetworkObjects

For dynamically spawned NetworkObjects (instantiating a network Prefab during runtime) the OnNetworkSpawn method is invoked before the Start method is invoked. So, it's important to be aware of this because finding and assigning components to a local property within the Start method exclusively will result in that property not being set in a NetworkBehaviour component's OnNetworkSpawn method when the NetworkObject is dynamically spawned. To circumvent this issue, you can have a common method that initializes the components and is invoked both during the Start method and the OnNetworkSpawned method like the code example below:

public class MyNetworkBehaviour : NetworkBehaviour
{
private MeshRenderer m_MeshRenderer;
private void Start()
{
Initialize();
}

private void Initialize()
{
if (m_MeshRenderer == null)
{
m_MeshRenderer = FindObjectOfType<MeshRenderer>();
}
}

public override void OnNetworkSpawn()
{
Initialize();
// Do things with m_MeshRenderer

base.OnNetworkSpawn();
}
}

In-Scene Placed NetworkObjects

For in-scene placed NetworkObjects, the OnNetworkSpawn method is invoked after the Start method since the SceneManager scene loading process controls when the NetworkObjects are instantiated. The previous code example shows how one can design a NetworkBehaviour that assures both in-scene placed and dynamically spawned NetworkObjects will have assigned the required properties before attempting to access them. Of course, you can always make the decision to have in-scene placed NetworkObjects contain unique components to that of dynamically spawned NetworkObjects. It all depends upon what usage pattern works best for your project.

De-Spawning

OnNetworkDespawn is invoked on each NetworkBehaviour associated with a NetworkObject when it's de-spawned. This is where all netcode "despawn cleanup code" should occur, but isn't to be confused with destroying. Despawning occurs before anything is destroyed.

Destroying

Each 'NetworkBehaviour' has a virtual 'OnDestroy' method that can be overridden to handle clean up that needs to occur when you know the NetworkBehaviour is being destroyed.

info

If you override the virtual 'OnDestroy' method it's important to alway invoke the base like such:

        public override void OnDestroy()
{
// Clean up your NetworkBehaviour

// Always invoked the base
base.OnDestroy();
}

NetworkBehaviour handles other destroy clean up tasks and requires that you invoke the base OnDestroy method to operate properly.

NetworkBehaviour Pre-Spawn Synchronization

There can be scenarios where you need to include additional configuration data or use a NetworkBehaviour to configure some non-netcode related component (or the like) before a NetworkObject being spawned. This can be particularly critical if you want specific settings applied before NetworkBehaviour.OnNetworkSpawn being invoked. When a client is synchronizing with an existing network session, this can become problematic as messaging requires a client to be fully synchronized before you know "it is safe" to send the message and even if you send a message there is the latency involved in the whole process that might not be convenient and can require additional specialized code to account for this.

NetworkBehaviour.OnSynchronize allows you to write and read custom serialized data during the NetworkObject serialization process.

There are two cases where NetworkObject synchronization occurs:

  • When dynamically spawning a NetworkObject.
  • When a client is being synchronized after connection approval
    • that is, Full synchronization of the NetworkObjects and scenes.
info

If you haven't already become familiar with the INetworkSerializable interface, then you might read up on that before proceeding as NetworkBehaviour.OnSynchronize as it follows a similar usage pattern.

Order of Operations When Dynamically Spawning:

The following provides you with an outline of the order of operations that occur during NetworkObject serialization when dynamically spawned.

Server-Side:

  • GameObject with NetworkObject component is instantiated.
  • The NetworkObject is spawned.
    • For each associated NetworkBehaviour component, NetworkBehaviour.OnNetworkSpawn is invoked.
  • The CreateObjectMessage is generated
    • NetworkObject state is serialized.
    • NetworkVariable state is serialized.
    • NetworkBehaviour.OnSynchronize is invoked for each NetworkBehaviour component.
      • If this method isn't overridden then nothing is written to the serialization buffer.
  • The CreateObjectMessage is sent to all clients that are observers of the NetworkObject.

Client-Side:

  • The CreateObjectMessage is received
    • GameObject with NetworkObject component is instantiated.
    • NetworkVariable state is deserialized and applied.
    • NetworkBehaviour.OnSynchronize is invoked for each NetworkBehaviour component.
      • If this method isn't overridden then nothing is read from the serialization buffer.
  • The NetworkObject is spawned
    • For each associated NetworkBehaviour component, NetworkBehaviour.OnNetworkSpawn is invoked.

Order of Operations During Full (late-join) Client Synchronization:

Server-Side:

  • The SceneEventMessage of type SceneEventType.Synchronize is created
    • All spawned NetworkObjects that are visible to the client, already instantiated, and spawned are serialized.
      • NetworkObject state is serialized.
      • NetworkVariable state is serialized.
      • NetworkBehaviour.OnSynchronize is invoked for each NetworkBehaviour component.
        • If this method isn't overridden then nothing is written to the serialization buffer.
  • The SceneEventMessage is sent to the client.

Client-Side:

  • The SceneEventMessage of type SceneEventType.Synchronize is received
  • Scene information is deserialized and scenes are loaded (if not already)
    • In-scene placed NetworkObjects are instantiated when a scene is loaded.
  • All NetworkObject oriented synchronization information is deserialized
    • Dynamically spawned NetworkObjects are instantiated and state is synchronized
    • For each NetworkObject instance:
      • NetworkVariable state is deserialized and applied.
      • NetworkBehaviour.OnSynchronize is invoked.
        • If this method isn't overridden then nothing is read from the serialization buffer.
      • The NetworkObject is spawned
        • For each associated NetworkBehaviour component, NetworkBehaviour.OnNetworkSpawn is invoked.

OnSynchronize Example

Now that you understand the general concept behind NetworkBehaviour.OnSynchronize, the question you might have is "when would you use such a thing"? NetworkVariables can be useful to synchronize state, but they also are only updated every network tick and you might have some form of state that needs to be updated when it happens and not several frames later so you decide to use RPCs. However, this becomes an issue when you want to synchronize late joining clients as there is no way to synchronize late joining clients based on RPC activity over the duration of a network session. This is one of many possible reasons one might want to use NetworkBehaviour.OnSynchronize. In the example below, it provides one simple use-case scenario where you can use NetworkBehaviour.OnSynchronize to synchronize late-joining clients with state set by ClientRpc events:

using UnityEngine;
using Unity.Netcode;

/// <summary>
/// Simple RPC driven state that shows one
/// form of NetworkBehaviour.OnSynchronize usage
/// </summary>
public class SimpleRpcState : NetworkBehaviour
{
private bool m_ToggleState;

/// <summary>
/// Late joining clients will be synchronized
/// to the most current m_ToggleState
/// </summary>
protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
{
serializer.SerializeValue(ref m_ToggleState);
base.OnSynchronize(ref serializer);
}

public void ToggleState(bool stateIsSet)
{
m_ToggleState = stateIsSet;
}

/// <summary>
/// Synchronizes connected clients with the
/// server-side m_ToggleState
/// </summary>
/// <param name="stateIsSet"></param>
[ClientRpc]
private void ToggleStateClientRpc(bool stateIsSet)
{
m_ToggleState = stateIsSet;
}
}
caution

Since NetworkBehaviour.OnSynchronize is primarily used for server to client synchronization, RPC state synchronization only works when using ClientRpcs since NetworkBehaviour.OnSynchronize is only invoked on the server side during the write part of serialization and only invoked on the client side during the read part of serialization. When running a host NetworkBehaviour.OnSynchronize is still only invoked once (server-side) during the write part of serialization.

Debugging OnSynchronize Serialization

If your serialization code has a bug and throws an exception, then NetworkBehaviour.OnSynchronize has additional safety checking to handle a graceful recovery without completely breaking the rest of the synchronization serialization pipeline.

When Writing:

If user-code throws an exception during NetworkBehaviour.OnSynchronize, it catches the exception and if:

  • LogLevel = Normal: A warning message that includes the name of the NetworkBehaviour that threw an exception while writing will be logged and that part of the serialization for the given NetworkBehaviour is skipped.
  • LogLevel = Developer: It provides the same warning message as well as it logs an error with the exception message and stack trace.

After generating the log message(s), it rewinds the serialization stream to the point just before it invoked NetworkBehaviour.OnSynchronize and will continue serializing. Any data written before the exception occurred will be overwritten or dropped depending upon whether there are more NetworkBehaviour components to be serialized.

When Reading:

For exceptions this follows the exact same message logging pattern described above when writing. The distinct difference is that after it logs one or more messages to the console, it skips over only the serialization data written by the server-side when NetworkBehaviour.OnSynchronize was invoked and continues the deserialization process for any remaining NetworkBehaviour components.

However, there is an additional check to assure that the total expected bytes to read were actually read from the buffer. If the total number of bytes read does not equal the expected number of bytes to be read it will log a warning that includes the name of the NetworkBehaviour in question, the total bytes read, the expected bytes to be read, and lets you know this NetworkBehaviour is being skipped.

caution

When using NetworkBehaviour.OnSynchronize you should be aware that you are increasing the synchronization payload size per instance. If you have 30 instances that each write 100 bytes of information you will have increased the total full client synchronization size by 3000 bytes