Multiplayer Quickstart

Tutorial: Physics Cube Throwing

This basic tutorial will show you how to spawn colored cubes from the local player and throw them around the scene.

Step 1: Setup and create a cube prefab

  1. Create a new Space Package
  2. Create the cube prefab:
    1. Create a Cube in the scene with GameObject > 3D Object > Cube and add a Rigidbody component to it.
    2. Set the local scale to (0.3, 0.3, 0.3) so it's not too big.
    3. Add a Spatial Network Object component to the GameObject, and enable Position, Rotation and Rigidbody sync.
    4. Save it as a prefab anywhere in your project (drag and drop the GameObject into the Assets folder).
  3. Hit Test Active Scene to test the space in the Spatial Sandbox

Cube Prefab

The cube prefab is embedded in the scene and is already spawned when you load the space. You should see the cube fall to the ground.

Step 2: Spawning cubes from the local player

  1. Now, lets remove the cube from the scene and spawn it dynamically based on input.
  2. Add a C# assembly to the space in the Space Package Config Add Assembly
  3. Create a new script (e.g. CubeSpawner.cs) in that assembly and attach it to a GameObject in the scene. We will use this to spawn cubes based on keyboard input.
    public class CubeSpawner : MonoBehaviour
    {
    public SpatialNetworkObject cubePrefab;

    private void Update()
    {
    if (Input.GetKeyDown(KeyCode.E) && SpatialBridge.networkingService.isConnected)
    {
    StartCoroutine(SpawnCube());
    }
    }

    private IEnumerator SpawnCube()
    {
    Vector3 localActorPosition = SpatialBridge.actorService.localActor.avatar.position;
    Vector3 lookDirection = SpatialBridge.cameraService.forward;

    Vector3 spawnPos = localActorPosition + Vector3.up * 1.6f + lookDirection * 0.5f;
    Quaternion spawnRot = Random.rotation;

    SpawnNetworkObjectRequest request = SpatialBridge.spaceContentService.SpawnNetworkObject(cubePrefab, spawnPos, spawnRot);
    yield return request;

    if (request.succeeded)
    {
    SpatialNetworkObject networkObject = request.networkObject;
    Rigidbody rb = networkObject.GetComponent<Rigidbody>();
    rb.AddForce(lookDirection * 25, ForceMode.Impulse);
    }
    }
    }
    public class CubeSpawner : MonoBehaviour
    {
    public SpatialNetworkObject cubePrefab;

    private void Update()
    {
    if (Input.GetKeyDown(KeyCode.E) && SpatialBridge.networkingService.isConnected)
    {
    StartCoroutine(SpawnCube());
    }
    }

    private IEnumerator SpawnCube()
    {
    Vector3 localActorPosition = SpatialBridge.actorService.localActor.avatar.position;
    Vector3 lookDirection = SpatialBridge.cameraService.forward;

    Vector3 spawnPos = localActorPosition + Vector3.up * 1.6f + lookDirection * 0.5f;
    Quaternion spawnRot = Random.rotation;

    SpawnNetworkObjectRequest request = SpatialBridge.spaceContentService.SpawnNetworkObject(cubePrefab, spawnPos, spawnRot);
    yield return request;

    if (request.succeeded)
    {
    SpatialNetworkObject networkObject = request.networkObject;
    Rigidbody rb = networkObject.GetComponent<Rigidbody>();
    rb.AddForce(lookDirection * 25, ForceMode.Impulse);
    }
    }
    }
  4. Assign the cube we created earlier to the cubePrefab field in the inspector.
  5. We will also need to register this network prefab in our Space Package Config. Add an element to "Network Prefabs" and drag the cube prefab into the field. Register Network Prefab
  6. Hit Test Active Scene to test if the cube spawns when you press E.

Step 3: Add some color variation

Now lets give each cube a random color.

  1. Create a new Network Behaviour script (e.g. ColoredCube.cs) and attach it to the cube prefab.
    public class ColoredCube : SpatialNetworkBehaviour, IVariablesChanged
    {
    private MeshRenderer _meshRenderer;

    private NetworkVariable<Color32> _color = new();

    public override void Spawned()
    {
    _meshRenderer = GetComponent<MeshRenderer>();

    if (hasControl)
    {
    // Initialize random color: we do this here because Random cannot be used in class initialization
    _color.value = Random.ColorHSV(0, 1, 1, 1, 0.5f, 1);
    }
    }

    public void OnVariablesChanged(NetworkObjectVariablesChangedEventArgs args)
    {
    // Update the material color based on the network variable value
    if (args.changedVariables.ContainsKey(_color.id))
    _meshRenderer.material.color = _color.value;
    }
    }
    public class ColoredCube : SpatialNetworkBehaviour, IVariablesChanged
    {
    private MeshRenderer _meshRenderer;

    private NetworkVariable<Color32> _color = new();

    public override void Spawned()
    {
    _meshRenderer = GetComponent<MeshRenderer>();

    if (hasControl)
    {
    // Initialize random color: we do this here because Random cannot be used in class initialization
    _color.value = Random.ColorHSV(0, 1, 1, 1, 0.5f, 1);
    }
    }

    public void OnVariablesChanged(NetworkObjectVariablesChangedEventArgs args)
    {
    // Update the material color based on the network variable value
    if (args.changedVariables.ContainsKey(_color.id))
    _meshRenderer.material.color = _color.value;
    }
    }
  2. Test the space again and you should see cubes of different colors being spawned when you press E.

Step 4: Cleanup

If we keep spawning cubes, the scene will get cluttered and we start to see performance issues. Let's clean up the cubes after a certain time.

  1. First lets enable the DestroyWhenCreatorLeaves object flag on the Network Object component of the cube prefab. This covers the case for cleaning up the object in case of unexpected disconnections. DestroyWhenCreatorLeaves
  2. Add logic to the ColoredCube behaviour to destroy the cube after a few seconds.
    public class ColoredCube : SpatialNetworkBehaviour, IVariablesChanged
    {
    private MeshRenderer _meshRenderer;

    private NetworkVariable<Color32> _color = new();

    public override void Spawned()
    {
    _meshRenderer = GetComponent<MeshRenderer>();

    if (hasControl)
    {
    // Initialize random color: we do this here because Random cannot be used in class initialization
    _color.value = Random.ColorHSV(0, 1, 1, 1, 0.5f, 1);
    StartCoroutine(DestroyAfter(5));
    }
    }

    private IEnumerator DestroyAfter(float seconds)
    {
    yield return new WaitForSeconds(seconds);

    GameObject.Destroy(gameObject);
    }

    public void OnVariablesChanged(NetworkObjectVariablesChangedEventArgs args)
    {
    // Update the material color based on the network variable value
    if (args.changedVariables.ContainsKey(_color.id))
    _meshRenderer.material.color = _color.value;
    }
    }
    public class ColoredCube : SpatialNetworkBehaviour, IVariablesChanged
    {
    private MeshRenderer _meshRenderer;

    private NetworkVariable<Color32> _color = new();

    public override void Spawned()
    {
    _meshRenderer = GetComponent<MeshRenderer>();

    if (hasControl)
    {
    // Initialize random color: we do this here because Random cannot be used in class initialization
    _color.value = Random.ColorHSV(0, 1, 1, 1, 0.5f, 1);
    StartCoroutine(DestroyAfter(5));
    }
    }

    private IEnumerator DestroyAfter(float seconds)
    {
    yield return new WaitForSeconds(seconds);

    GameObject.Destroy(gameObject);
    }

    public void OnVariablesChanged(NetworkObjectVariablesChangedEventArgs args)
    {
    // Update the material color based on the network variable value
    if (args.changedVariables.ContainsKey(_color.id))
    _meshRenderer.material.color = _color.value;
    }
    }
  3. Test the space again and you should see the cubes disappear after a few seconds.

Templates

Pre-built examples to help you get started with multiplayer development.