Get SpatialOS

Sites

Menu

Pirates 3 - Update a component property

In the previous lesson, you filled the game world with enemy pirate ships. In this lesson you’ll:

  • learn how to get write access to a component
  • learn about limiting which code runs on which workers
  • write to a component by updating the steering component properties on the PirateShip

1. Write a MonoBehaviour to move PirateShips

As you saw at the end of the last lesson, the world is scattered with pirate ships, but they don’t do anything. In this step, you’ll write a script that uses the existing ShipControls component to move the ships around.

1.1. How to move ships around

You saw in lesson 1 that an entity’s position is stored in its Position component. The entity’s template also included a ShipControls component.

Properties are part of a component, and they store any data of an entity that you want to share between workers. ShipControls has two properties that will be useful here: targetSteering and targetSpeed.

How the PlayerShip moves

PlayerShip prefabs include a script (PlayerInputController.cs) that writes player keyboard input to the ShipControls properties whenever a player presses particular keys.

Another script (ShipMovement.cs) watches for updates to ShipControls, and updates the Position accordingly. That’s what moves the ship.

Why don’t we just write to the Position component directly?

Two reasons. One: in a real game, you wouldn’t want to let players write to their own position, in order to prevent cheating. Two: this way lets you re-use ShipMovement.cs to make it easy to steer a PirateShip around.

How the PirateShip will move

When you wrote the template method to create PirateShip entities, you initialized its properties with the value 0:

.AddComponent(new ShipControls.Data(0, 0), CommonRequirementSets.PhysicsOnly)

There’s nothing changing those values from 0 at the moment, which is why the pirate ships don’t move.

Like the PlayerShip prefab, the PirateShip prefab includes ShipMovement.cs, so it’s ready to move as soon as it receives updates to the ShipControls properties. So you need to write a MonoBehaviour that makes steering decisions and updates the ShipControls properties with them.

In this lesson you’re going to use components which have already been created for you. If you’re keen to know how to define new components - you’ll learn all about it in the next lesson.

Which worker?

NPCs should be completely controlled on the server side. So you’ll lock down this script so it can only be run by a UnityWorker.

1.2. Create a MonoBehaviour to steer the ship

  1. In the Unity Editor’s project panel, navigate to Assets/Gamelogic/Pirates/Behaviours/ and create a C# script called SteerRandomly.

  2. Replace the script’s contents with the following:

    using UnityEngine;
    using Improbable.Unity;
    using Improbable.Unity.Visualizer;
    
    namespace Assets.Gamelogic.Pirates.Behaviours
    {
        public class SteerRandomly : MonoBehaviour
        {
        }
    }
    
  3. So that you can interact with the ShipControls component, at the top of the script, add using Improbable.Ship;:

    using UnityEngine;
    using Improbable.Unity;
    using Improbable.Unity.Visualizer;
    using Improbable.Ship;
    
  4. Just before the class declaration, add the line [WorkerType(WorkerPlatform.UnityWorker)].

    This annotation, provided by the SpatialOS Unity SDK, means that when you build the project, this script will only be included in prefabs on the UnityWorker.

    // Add this MonoBehaviour on UnityWorker (server-side) workers only
    [WorkerType(WorkerPlatform.UnityWorker)]
    public class SteerRandomly : MonoBehaviour
    {
    }
    
  5. Just inside the SteerRandomly class, add the line [Require] private ShipControls.Writer ShipControlsWriter;.

    This does two things:

    • It imports a ShipControls.Writer.

      This is an object you can use to interact with ShipControls. It has methods for checking the current values of properties, sending updates to properties, and a lot more. You’ll use one of those methods in the next step.

    • It uses the [Require] annotation.

      This annotation, also provided by the SpatialOS Unity SDK, is another way of controlling when this script is run.

      Workers can have read access or write access to a component. Only one worker at a time, regardless of whether it’s a UnityWorker or UnityClient, can have write access to a specific component on a specific entity.

      The [Require] annotation means that this script (SteerRandomly.cs) is only enabled if the worker has write access to the component specified (ShipControls): ie, if the worker is allowed to have a Writer.

    [WorkerType(WorkerPlatform.UnityWorker)]
    public class SteerRandomly : MonoBehaviour
    {
        /*
         * This MonoBehaviour will only be enabled for the single UnityWorker
         * which has write access to this entity's ShipControls component.
         */
        [Require] private ShipControls.Writer ShipControlsWriter;
    }
    
  6. Add a method that sends a random update to the ShipControls.

    The following shows you how to use the ShipControlsWriter to do this:

    private void RandomizeSteering()
    {
        ShipControlsWriter.Send(new ShipControls.Update()
            .SetTargetSpeed(Random.value)
            .SetTargetSteering((Random.value * 30.0f) - 15.0f));
    }
    

    For all components and all properties, there’s a method that looks like this: <component name>.Update().Set<property name>().

  7. Finally, something needs to invoke RandomizeSteering().

    You can use the Unity function InvokeRepeating() to do this. You should use InvokeRepeating in OnEnable(), and cancel it in OnDisable(), in order to prevent unexpected behaviour - for more information see MonoBehaviour lifecycle:

    private void OnEnable()
    {
        // Change steering decisions every five seconds
        InvokeRepeating("RandomizeSteering", 0, 5.0f);
    }
    
    private void OnDisable()
    {
        CancelInvoke("RandomizeSteering");
    }
    

The finished script should look like something this:

using UnityEngine;
using Improbable.Unity;
using Improbable.Unity.Visualizer;
using Improbable.Ship;

namespace Assets.Gamelogic.Pirates.Behaviours
{
    // Add this MonoBehaviour on UnityWorker (server-side) workers only
    [WorkerType(WorkerPlatform.UnityWorker)]
    public class SteerRandomly : MonoBehaviour
    {
        /*
         * This MonoBehaviour will only be enabled for the single UnityWorker
         * which has write access to this entity's ShipControls component.
         */
        [Require] private ShipControls.Writer ShipControlsWriter;

        private void OnEnable()
        {
            // Change steering decisions every five seconds
            InvokeRepeating("RandomizeSteering", 0, 5.0f);
        }

        private void OnDisable()
        {
            CancelInvoke("RandomizeSteering");
        }

        private void RandomizeSteering()
        {
            ShipControlsWriter.Send(new ShipControls.Update()
                .SetTargetSpeed(Random.value)
                .SetTargetSteering((Random.value*30.0f)-15.0f));
        }
    }
}

2. Apply the changes

2.1. Set up Unity

Previously, you’ve used the spatial command-line to build your game. But it’s quicker to build from Unity. To set up building from Unity:

  1. Use the menu Window > SpatialOS to open the SpatialOS window.
  2. In the window, click Settings, and scroll down.

    In the Spatial CLI location field, enter the location of spatial.exe (Windows) / spatial (MacOS) is installed on your machine.

    • On Windows: you can run where spatial to find this location.

    • On Mac: if you used brew cask install spatial to install spatial, the location should be /usr/local/bin/spatial.

2.2. Build

For the game to register the changes you’ve made:

  1. In Unity, open the EntityPrefabs folder, and double-click on the PirateShip prefab to open it.
  2. Click Add component, and add the SteerRandomly script to the prefab (it should appear in the search list as you start typing its name).
  3. Build entity prefabs: In the SpatialOS window (open it using the menu Window > SpatialOS), under Entity prefabs, click Build all.

    Whenever you change what’s on a prefab, you need to build prefabs.

  4. In the same window, under Workers, click Build.

    This builds your worker code for local development, and is equivalent to running spatial worker build UnityWorker UnityClient --target=development, as you did in the first lesson. It’s just quicker to run this, as you don’t have to close Unity.

You don’t always have to build everything. For a handy reference, see What to build when.

3. Check it worked

To test the changes, run the game locally:

  1. In the SpatialOS window, under Run SpatialOS locally, click Run.

    This opens a terminal window and runs spatial local launch for you.

  2. When SpatialOS is ready, run a client (open the scene UnityClient.unity, then click Play ▶), and click CONNECT.

It’s done when: The enemy pirate ships move around the world:

So many pirates

If you open the Inspector, you’ll be able to see them all moving.

Don’t stop spatial local launch now - you’ll need it in the next lesson.

Lesson summary

In this lesson you:

  • thought about which code should run on which worker
  • learnt about the [Require] annotation
  • learnt about component Writers
  • used a Writer to send an update to a component property
  • re-used existing functionality to move the pirate ships around

At this stage, your enemies are fully-fledged and roaming around the world. It’s time to introduce some action!

In the next lesson, you’ll work on a new feature: firing cannonballs.

Was this page helpful?

Thanks for letting us know!

Thanks for your feedback

Need more help? Ask on the forums