Network Variables

Network Variables are a way to synchronize state between clients in a persistent way, unlike Remote Events which are messages that are only processed by clients that are currently connected. Late-joining clients will catch up on the current state of Network Variables when they connect to a server instance.

Defining Network Variables

Network Variables are defined within Network Behaviours as fields or properties. These can be either private or public.

public class ExampleBehaviour : SpatialNetworkBehaviour
{
// Changes to this variable will be synchronized across all clients
private NetworkVariable<int> _health = new(initialValue: 100);

// Properties are also supported
public NetworkVariable<float> jetpackFuel { get; } = new();

public override void Spawned()
{
Debug.Log($"Player spawned with health: {_health.value}");
}

private void Update()
{
// Actor who has control (state authority) can modify the variable
if (hasControl)
{
if (Input.GetKeyDown(KeyCode.Space))
{
// Modifying the variable will automatically synchronize the change to other clients
jetpackFuel.value -= 10 * Time.deltaTime;
}
}
}
}
public class ExampleBehaviour : SpatialNetworkBehaviour
{
// Changes to this variable will be synchronized across all clients
private NetworkVariable<int> _health = new(initialValue: 100);

// Properties are also supported
public NetworkVariable<float> jetpackFuel { get; } = new();

public override void Spawned()
{
Debug.Log($"Player spawned with health: {_health.value}");
}

private void Update()
{
// Actor who has control (state authority) can modify the variable
if (hasControl)
{
if (Input.GetKeyDown(KeyCode.Space))
{
// Modifying the variable will automatically synchronize the change to other clients
jetpackFuel.value -= 10 * Time.deltaTime;
}
}
}
}

Variables must be initialized when declared to prevent null reference exceptions.

NetworkVariable<int> health = new();

NetworkVariable<int> jetpackFuel; // will throw null reference exception
NetworkVariable<int> health = new();

NetworkVariable<int> jetpackFuel; // will throw null reference exception

By the time Spawned() is called on the behaviour, all Network Variables will have been initialized and are ready to be used. Do not interact with Network Variables in Awake() or Start() as they may not be initialized yet.

Optionally, initial values can be provided when creating a Network Variable. These initial values will be used when the associated object is spawned.

private NetworkVariable<int> _health = new(initialValue: 100);
private NetworkVariable<int> _health = new(initialValue: 100);

Variable IDs

Each Network Variable is assigned a unique ID within the associated Network Object. IDs are automatically assigned to Network Variables before Spawned() is called if they are not explicitly specified. IDs are of type byte so they have a valid range of 0 to 255.

For example, for a Network Object with a single ExampleBehaviour component, the _health variable will be assigned ID 0 and the jetpackFuel variable will be assigned ID 1. The order of the variables in the class definition determines the ID.

public class PlayerBehaviour : SpatialNetworkBehaviour
{
private NetworkVariable<int> _health = new(); // ID 0
private NetworkVariable<float> _jetpackFuel = new(); // ID 1

// ...
}
public class PlayerBehaviour : SpatialNetworkBehaviour
{
private NetworkVariable<int> _health = new(); // ID 0
private NetworkVariable<float> _jetpackFuel = new(); // ID 1

// ...
}

If the Network Object has multiple Network Behaviours, the IDs are assigned based on the order of the behaviours in the object.

// For a GameObject with a NetworkObject component and two Network Behaviours attached (PlayerBehaviour
// first, then PlayerMovement), the IDs will be assigned as follows:

public class PlayerBehaviour : SpatialNetworkBehaviour
{
private NetworkVariable<int> _health = new(); // ID 0
private NetworkVariable<float> _jetpackFuel = new(); // ID 1

// ...
}

public class PlayerMovement : SpatialNetworkBehaviour
{
private NetworkVariable<float> _speed = new(); // ID 2
private NetworkVariable<bool> _isSliding = new(); // ID 3

// ...
}
// For a GameObject with a NetworkObject component and two Network Behaviours attached (PlayerBehaviour
// first, then PlayerMovement), the IDs will be assigned as follows:

public class PlayerBehaviour : SpatialNetworkBehaviour
{
private NetworkVariable<int> _health = new(); // ID 0
private NetworkVariable<float> _jetpackFuel = new(); // ID 1

// ...
}

public class PlayerMovement : SpatialNetworkBehaviour
{
private NetworkVariable<float> _speed = new(); // ID 2
private NetworkVariable<bool> _isSliding = new(); // ID 3

// ...
}

Maintaining compatibility with older versions

As you may guess, changing the order of variables in a Network Behaviour class, or adding new variables will change which IDs are assigned to the variables. This can cause issues when trying to maintain compatibility with clients that are on previous versions of the package: "health" on Client A which is on version 1 of the package might be "jetpackFuel" on Client B which is on version 2 of the package.

To maintain compatibility, you can manually assign IDs to Network Variables. However, maintaining compatibility between versions can be complex and error-prone. Careful consideration should be given to which Network Behaviours are used together, as variable IDs may conflict if two Network Behaviours have variables with the same ID.

public class PlayerBehaviour : SpatialNetworkBehaviour
{
private NetworkVariable<int> _health = new(id: 0, initialValue: 100);
private NetworkVariable<float> _jetpackFuel = new(id: 1, initialValue: default);

// ...
}
public class PlayerBehaviour : SpatialNetworkBehaviour
{
private NetworkVariable<int> _health = new(id: 0, initialValue: 100);
private NetworkVariable<float> _jetpackFuel = new(id: 1, initialValue: default);

// ...
}

Many things can break multiplayer compatibility:

  • Adding or removing Network Behaviours
  • Changing the order of Network Behaviours
  • Changing the order of Network Variables within a Network Behaviour
  • Changing the type of a Network Variable
  • Changing which Network Objects are included in the scene

This topic is covered in more detail in the Version Compatibility section.

Responding To Changes

To be notified of changes to variables, you can implement the IVariablesChanged interface on your Network Behaviour.

public class ExampleBehaviour : SpatialNetworkBehaviour, IVariablesChanged
{
private NetworkVariable<int> _health = new(initialValue: 100);

public void OnVariablesChanged(NetworkObjectVariablesChangedEventArgs args)
{
if (args.changedVariables.ContainsKey(_health.id))
{
Debug.Log($"Health changed to {_health.value}");
}
}
}
public class ExampleBehaviour : SpatialNetworkBehaviour, IVariablesChanged
{
private NetworkVariable<int> _health = new(initialValue: 100);

public void OnVariablesChanged(NetworkObjectVariablesChangedEventArgs args)
{
if (args.changedVariables.ContainsKey(_health.id))
{
Debug.Log($"Health changed to {_health.value}");
}
}
}

You can use this event callback to refresh UI elements, play sounds, or trigger other game logic in response to changes in Network Variables.

Supported Types

Network Variables support the following types:

  • Primitives: bool, byte, int, float, double, long, string
  • Unity types: Vector2, Vector3, Color32
  • Arrays: int[] (size limited to 256 elements)

Arrays

Array value modifications are synchronized as a whole, and modifying individual elements will not trigger a synchronization event.

Note

Retrieving an array from a Network Variable will return a copy of the array. Modifying the array will not trigger a synchronization event.

public class ExampleBehaviour : SpatialNetworkBehaviour
{
private NetworkVariable<int[]> _inventory = new();

public override void Spawned()
{
_inventory.value = new int[5];
}

private void Update()
{
if (hasControl)
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
// This will not trigger a synchronization event and just modifies the local copy
_inventory.value[0] = 1;

// This will
int[] inventory = _inventory.value; // creates a copy
inventory[0] = 1;
_inventory.value = inventory;
}
}
}
}
public class ExampleBehaviour : SpatialNetworkBehaviour
{
private NetworkVariable<int[]> _inventory = new();

public override void Spawned()
{
_inventory.value = new int[5];
}

private void Update()
{
if (hasControl)
{
if (Input.GetKeyDown(KeyCode.Alpha1))
{
// This will not trigger a synchronization event and just modifies the local copy
_inventory.value[0] = 1;

// This will
int[] inventory = _inventory.value; // creates a copy
inventory[0] = 1;
_inventory.value = inventory;
}
}
}
}

You can use NetworkVariable<int[]> to synchronize arbitrary data as long as you can represent it as an array of integers.

Visual Scripting

Network Variables can also be used with Visual Scripting. In general you should prefer using C# for multiplayer logic, as Visual Scripting incurs a big performance overhead. However, for simple use cases, Visual Scripting can be used to interact with Network Variables.

To add Visual-Scripting variables to a Network Object, use the Add Network Variables button on the Network Object component.

Add Network Variables

This will automatically add a Variables component to the Game Object where you can define the variables. If any of the variables are of the supported types, you can enable synchronization for them individually.

Visual Scripting Variables

To set a network variable from a Visual Scripting scripting graph, use the Set Object Variable node.

Using Visual Scripting Variables together with C# Variables

Visual scripting variables will be assigned IDs at editor time. You can see these IDs in the Network Object inspector. If you manually assign IDs to C# Network Variables, you should ensure that the IDs do not conflict with the Visual Scripting variables.

Visual Scripting Variable IDs

Setting Visual Scripting Variables from C# Code

Generally, this is advised against, as the performance overhead of Visual Scripting is significant. If you need to set Visual Scripting variables from C# code, ask yourself if you can achieve the same result using full C# code for the network object logic.

If you still need to set Visual Scripting variables from C# code, you can get the Unity.VisualScripting.Variables component and update variable values directly. The changes will be detected by Spatial and synchronized if they are marked as synced.

private void SetVisualScriptingVariableExample()
{
// Unity.VisualScripting.Variables: If this type is not found, make sure your C# assembly is
// referencing the Unity.VisualScripting.Core assembly

var variables = GetComponent<Unity.VisualScripting.Variables>();

// Set the value of a Visual Scripting variable
variables.declarations.Set("myVariable", 42);
}
private void SetVisualScriptingVariableExample()
{
// Unity.VisualScripting.Variables: If this type is not found, make sure your C# assembly is
// referencing the Unity.VisualScripting.Core assembly

var variables = GetComponent<Unity.VisualScripting.Variables>();

// Set the value of a Visual Scripting variable
variables.declarations.Set("myVariable", 42);
}