Pirates 5 — Detect a collision
In the previous lesson you made the ship fire cannonballs using an event which is synchronized by SpatialOS. This resulted in the cannonballs being fired on all workers, not just the UnityClient of the firing player.
In this lesson you’ll:
- detect collisions between cannonballs and ships using Unity functions
- learn more about what happens on which worker
This is a short lesson, because there isn’t much to do here - it’s purely Unity code.
The reason there’s not much to do is because, at the moment, there’s no sensible consequence for a cannonball hit. There’s no concept of health, or dying. So this sets up for the next lesson, where you’ll add the concept of health.
This is what you’re going to set up in the next few lessons:
1. Understanding what happens on which worker
In the previous lesson, you were working on CannonFirer.cs
: a script that is run on all workers that have
the firing ship checked out. When this script receives a FireLeft
or FireRight
event, it creates a cannonball
GameObject on whichever worker.
What does “checked out” mean?
To understand, it helps to think about a game world that’s a little bigger than the world in this game.
Most of the things you have to take into consideration when designing a SpatialOS game come from one fact: workers don’t know about the whole world, only a subset of it.
A worker only “knows” about a specific sub-set of the world, allocated to it by SpatialOS. We call this its checkout area. A worker gets all updates (eg property changes, event triggers) for all entities in its area - and none for any entity outside that area. They have read access on all components of all those entities, and UnitySDK instantiates GameObjects in the scene for all of these entities.
A worker’s checkout area can overlap with another worker. This could be a problem - what if two workers both make a change to the same component? That’s why only one worker can have write access on a component at one time.
Managing write access
As a developer, you specify which types of worker could have write access. You’ve seen an example in this tutorial,
with ShipControls
, where you always want the player’s specific UnityClient to have write access. You’ve also seen
the example of Position
. It’s important that only UnityWorker
s and not UnityClient
s have write access, but
which specific UnityWorker doesn’t matter so much.
So for each entity, SpatialOS manages which worker has write access. In the diagram above, if an entity moves from
area A to area B, UnityWorkerA
will start off with write access. Then, as the entity moves to the other side,
SpatialOS transfers write access over that entity to UnityWorkerB
.
As a developer, it doesn’t matter to you whether you’re writing code for A or B. You write them in the same way,
and make sure the right code is being run using the [WorkerType]
and [Require]
annotations.
Access control lists
As an example, let’s take a look at the ShipControls
component. What says the UnityClient can write to this
component, and change the speed and steering?
In Unity, navigate to Assets/Gamelogic/EntityTemplates/
, and open EntityTemplateFactory.cs
.
In the CreatePlayerShipTemplate
method, you’ll see the following lines:
.SetReadAcl(CommonRequirementSets.PhysicsOrVisual)
.AddComponent<ClientConnection>(new ClientConnection.Data(SimulationSettings.TotalHeartbeatsBeforeTimeout), CommonRequirementSets.PhysicsOnly)
.AddComponent<ShipControls>(new ShipControls.Data(0, 0), CommonRequirementSets.SpecificClientOnly(clientWorkerId))
.AddComponent<ClientAuthorityCheck>(new ClientAuthorityCheck.Data(), CommonRequirementSets.SpecificClientOnly(clientWorkerId))
.AddComponent<Rotation>(new Rotation.Data(0), CommonRequirementSets.SpecificClientOnly(clientWorkerId))
When you add a component, you also decide which workers have access to that component. This information gets built into an access control list, or ACL, for this entity. You saw this briefly in lesson 2.
The ACL is a component itself, and it manages:
- which workers have read access to all of the components
- for each component, which workers have write access
In this context, we look at workers in terms of requirements: ie, a component requires a worker to have certain properties. These might be:
- to be a ‘specific client’ - ie the Unity Client that owns a particular entity
- to be a ‘physics’ worker - ie a UnityWorker
- to be a ‘visual’ worker - ie a UnityClient.
When you build a entity, you specify which worker has write access when you add the component, like this
(as the second argument to the Add()
method):
.AddComponent<ShipControls>(new ShipControls.Data(0, 0), CommonRequirementSets.SpecificClientOnly(clientWorkerId))
So the ACL for the PlayerShip says:
- both the UnityWorker (aka “physics worker”) and UnityClient (aka “visual worker”) can read all components (you don’t specify a component with read access, it applies to all components equally)
- only the UnityClient that owns the entity (aka the “specific client”) can write to
Position
,ShipControls
,ClientAuthorityCheck
, andRotation
- only the UnityWorker can write to
ClientConnection
Take a look at this line in particular:
.AddComponent<ShipControls>(new ShipControls.Data(0, 0), CommonRequirementSets.SpecificClientOnly(clientWorkerId))
This line of code gives only the player’s UnityClient access to write to ShipControls
.
This allows the Unity Client to update the ShipControls
component when the player presses keys.
2. Detect the collision on the UnityWorker
Enough background. Collisions are something that should be detected server-side, so on the UnityWorker, because you don’t want players deciding whether or not they’ve hit something.
Create a MonoBehaviour that runs on the UnityWorker, detecting collisions:
- In Unity’s Editor’s project panel, navigate to
Assets/Gamelogic/Pirates/Behaviours/
- Open the C# script called
TakeDamage
. The script should look like the following:
using Improbable.Unity; using Improbable.Unity.Visualizer; using UnityEngine; namespace Assets.Gamelogic.Pirates.Behaviours { // Add this MonoBehaviour on UnityWorker (server-side) workers only [WorkerType(WorkerPlatform.UnityWorker)] public class TakeDamage : MonoBehaviour { private void OnTriggerEnter(Collider other) { if (other != null && other.gameObject.tag == "Cannonball") { } } } }
This very simple script uses the Unity function
OnTriggerEnter()
to detect collisions.- Inside the
if
statement, add the following line:
// Reduce health of this entity when hit Debug.LogWarning("Collision detected with " + gameObject.EntityId());
Now, you’ll log a message when a cannonball hits the current GameObject.
- Inside the
Add
TakeDamage.cs
to thePlayerShip
prefab.Add
TakeDamage.cs
to thePirateShip
prefab.
3. Build the changes
You added a new MonoBehaviour to the PlayerShip
prefab: TakeDamage
. For SpatialOS to make use of this
updated prefab:
- Build entity prefabs: In the SpatialOS window, under
Entity prefabs
, clickBuild all
. - Build worker code: Under
Workers
, clickBuild
.
You don’t always have to build everything. For a handy reference to what to build when in Unity, see this cheat sheet.
4. Check it worked
To test the changes, run the game locally:
- In the SpatialOS window, under
Run SpatialOS locally
, clickRun
. - Run a client (open the scene
UnityClient.unity
, then click Play ▶). - Find another ship, and press
E
orQ
to fire a cannon at it.
It’s done when: you see the message printed out in the console window which opens when you press Run
:
WARN [improbable.bridge.logging.EngineLogMessageHandler] [Worker: UnityWorker0] Collision detected with <entity id>
If you like, you can also connect another client (for a reminder on how to do this, see lesson 4, and check that it’s working on the client too.
To stop spatial local launch
running, switch to the terminal window and use Ctrl + C
.
Lesson summary
In this lesson, you’ve written some server-side logic, detecting collisions between cannonballs and enemy pirate ships.
This script only runs on UnityWorkers: Unity Clients won’t detect collisions. Which is as you want it, because things like collisions and damage shouldn’t be controlled by clients.
This is pretty limited, though - at the moment there are no consequences for a ship that’s hit by a cannonball. That’s because there’s nothing that can happen in this game yet.
What should happen is that a cannonball should damage the ship it hits.
What’s next?
In the next lesson, you’ll create a new component: Health
. You’ll add this new
component to ships, and then use the script you wrote in this lesson to decrease a ship’s health when it gets hit by a
cannonball.