Network Object

A Network Object is a GameObject with a Spatial Network Object component, and represents a single synchronized entity in a Spatial Space.

Network objects can either be embedded into a scene, or can be in Prefabs which can be instantiated at runtime.

Creating a Network Object

Scene Embedded Objects

The most basic way to make a GameObject networked is to add a SpatialNetworkObject component to it in the scene. Scene-embedded network objects should be used for objects that are always present in the scene, like a game manager, doors with synchronized open/closed state. In other words, scene-embedded network objects should be considered "static" objects that never get destroyed. In fact, destroying a scene-embedded network object will log an error, and that destruction will not be replicated to other clients.

  1. Create a new GameObject in the scene.
  2. Add a SpatialNetworkObject component to the GameObject.
  3. Customize its properties as needed (such as whether to sync transform, rigidbody, etc.).
  4. Additionally, you can attach any of your custom NetworkBehaviour components to the GameObject or its children.

The first actor to join the space will be the owner of any scene-embedded network objects.

Network Prefabs

Network Prefabs are unity prefabs that have a SpatialNetworkObject component attached to the root GameObject. Network Prefabs can be spawned at runtime using the ISpaceContentService.SpawnNetworkObject method and can have shorter lifetimes than scene-embedded network objects.

  1. First, create a new prefab in your project, edit it and add a SpatialNetworkObject component to the root GameObject.
  2. Attach any custom NetworkBehaviour components to the GameObject or its children.
  3. When the prefab is saved, it will be automatically registered in the Network Prefab Table where it will be assigned a unique networkPrefabGuid. This ID is used at runtime to identify the prefab when spawning it, so that both the local spawning client and the other remote clients can instantiate the same prefab.
  4. Before we can spawn the prefab, we need to register it with the Space Package Config. This lets Spatial know which of the prefabs in the project are included in the space. To do this, add a new element to the Network Prefabs list in the Space Package Config and drag the prefab into the slot. Space Config Network Prefabs
  5. Now you can use ISpaceContentService.SpawnNetworkObject(networkPrefab, position, rotation) to spawn the prefab at runtime.
    public class ColoredCubeSpawner : MonoBehaviour
    {
    public SpatialNetworkObject coloredCubePrefab;

    private void Update()
    {
    if (Input.GetKeyDown(KeyCode.E))
    {
    Vector3 pos = transform.position + new Vector3(Random.Range(-5, 5), Random.Range(-5, 5), Random.Range(-5, 5));
    Quaternion rot = Random.rotation;
    SpatialBridge.spaceContentService.SpawnNetworkObject(coloredCubePrefab, pos, rot);
    }
    }
    }
    public class ColoredCubeSpawner : MonoBehaviour
    {
    public SpatialNetworkObject coloredCubePrefab;

    private void Update()
    {
    if (Input.GetKeyDown(KeyCode.E))
    {
    Vector3 pos = transform.position + new Vector3(Random.Range(-5, 5), Random.Range(-5, 5), Random.Range(-5, 5));
    Quaternion rot = Random.rotation;
    SpatialBridge.spaceContentService.SpawnNetworkObject(coloredCubePrefab, pos, rot);
    }
    }
    }

When Network Prefabs (prefabs with a SpatialNetworkObject on the root GameObject) are embedded in a scene, they will be treated as scene-embedded network objects.

Object Properties

Network Objects manage the synchronization of ownership, lifecycle, transform state, physics and custom state defined in NetworkBehaviour components.

Network Object Properties

Object Flags

These flags control the ownership and lifecycle of the Network Object.

FlagDescription
MasterClientObjectIndicates that this object is always owned and controlled by the master client. Ownership is fixed and cannot be transferred. If the current master client leaves the server instance, ownership will be transferred to the newly elected master client.
DestroyWhenOwnerLeavesIndicates that this object should be destroyed when the owner of the object leaves the server instance. Can only be used with Network Prefabs.
DestroyWhenCreatorLeavesIndicates that this object should be destroyed when the creator of the object leaves the server instance. Can only be used with Network Prefabs.
AllowOwnershipTransferIndicates that ownership of this object can be transferred to another client.

Sync Transform

You can specify which transform properties to synchronize with the syncFlags property. By default transform properties are not synchronized.

It is recommended to only synchronize the properties that are necessary for your use case. So for example, if you have a "GameManager" network object that simply needs to synchronize custom game state, you don't need to synchronize the transform properties of that GameObject. Typically, you would only synchronize the transform properties of GameObjects that move around in the scene at runtime.

Sync Transform Properties

All transform properties are synchronized in local space to its parent GameObject (transform.localPosition, transform.localRotation and transform.localScale). This means that if a NetworkObject is reparented you may see desynchronization between clients.

Note

The transform parent of an object is not synchronized. Changing a NetworkObject's parent will only affect the local client. However, this can be implemented manually by synchronizing a NetworkObject.objectID property that is the parent and then finding the object by ID to parent it to.

Sync Rigidbody

If you want to synchronize physics properties like velocity, angular velocity, etc., you need to add a Rigidbody component to the GameObject that has the SpatialNetworkObject component, then enable the Rigidbody flag under the syncFlags property.

Sync Rigidbody

Only 3D rigidbodies are currently supported for synchronization.

Syncing physics is covered in more detail in the Network Physics section.

Spawning

You can spawn Network Objects by embedding them into a scene, or on-demand at runtime using the ISpaceContentService.SpawnNetworkObject method.

When a Network Object is spawned with ISpaceContentService.SpawnNetworkObject, an instance is created using GameObject.Instantiate on all clients. Immediately after that, it is "bound" to its network state (SpaceObject).

Before the object is bound it is not ready to be used in the networking context. Therefor, it is important not to have any network-setup logic in the MonoBehaviour.Awake method, since that will be called before binding.

Note

Spawning Network Objects with GameObject.Instantiate will simply create a local instance of the object and not replicate it to other clients.

Initialization Order & Lifecycle

Either the network object already exists in the scene, or is instantiated with GameObject.Instantiate.

  1. After the instance is created, Awake is called on any components on the GameObject.
  2. Spatial binds the object to its network state by assigning the SpatialNetworkObject.spaceObject property.
  3. SpatialNetworkBehaviour.Spawned() is called on all behaviours associated with that Network Object. The order in which these calls happen will match the order in which the behaviours are added to the GameObject and its children. First any behaviours on the root GameObject, then any behaviours on its children starting from the top of the hierarchy.

Nesting

Network objects can be nested within other network objects. This is useful for creating hierarchies of objects that need to be synchronized together.

When a parent network object is spawned, all of its children are also spawned. When a parent network object is destroyed, all of its children are also destroyed. Destroying nested network objects at runtime is not supported and will not replicate to other clients.

Nested objects can have different owners, but their lifecycle is tied to the parent object.

Parenting

A Network Object's transform parent is not synchronized by default. Changing a NetworkObject's parent will only affect the local client.

You can however manually implement transform parent replication by syncing a NetworkObject.objectID property and parent the object by finding the object by ID:

public class ParentedObjectBehavior : SpatialNetworkBehaviour, IVariablesChanged
{
private NetworkVariable<int> _parentObjectID = new();

public override void Spawned()
{
// For illustrative purposes, we will set the parent to the first object we can find
if (hasControl)
{
SpatialNetworkObject firstObject = SpatialBridge.spaceContentService.networkObjects.Values.FirstOrDefault();
_parentObjectID.value = (firstObject != null) ? firstObject.objectID : 0;
}
}

public void OnVariablesChanged(NetworkObjectVariablesChangedEventArgs args)
{
// Check if parent object ID has changed
if (args.changedVariables.ContainsKey(_parentObjectID.id))
{
if (SpatialBridge.spaceContentService.TryFindNetworkObject(_parentObjectID.value, out SpatialNetworkObject parentObject))
{
transform.SetParent(parentObject.transform, worldPositionStays: false);
}
else
{
Debug.LogError($"Parent object with ID {_parentObjectID.value} not found");
transform.SetParent(null, worldPositionStays: false);
}
}
}
}
public class ParentedObjectBehavior : SpatialNetworkBehaviour, IVariablesChanged
{
private NetworkVariable<int> _parentObjectID = new();

public override void Spawned()
{
// For illustrative purposes, we will set the parent to the first object we can find
if (hasControl)
{
SpatialNetworkObject firstObject = SpatialBridge.spaceContentService.networkObjects.Values.FirstOrDefault();
_parentObjectID.value = (firstObject != null) ? firstObject.objectID : 0;
}
}

public void OnVariablesChanged(NetworkObjectVariablesChangedEventArgs args)
{
// Check if parent object ID has changed
if (args.changedVariables.ContainsKey(_parentObjectID.id))
{
if (SpatialBridge.spaceContentService.TryFindNetworkObject(_parentObjectID.value, out SpatialNetworkObject parentObject))
{
transform.SetParent(parentObject.transform, worldPositionStays: false);
}
else
{
Debug.LogError($"Parent object with ID {_parentObjectID.value} not found");
transform.SetParent(null, worldPositionStays: false);
}
}
}
}

Finding Network Objects

Each spawned Network Object has a unique objectID which is assigned at runtime when it is spawned. You can use this ID to find a Network Object using the ISpaceContentService.TryFindNetworkObject method.

int objectID = 123;
if (SpatialBridge.spaceContentService.TryFindNetworkObject(objectID, out SpatialNetworkObject networkObject))
{
Debug.Log($"Found Network Object with ID {objectID}; Match? {networkObject.objectID == objectID}");
}
int objectID = 123;
if (SpatialBridge.spaceContentService.TryFindNetworkObject(objectID, out SpatialNetworkObject networkObject))
{
Debug.Log($"Found Network Object with ID {objectID}; Match? {networkObject.objectID == objectID}");
}

Object Identification

We use SpatialNetworkObject.sceneObjectGuid and SpatialNetworkObject.networkPrefabGuid to identify Network Objects across clients. These are baked in at editor time and these IDs are preserved between package versions to maintain network-compatibility.

These serve a different purpose than objectID:

  • SpatialNetworkObject.**Guid are used to identify source assets (prefabs or scene-embedded objects) across clients.
  • SpatialNetworkObject.objectID is used to identify instances of those assets.

Identification in Scenes

When a Network Object is embedded in a scene, either as a prefab, or as a basic GameObject, it will be assigned a sceneObjectGuid that is unique to that object within the scene.

Network Prefab Table

When prefabs are saved that have a SpatialNetworkObject component attached to the root GameObject, they are automatically registered in the Network Prefab Table. This asset is located at Assets/Spatial/NetworkPrefabTable.asset and contains a list of all the prefabs that have been registered in the project.

Generally, you never need to interact with this asset directly, but it important to understand that this table is used to preserve network-compatibility between published package versions.

If you use version control for your project, make sure to commit this asset to your repository. If version control merge-conflicts arise, generally speaking you want to keep both changes, as they represent different prefabs.

Network Prefab Table

Misc

Adding or Removing Behaviours at Runtime

Adding custom NetworkBehaviour components at runtime is not supported. All network behaviours must be known at build time (in editor). This is because the network state of a Network Object is defined by the set of behaviours that are attached to it.

Similarly, removing network behaviour components at runtime is not supported. Destroying a Network Behaviour component will destroy it locally, but will not replicate that destruction to other clients.

Destroying Scene-Embedded Network Objects

Destroying scene-embedded network objects at runtime is not supported and will not replicate to other clients.

Scene-embedded network objects should be considered "static" objects that never get destroyed. If you need to destroy a scene-embedded network object, you should consider spawning it dynamically with a network prefab instead.

Destroying Nested Network Objects

When you want to destroy a network object, you should destroy the root GameObject that has the SpatialNetworkObject component attached. This will destroy the entire hierarchy of nested network objects.

Destroying nested network objects at runtime will result in errors, and the destruction will not replicate to other clients.