a. Add entities to snapshot

Let’s create a new entity for our world and add it to our initial snapshot.

What’s a snapshot?

A snapshot represents the state of a world at some point in time. It stores each persistent entity and the values of their components' properties. The initial snapshot is used as the starting point of your world when you deploy, locally or in the cloud.

A persistent entity is any entity with the Persistence component attached to it. An entity must have the Persistence component in order for it to be part of a snapshot.

In order to be able to update the initial snapshot with some spheres, you will need to create a new EntityTemplate for the “Sphere” entity.

Introduction to entity templates

An entity template specifies what components a SpatialOS entity contains, the initial values of these components and which layers get write access to each component. You must provide an entity template when creating new SpatialOS entities.

For example, the EntityTemplates class contains a CreatePlayerEntityTemplate method used by the Player Lifecycle module to construct the initial component set and values of a Player entity. In this method, 3 well-known SpatialOS components are added: Position, Metadata and Interest. Additionally, the components used by the Player Lifecycle module are added.

template.AddComponent(new Position.Snapshot(coords), clientAttribute);

An entity’s Position component defines the canonical location of an entity within a SpatialOS world. This component is used by the SpatialOS Runtime’s load balancing to determine which worker gets write-access to its components, and to handle relative query-based interest (QBI) constraints such as RelativeCylinder or RelativeSphere.

Click here to read more about QBI.

Later on we’ll be adding a script to let clients move in the world, so we give the client write-access to this component.

template.AddComponent(new Metadata.Snapshot("Player"), serverAttribute);

As covered by the previous chapter, the Metadata component is required by the GameObject Creation module to identify which Prefab to spawn for a given entity type.

PlayerLifecycleHelper.AddPlayerLifecycleComponents(template, workerId, serverAttribute);

This adds the PlayerHeartbeatClient and PlayerHeartbeatServer components which are used by the Feature Module to manage player heartbeats. In addition, an OwningWorker component is added which contains the worker ID of the client that the Player entity is being created for.

const int serverRadius = 500;
var clientRadius = workerId.Contains(MobileClientWorkerConnector.WorkerType) ? 100 : 500;

var serverQuery = InterestQuery.Query(Constraint.RelativeCylinder(serverRadius));
var clientQuery = InterestQuery.Query(Constraint.RelativeCylinder(clientRadius));

var interest = InterestTemplate.Create()
    .AddQueries<Metadata.Component>(serverQuery)
    .AddQueries<Position.Component>(clientQuery);
template.AddComponent(interest.ToSnapshot(), serverAttribute);

The Interest component is used by the Runtime to determine what entities and components a worker should be given read access to per client. It works by mapping a component to a set of queries that should run when a worker is given authority over that specific component on the given entity.

The Metadata component defines the serverAttribute for write-access, and the Position component defines the clientAttribute for write access. We therefore map Metadata to a serverQuery and Position to a clientQuery. The serverQuery asks for any entity within a radius of 500 around the Player entity. The clientQuery also has a radius of 500, with a drop to 100 if on a mobile client.

Add sphere entity template

In the EntityTemplates class, create a method which takes a Vector3 position as an argument and returns an EntityTemplate.

public static EntityTemplate CreateSphereTemplate(Vector3 position = default)
{
    // We will write code here.
}

Create a new EntityTemplate and add the Position and Metadata components to it. You will need to use the Coordinates.FromUnityVector conversion method to populate the Position component. Since we’re creating a sphere, the Metadata value should be set to “Sphere”. Make sure the Persistence component is also added, to allow the entity to be added to the snapshot.

For simplicity, make the sphere server-authoritative by giving write-access to the UnityGameLogic worker.

var serverAttribute = UnityGameLogicConnector.WorkerType;

var template = new EntityTemplate();
template.AddComponent(new Position.Snapshot(Coordinates.FromUnityVector(position)), serverAttribute);
template.AddComponent(new Metadata.Snapshot("Sphere"), serverAttribute);
template.AddComponent(new Persistence.Snapshot(), serverAttribute);

Each sphere should know about other spheres nearby, whether its owning worker is authoritative over its neighbours or not. As all components have server write-access, we only need to add one query to be triggered for the server.

Note: We use the Position component below but any of the other components with server write-access would be acceptable.

const int serverRadius = 500;

var query = InterestQuery.Query(Constraint.RelativeCylinder(serverRadius));
var interest = InterestTemplate.Create().AddQueries<Position.Component>(query);
template.AddComponent(interest.ToSnapshot());

After adding all the components with their write access, we finally set the read access of the entity. This restricts what types of workers the entity can be visible to. For this tutorial, we would like all worker types to be able to see spheres.

template.SetReadAccess(UnityClientConnector.WorkerType, MobileClientWorkerConnector.WorkerType, serverAttribute);

The complete method should look like this:

public static EntityTemplate CreateSphereTemplate(Vector3 position = default)
{
    var serverAttribute = UnityGameLogicConnector.WorkerType;

    var template = new EntityTemplate();
    template.AddComponent(new Position.Snapshot(Coordinates.FromUnityVector(position)), serverAttribute);
    template.AddComponent(new Metadata.Snapshot("Sphere"), serverAttribute);
    template.AddComponent(new Persistence.Snapshot(), serverAttribute);

    const int serverRadius = 500;

    var query = InterestQuery.Query(Constraint.RelativeCylinder(serverRadius));
    var interest = InterestTemplate.Create().AddQueries<Position.Component>(query);
    template.AddComponent(interest.ToSnapshot());

    template.SetReadAccess(UnityClientConnector.WorkerType, MobileClientWorkerConnector.WorkerType, serverAttribute);

    return template;
}

Update snapshot generation script

Now that there is a method to create a Sphere entity template with a given position, the SnapshotGenerator script should be updated to make use of it,

First, create an AddSphere method which takes in the position a sphere should have and the snapshot it should be added to. Then create an AddSpheres method which uses the AddSphere method to place 3 spheres in the world.

private static void AddSpheres(Snapshot snapshot)
{
    AddSphere(snapshot, new Vector3(-10f, 0.5f, 10f));
    AddSphere(snapshot, new Vector3(10f, 5f, 10f));
    AddSphere(snapshot, new Vector3(25f, 0.5f, 0f));
}

private static void AddSphere(Snapshot snapshot, Vector3 position)
{
    var template = EntityTemplates.CreateSphereTemplate(position);
    snapshot.AddEntity(template);
}

Ensure that you call AddSpheres from the CreateSnapshot method, providing a Snapshot to be written to as an argument.

private static Snapshot CreateSnapshot()
{
    var snapshot = new Snapshot();

    AddPlayerSpawner(snapshot);

    AddSpheres(snapshot);

    return snapshot;
}

Finally, regenerate the snapshot by selecting SpatialOS > Generate snapshot.

Updated about a year ago



a. Add entities to snapshot


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.