Get SpatialOS

Sites

Menu

Unity MonoBehaviour components are currently experimental. We're very open to feedback - don't hesitate to get in touch on the forums if you have any thoughts.

Experimental Unity MonoBehaviour Components

This section describes the new, currently experimental, generated Unity MonoBehaviours used for interacting with SpatialOS components in Unity. The command spatial worker codegen now generates Unity MonoBehaviours in your Unity project’s Assets/Improbable/Generated directory; one for every component defined in your SpatialOS project’s schema. You can use these MonoBehaviours in your Unity project just like any other MonoBehaviour. You can add them to your prefabs as script components and reference them using the standard Unity syntax:

var exampleComponent = GetComponent<SpatialOsExampleComponent>()

They provide all of the functionality of the current Readers and Writers and a simpler syntax to send commands which does not require command descriptors.

On this page:

Adding a generated MonoBehaviour to a prefab

After running spatial worker codegen, the generated MonoBehaviours should be present in the directory Assets/Improbable/Generated (relative to the root of your Unity project).

When you open or return to the Unity Editor, a script reload will cause Unity to recognize the generated MonoBehaviours so you can use them like any other MonoBehaviours defined in your project. Simply select a prefab from Assets/Entity Prefabs in the Project browser, then click “Add Component” at the bottom of the Inspector panel, and start typing the name of the SpatialOS component whose MonoBehaviour you’d like to add to the prefab. Unity’s search filter should narrow the list down. The name of the MonoBehaviour for the component Example will be SpatialOsExampleComponent.

Using the generated MonoBehaviour from another script

After you’ve added a generated MonoBehaviour for each component that you want to interact with to a prefab, you can use them from other scripts on that prefab. For example, suppose you’ve added the ‘SpatialOsExampleComponentMonoBehaviour to your prefab, and you have another MonoBehaviour, ‘GameLogic’. You can reference the former in the latter using the standard Unity syntax:

using Improbable.GeneratedCode;

class GameLogic
{
    private SpatialOsExampleComponent exampleComponent;

    private void OnEnable()
    {
        exampleComponent = GetComponent<SpatialOsExampleComponent>();
    }
}

Note: You must import the namespace Improbable.GeneratedCode as this is the containing namespace of all generated MonoBehaviours.

Before you interact with this component, you must wait for it to become ready, i.e. you must be receiving component updates and authority updates for it. All generated MonoBehaviours expose a property bool IsComponentReady and an event OnComponentReady which you can use to be notified of when the component is ready. This means you can defer all component interaction until the component is ready:

private void OnEnable()
{
    ...
    exampleComponent.OnComponentReady += OnExampleComponentReady;
}

private void OnDisable()
{
    exampleComponent.OnComponentReady -= OnExampleComponentReady;
}

private void OnExampleComponentReady()
{
    // start interacting with the Example component
    ...
}

Note: You must deregister your callbacks in OnDisable. This is important; not doing so can lead to unexpected behaviour.

Checking if you have authority

Each generated MonoBehaviour exposes a property bool HasAuthority and an event OnAuthorityChange which you can use to be notified when authority for the associated component changes. This means you can execute specific logic depending on whether you are authoritative on the component or not:

private void OnEnable()
{
    ...
    exampleComponent.OnAuthorityChange += OnExampleComponentAuthorityChange;
}

private void OnDisable()
{
    ...
    exampleComponent.OnAuthorityChange -= OnExampleComponentAuthorityChange;
}

private void OnExampleComponentAuthorityChange(bool newAuthority)
{
    Debug.Log("authority on example component: " + newAuthority)
}

private void Update()
{
    if (exampleComponent.IsComponentReady)
    {
        if (exampleComponent.HasAuthority)
        {
            // execute authoritative logic
        }
        else
        {
            // execute non-authoritative logic
        }
    }
}

Note: You must deregister your callbacks in OnDisable. This is important; not doing so can lead to unexpected behaviour.

Getting the value of a property

Each generated MonoBehaviour exposes properties for each property defined on its associated component:

var propertyOneVal = exampleComponent.PropertyOne;
var propertyTwoVal = exampleComponent.PropertyTwo;

Responding to a change of a property

Each generated MonoBehaviour exposes an event, for each property defined on its associated component, which you can use to respond to a change in the property’s value.

This means you can execute specific logic when individual properties on components are updated:

private void OnEnable()
{
    ...
    exampleComponent.OnPropertyOneUpdate += ExamplePropertyOneUpdated;
}

private void OnDisable()
{
    ...
    exampleComponent.OnPropertyOneUpdate -= ExamplePropertyOneUpdated;
}

private void ExamplePropertyOneUpdated(float newPropertyOne)
{
    Debug.Log("new property one on example component: " + newPropertyOne);
}

Note: First the MonoBehaviour’s property value is updated, and then the associated event’s callbacks are invoked.

Note: You must deregister your callbacks in OnDisable. This is important; not doing so can lead to unexpected behaviour.

Setting the value of a property

Each generated MonoBehaviour exposes a method void SendComponentUpdate(Update update) which you can use to send a component update. Note that the type Update is an interface nested inside the class of the associated component. This method will only result in an actual component update if the executing Unity worker is authoritative for the component. If it is not, the update will be silently ignored. You’ll only see the update once it comes back from SpatialOS, so on the next frame at the earliest. You can create an update object first and then send it:

var update = new Example.Update();
update.SetPropertyOne(3.0);
exampleComponent.SendComponentUpdate(update);

Or, you can send the component update in one line:

exampleComponent.SendComponentUpdate(new Example.Update().SetPropertyOne(3.0));

Triggering an event

An event is triggered in much the same way as setting the value of a property:

var update = new Example.Update();
var exampleEvent = new ExampleEvent();
exampleEvent.eventValue = 2;
update.AddEventOne(exampleEvent);
exampleComponent.SendComponentUpdate(update);

Or:

exampleComponent.SendComponentUpdate(new Example.Update().AddEventOne(new ExampleEvent(2)));

Similarly, this will only work if the executing Unity worker is authoritative on the component. If it is not, the event update will be silently ignored.

Responding to an event

Each generated MonoBehaviour exposes an event, for each event defined on its associated component in the schema, which you can use to be notified when that event is received. This means you can execute specific logic when individual events on components are received:

private void OnEnable()
{
    ...
    exampleComponent.OnEventOne += OnExampleEventOne;
}

private void OnDisable()
{
    ...
    exampleComponent.OnEventOne -= OnExampleEventOne;
}

private void OnExampleEventOne(Example.ExampleEvent event)
{
    Debug.Log("received event one: " + event.eventValue);
}

Note: You must deregister your callbacks in OnDisable. This is important; not doing so can lead to unexpected behaviour.

Sending a command

The list of available commands you can send from a component can be seen via code completion results:

exampleComponent.SendCommand.

will present a list of options. The following world commands will always be in this list:

  • CreateEntity
  • DeleteEntity
  • ReserveEntityId
  • SendQuery

And all of the commands defined in your schema will also be in this list, e.g.:

  • CommandOne
  • CommandTwo
  • Heartbeat
  • Damage
  • Increase_Health
  • Increase_Level

The first list are world commands. The latter list are component commands. For an overview of the difference between the two, see the section on commands. All of these methods will be present on all generated MonoBehaviours, since any component can send any world command, and any component can send any component command. This means that SendCommand has methods for sending any command defined in the project schema.

Make sure that an component command is only sent to an entity that has a component that defines that command, and that the response logic is implemented somewhere, otherwise no command response will be received; a timeout error will occur. One way to be sure of this is to only send a command to an entity ID that was returned from a query for entities that have a component that defines that command.

Note: Sending a command from a generated MonoBehaviour will succeed only if the executing Unity worker is authoritative on the associated component. If the worker is not authoritative, the command will fail with an error message explaining the lack of authority.

Note: It is possible to have two commands defined in your schema with the same name, but in differently named components. Suppose you have the components Health and Level, both with an Increase command. Then the command you wish to send will be disambiguated by the name of the method; the available command methods will be named Increase_Health and Increase_Level (as above). Further disambiguation will occur in the case of commmands with the same name and component name but in differently named packages.

Sending a world command

You can send a CreateEntity world command as follows:

exampleComponent.SendCommand.CreateEntity("Player", EntityTemplate, response =>
{
    if (response.StatusCode == StatusCode.Success)
    {
        Debug.Log("created an entity with ID: " + response.Response.Value);
    }
    else
    {
        Debug.Log("failed to create an entity with error: " + response.ErrorMessage);
    }
});

The DeleteEntity, ReserveEntityId and SendQuery world commands can be sent similarly.

Responding to a command request

Each generated MonoBehaviour exposes an interface, for each command defined on its associated component in the schema, which you can use to be notified when a command request for that command is received. You may choose to respond to the command request in a synchronous or asynchronous way by calling RegisterResponse() or RegisterAsyncResponse() methods respectively. The synchronous callbacks are passed the request data along with a ICommandCallerInfo object containing information about the caller of the command; they must return an object of the return type of the command:

private void OnEnable()
{
    ...
    exampleComponent.OnDamageCommandRequest.RegisterResponse(HandleDamageCommandRequest);
}

private void OnDisable()
{
    ...
    exampleComponent.OnDamageCommandRequest.DeregisterResponse();
}

private DamageResponse HandleDamageCommandRequest(DamageCommandRequest request, ICommandCallerInfo commandCallerInfo)
{
    return new DamageResponse(request.amount / 2));
}

The asynchronous callbacks are passed a ResponseHandle object which exposes a property TRequest Request that returns the request data, and a method void Respond(TResponse response) to send the command response:

private void OnEnable()
{
    ...
    exampleComponent.OnDamageCommandRequest.RegisterAsyncResponse(HandleDamageCommandRequest);
}

private void OnDisable()
{
    ...
    exampleComponent.OnDamageCommandRequest.DeregisterResponse();
}

private void HandleDamageCommandRequest(DamageCommandResponseHandle responseHandle)
{
    var requestAmount = responseHandle.Request.amount;
    responseHandle.Respond(new DamageResponse(requestAmount / 2));
}

Note that these callbacks will only be invoked if the Unity worker is authoritative on the associated component. This means you can execute specific logic when you receive a command request, and send an appropriate response.

Note: You must deregister your callbacks in OnDisable. This is important; not doing so can lead to unexpected behaviour.

You may only register one command request handler per command.

Inspecting component details live in the Unity Editor

When you are running your simulation, i.e. after a spatial local launch, and you click Play in the Unity Editor to debug your UnityClient or UnityWorker, you can inspect your components in the Inspector panel.

After your worker connects, GameObjects should populate the scene corresponding to the entities in your simulation. These GameObjects will be instantiations of the prefabs you added the generated MonoBehaviours to. When you select one of these GameObjects in the Scene Hierarchy, you can see the attached generated MonoBehaviours in the Inspector panel. Expanding the script property panel of one of these MonoBehaviours will reveal the following details:

  • Live property values: for all properties on the component that have equivalent Unity native types (i.e. numbers, strings, and vectors).
  • Initialization: whether or not the Unity instance has started receiving component / authority updates for this component.
  • Authority: whether or not the Unity instance has authority on the component.
  • Component update log: a trailing log of the most recent component updates received for this component and the received values.
  • Average # of component updates received per second.
  • Command request log: a trailing log of the most recent command requests received for this component along with the request values.
  • Average # of command requests received per second.

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums