Space Objects

All synchronized objects in a space are represented as SpaceObjects. This interface is a purely data oriented way to access and sync state across clients.

Overview

Space Objects are an abstraction layer that Network Objects are built on top of. Typically you will stick to using Network Objects, but Space Objects can be useful when you need to interact with built-in space content like Avatars.

A SpaceObject is not a GameObject or a MonoBehaviour, it's just data associated with a replicated object. This data can be used to drive and sync a GameObject across clients, such as the player avatar which always has a corresponding SpaceObject on the server.

If you are familiar with networking you might recognize Space Objects as the model, while GameObjects are the view. The model describes an object as simply as possible, while the view presents it to the user, in our case using Unity GameObject's and renderer's.

Image explaining spaceObjects

Image explaining gameObjects

Space Objects vs Network Objects

To put it simply, Space Objects represent space content that is built-in to Spatial, while Network Objects and Network Behaviours are custom objects that you create.

Space Object Variables

You can assign custom variables to any SpaceObject you own, and these variables are automatically synchronized across all clients.

This is the same thing as Network Variables, and you will notice that all Network Object variables can be accessed through the SpaceObject interface.

Variables have unique IDs, which are used to reference them in an efficient way. The IDs are of type byte and so there can be a maximum of 256 variables per object.

ISpaceObject spaceObject = //..;

// Set some variables
spaceObject.SetVariable(0, 100.0f); // health
spaceObject.SetVariable(1, 0.9f); // power
spaceObject.SetVariable(2, 516); // power

// Retrieve the variable values
if (spaceObject.TryGetVariable(0, out float health))
Debug.Log($"Got Health: {health}");
ISpaceObject spaceObject = //..;

// Set some variables
spaceObject.SetVariable(0, 100.0f); // health
spaceObject.SetVariable(1, 0.9f); // power
spaceObject.SetVariable(2, 516); // power

// Retrieve the variable values
if (spaceObject.TryGetVariable(0, out float health))
Debug.Log($"Got Health: {health}");

As you might have noticed, keeping track of variable IDs can be cumbersome. To make this more manageable, you can define your own enum to represent the variables.

// It is important to manually assign the values to the enum in order to preserve backwards compatibility
// with previous versions of your networking code. If you don't do this, you may add new elements to this
// enum, which will change the values of the existing elements, causing the old values to be misinterpreted.
public enum MySpaceObjectVariables : byte
{
Health = 0,
Power = 1,
Ammo = 2,
}

ISpaceObject spaceObject = //..;

// Register to changes (make sure to unregister when appropriate)
spaceObject.onVariablesChanged += (eventArgs) => {
foreach (IReadOnlyDictionary<byte, object> variable in eventArgs.changedVariables)
Debug.Log($"Variable {(MySpaceObjectVariables)variable.Key} changed to {variable.Value}");
foreach (byte variableID in eventArgs.removedVariables)
Debug.Log($"Variable {(MySpaceObjectVariables)variableID} removed");
};

// Set some variables
spaceObject.SetVariable((byte)MySpaceObjectVariables.Health, 100.0f);
spaceObject.SetVariable((byte)MySpaceObjectVariables.Power, 0.9f);
spaceObject.SetVariable((byte)MySpaceObjectVariables.Ammo, 516);

// Retrieve the variable values
if (spaceObject.TryGetVariable((byte)MySpaceObjectVariables.Health, out float health))
Debug.Log($"Got Health: {health}");
// It is important to manually assign the values to the enum in order to preserve backwards compatibility
// with previous versions of your networking code. If you don't do this, you may add new elements to this
// enum, which will change the values of the existing elements, causing the old values to be misinterpreted.
public enum MySpaceObjectVariables : byte
{
Health = 0,
Power = 1,
Ammo = 2,
}

ISpaceObject spaceObject = //..;

// Register to changes (make sure to unregister when appropriate)
spaceObject.onVariablesChanged += (eventArgs) => {
foreach (IReadOnlyDictionary<byte, object> variable in eventArgs.changedVariables)
Debug.Log($"Variable {(MySpaceObjectVariables)variable.Key} changed to {variable.Value}");
foreach (byte variableID in eventArgs.removedVariables)
Debug.Log($"Variable {(MySpaceObjectVariables)variableID} removed");
};

// Set some variables
spaceObject.SetVariable((byte)MySpaceObjectVariables.Health, 100.0f);
spaceObject.SetVariable((byte)MySpaceObjectVariables.Power, 0.9f);
spaceObject.SetVariable((byte)MySpaceObjectVariables.Ammo, 516);

// Retrieve the variable values
if (spaceObject.TryGetVariable((byte)MySpaceObjectVariables.Health, out float health))
Debug.Log($"Got Health: {health}");

As you can see, this is still a bit cumbersome, which is why we recommend using Network Objects for most use cases.

Keep in mind that the variable IDs are unique to each object, so you can reuse the same ID for different variables on different objects. In other words, a variable with ID: 0 can represent "health" on one object and "ammo" on another.