c. Use transform in a GameObject creator

The default GameObject creator uses the Position component to determine where in the world a gameobject should be instantiated. Now that you have objects moving in the world with the Transform Synchronization module enabled, you should update the GameObject creation process to make use of transform data.

What's the issue?

First of all the Position component is updated less frequently than TransformInternal. Given that you are updating them at 1Hz and 30Hz respectively, Position could be up to a second out of date.

Secondly, the rotation state of entities isn’t taken into consideration when instantiating GameObjects. To see this more clearly add two nearby spheres to the snapshot - a normal sphere and a rotated one.

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));

    var rotation = new Quaternion
    {
        eulerAngles = new Vector3(90, 0, 0)
    };
    AddSphere(snapshot, new Vector3(-4f, 0.5f, 4f), rotation);
    AddSphere(snapshot, new Vector3(-4f, 0.5f, 7f));
}

private static void AddSphere(Snapshot snapshot, Vector3 position)
{
    AddSphere(snapshot, position, Quaternion.identity);
}

// Now takes an optional rotation argument.
private static void AddSphere(Snapshot snapshot, Vector3 position, Quaternion rotation)
{
    var template = EntityTemplates.CreateSphereTemplate(rotation, position);
    snapshot.AddEntity(template);
}

The image below show the expected difference between the default rotation and the one you provided.

Regenerate your snapshot, relaunch your local deployment and play the development scene. Once your player connects you should observe that the two spheres that you just added have the same rotation, even though you know they should appear different.

The default GameObject creator only uses the Position component, so it has no way of knowing what rotation to instantiate GameObjects with. This is why all GameObjects are currently spawned with the default rotation.

Create the GameObjectCreatorFromTransform class

Create an empty GameObjectCreatorFromTransform.cs file under BlankProject > Scripts > Workers and copy the GameObjectCreatorFromMetadata implementation into here. Change the namespace to Scripts.Worker and the class to GameObjectCreatorFromTransform. Finally, add the following two using statements:

using Improbable.Gdk.GameObjectCreation;
using Improbable.Gdk.TransformSynchronization;

All GameObject creators must implement the IEntityGameObjectCreator interface, which contains three methods. You can read more about their purpose here, but at a high level:

  • PopulateEntityTypeExpectations: Called to register the components expected on an entity to create a GameObject for a given entity type.
  • OnEntityCreated: Called when a new SpatialOS Entity is checked out by the worker and a prefab can be resolved for the given entity.
  • OnEntityRemoved: Called when a SpatialOS Entity is removed from the worker's view.

The default creator only uses the Position component to instantiate GameObjects, but you would like the custom creator to use TransformInternal in order to use both position and rotation data. To do this, change the default type registered in PopulateEntityTypeExpectations from Position.Component to TransformInternal.Component.

public void PopulateEntityTypeExpectations(EntityTypeExpectations entityTypeExpectations)
{
    entityTypeExpectations.RegisterDefault(new[]
    {
        typeof(TransformInternal.Component)
    });
}

The OnEntityCreated method is the entry point for GameObject creators to instantiate GameObjects. Currently, the implementation copied across gets the Position component, applies the worker offset, calls Object.Instantiate with the position that was read and links the created GameObject to the SpatialOS entity.

Firstly, change the entityManager.GetComponentData call to use TransformInternal.Component instead. Next, read the location and rotation data from the transform component and convert them to their respective Unity types.

Don’t forget to offset the position by the worker origin!

    var transformInternal = entityManager.GetComponentData<TransformInternal.Component>(entityInfo.Entity);
    var position = transformInternal.Location.ToUnityVector() + workerOrigin;
    var rotation = transformInternal.Rotation.ToUnityQuaternion();

Lastly in the call to Object.Instantiate(), replace Quaternion.identity with the rotation you defined above. When you’re done modifying the method, it should look something like this:

public void OnEntityCreated(SpatialOSEntityInfo entityInfo, GameObject prefab, EntityManager entityManager, EntityGameObjectLinker linker)
{
    var transformInternal = entityManager.GetComponentData<TransformInternal.Component>(entityInfo.Entity);
    var position = transformInternal.Location.ToUnityVector() + workerOrigin;
    var rotation = transformInternal.Rotation.ToUnityQuaternion();

    var gameObject = Object.Instantiate(prefab, position, rotation);
    gameObject.name = $"{prefab.name}(SpatialOS: {entityInfo.SpatialOSEntityId}, Worker: {workerType})";

    entityIdToGameObject.Add(entityInfo.SpatialOSEntityId, gameObject);
    linker.LinkGameObjectToSpatialOSEntity(entityInfo.SpatialOSEntityId, gameObject, componentsToAdd);
}

Finally, the OnEntityRemoved method currently tries retrieving the GameObject for a given entity ID and destroys the object if it exists. This is a simple method that does not depend on any components being on an entity, so it can be left unchanged.

Enable the custom GameObject creator

Now that you have a custom GameObject creator, it needs to be enabled. You may recall that you enabled GameObject creation in both UnityClient and UnityGameLogic worker connectors. Currently both HandleWorkerConnectionEstablished methods contain the following:

GameObjectCreationHelper.EnableStandardGameObjectCreation(Worker.World, entityRepresentationMapping);

This tells the GameObject creation module to instantiate and use a GameObjectCreatorFromMetadata under the hood. To use the newly-created GameObjectCreatorFromTransform, replace the above line in both worker connectors with the following:

var gameObjectCreator = new GameObjectCreatorFromTransform(WorkerType, transform.position);
GameObjectCreationHelper.EnableStandardGameObjectCreation(Worker.World, gameObjectCreator, entityRepresentationMapping);

Test the custom GameObject creator

At last, you can test the custom GameObject creator! Restart your local deployment and play the development scene when ready. You should see that the initial rotation of your rotated sphere now matches the rotation you defined in the snapshot. Huzzah!

Updated about a year ago


c. Use transform in a GameObject creator


Suggested Edits are limited on API Reference Pages

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