Introduction

This page runs through some guidance on how to implement shooting in a SpatialOS game. It covers a
few different options and what they’ll let you achieve, and should give you the tools to decide
how you want to implement it in your game, depending on what your particular gameplay needs.

We recommend reading
Understanding authority before you read this page, as it provides
useful background to some of the ideas covered here.

Detecting a hit

When a shot has been fired, which worker detects the hit? And which has the final say on
whether someone got shot? In other words: which type of worker has
authority over the decision?

You might not want to give the power of final say to
clients: this would mean a hacked client
could do whatever damage it likes to other players, potentially regardless of where they are
in the world.

And if server-side workers
are responsible for hit detection, that can cause problems too. For example, a player could shoot at an
entity that their client has knowledge of (because the client has it
checked out), but that the server-side worker
doesn’t, if that entity is outside the server-side worker’s checkout radius. You also have to think about
making it feel right for a client: if a player thinks they shot something, but they ‘actually’ hit something else,
it can result in a frustrating user experience.

Client-side hit detection, server-side authority

This pattern assumes that you’re not modelling your projectiles as entities, and so
whatever you’re using to detect a hit exists only locally on the client. It won’t work for entity
projectiles.

The pattern we recommend is client-side hit detection, but with server-side authority to make the changes (and some
level of validation on the server side too). In this pattern, a player shoots at an entity and - on their own client
worker - determines whether or not there was a hit. We’ll explain this in detail below.

If that client thinks it hit the other entity, it sends a
command to its own player entity.
(In the entity ACL, you’ll need to restrict
authority over the component for the command server-side worker.) Whichever managed worker instance
has authority over the player receives the command. The command request should include, at minimum,
the entityId of entity that was hit.
(You’d want to use a command here because you only want this information to be sent to that specific
server-side worker.)

However, as mentioned above, you don’t necessarily want to trust what the client says. If the player has
hacked their client, they could claim to have hit any arbitrary entity, even if there’s an obstruction
in the way, or if that entity is kilometres away.

At this point the server-side worker can do some validation. How much complexity to include here is up
to you. As an example, at the simplest, you might want to check whether the entity targeted is actually
nearby, whether there are any obvious obstructions (like a mountain) between the entities, or whether the
player actually has enough ammunition to shoot.

Once the server-side worker has checked to its satisfaction that the hit is plausible, it sends a further
command to the targeted entity, informing it that it’s been hit. (A command is appropriate here for the same
reasons as the command from the shooter.) It’s worth noting you can’t be sure that the same server-side worker has
authority over the shooting entity and the targeted entity. So it might be received by the same worker,
or it might not.

The server-side worker, on receiving the command, can then apply the damage. Unlike the original “I hit something”
command, this command comes from a server-side worker and so can be trusted. When a command is
received, the payload includes the ID of the worker that sent it (assigned by the runtime, not by
the worker that sent the command): so you can use that to check that the command was sent by the right
type of worker.

Alternatives: server-side hit detection

The alternative is to do hit detection on the server side - you can see an example of this in the
Pirates tutorial project. In that pattern, the client worker
announces its attempt to shoot, but volunteers no opinion on whether it resulted in a hit.
Workers can still execute related logic, such as visualising the shot
(on all client workers) and calculating potential hits (on the server-side worker).

A disadvantage of this is that
it’ll only work if both players are checked out by the same server-side worker: the managed worker
authoritative over the player shooting will need to at least have the target entity checked out. Otherwise
it’ll never be able to detect the hit, because it doesn’t know the target exists.

You can see the problem clearly in the the diagram above. If Player 1 shoots at Player 2, Server-side worker 1 won’t
be able to detect a hit, because it doesn’t have Player 2 checked out.

One solution to this problem is to make sure that workers will always be able to see players that can see
each other. You can do this by making the worker overlap zone is at least as big as a player client’s
checkout area, ie as far as the client can see. But increasing the checkout area increases the load on
the worker, and the bigger that area, the more inefficient it’ll be (because they receive data for entities
that aren’t actually necessary for most of the worker’s computation).

There are some other disadvantages of this pattern: how the client experiences shooting (client-side responsiveness),
and server-side correctness. There’s a full round-trip’s worth of delay before the client can perform any logic
in relation to a hit.

In many games, during a laggy period, when a user shoots someone it might immediately visualise a successful hit on
their client, but the target only dies once the server confirms the valid hit and that the target’s health is
now zero. However, with server-side hit detection, you wouldn’t even be able to visualise the hit. What a user
would probably see is that after too long a moment the target suddenly at once displays all the
hits that just got confirmed, and they drop dead. As mentioned above, this pattern isn’t
a great user experience.

Visualising shooting

Visualising shooting happens at two main points: the origin of the shot, and where it hits.

Events are an appropriate tool to use here. If an event is emitted by an entity when it shoots and
when it gets shot, all clients that have the shooting or shot entity checked out will receive the event
and visualise that occurrence appropriately.

You can modify the commands described above to get a more exact visualisation. For example in the
“I hit something” command, you can include a position (in the local space of the target entity)
identifying the exact point it was shot, so that you can display an animation on that point if you want.

You could also modify the message flow above to have a smoother client experience and remove lag. To do
this, instead of triggering the “shots fired” event, you could play both the firing and the hit animation
immediately on Client 1. Then, after Server-side worker 1 validates the hit, it can trigger the “shots fired”
event so that Client 2 can play the animation too. You’ll need to adapt the component event so that it
includes the EntityId of the firer, so that Client 1 knows not to replay the animation when it receives
an event that comes from its own shot.

As for visualising what happens in between when a shot is fired and when it hits - that’ll be
covered later on when discussing models for shooting.

Shooting at things that aren’t entities

Most of the discussion so far has assumed that the player is shooting at another player or an NPC,
both of which will be entities. You may also want to think about what happens if a player doesn’t
hit an entity: how (if at all) do you want it to interact with the environment? This will depend
heavily on exactly how the environment of your world is set up. Some options are:

If you have fine-grained environment entities, follow the same pattern as above.

Broadcast an event from the player who fired the shot.

However, this won’t be observed by clients that have the target position in their interest region
but not the firing player entity.

Run a query for all players around the point that was shot,
and send commands to all of them.

This means waiting for the query result to return before sending many commands, which increases
latency and is inefficient.

Other effects of shooting

You may want to have more complex effects of a shot than just doing damage to an entity: for example,
explosive ammunition. This could get pretty complicated in implementation terms - for example, what
happens if you want to damage multiple entities? Some of the options described above in
Shooting at things that aren’t entities can apply here.

Players shooting vs NPCs shooting

The discussion so far has also talked mostly about implementing a player shooting. The flow is pretty
similar if the entity shooting is an NPC.

The main difference is that, if the entity shooting is a player, the shooting originate from a client
worker. If the entity shooting is an NPC, the shooting comes from a managed worker. So when it’s an
NPC shooting, because it comes from a server-side managed worker, you don’t need to think so much about
validation - you can trust it.

However, it also comes with a downside, similar to the one discussed in
Alternatives: server-side hit detection above: an NPC won’t
be able to shoot at something that the server-side worker hasn’t got checked out.

If the managed worker’s entity interest range is the same as the client’s, then this shouldn’t cause
too much of a problem. On the client side, the radius will be determined by how far your client
workers/players need to be able to ‘see’ around them. On the server-side, in general you want to
minimize a server-side worker’s checkout area while maintaining gameplay correctness. The larger
the checkout region, the more updates your server-side worker will receive, and the slower it will
run. How small this checkout region is depends on the things a server-side worker is authoritative
over, and how far away the things they need to know about are. Having NPCs able to see far into
the distance would definitely be a limiting factor on efficiency.

Models for shooting

On to the second aspect of implementation. The question you need to consider here is: what
does your shooting actually look like?

Straight ray casts

For laser-style weapons that shoot in a straight line, you can use the simplest model for shooting:
a straightforward raycast.

This is effectively not to model projectiles at all. The advantage here is the simplicity: no physics
simulation of projectiles needed, no overhead of adding something to the scene.

Because the bullet is going too fast to be seen anyway, there’s no need to do anything more. Just
have the visualisation described above.

Sphere cast/trajectory calculation

If you want to have a more complex effect, you can model a fast moving projectile using a series of
ray/sphere casts to approximate an easy to calculate trajectory.

If you fire a bullet that has an arc, that arc’s fairly easy to calculate. You could do a sphere cast
each time step, from where the abstract bullet was last frame to where it is this frame.

You still don’t need to create any physical objects (either local game objects or SpatialOS entities),
avoiding that overhead.

Local game objects

For something slower-moving, like a cannonball, you’ll want to be able to see the projectile in flight.
At this point you’ll probably want to create some kind of local game object. If you’ve done the Pirates
tutorial, you’ll have come across this model: the cannonballs in Pirates work this way.

However, adding stuff to the scene has a cost, which is why you probably don’t want to do it otherwise.
Just calculating where it goes and whether it collides with anything would be enough.

SpatialOS entities

Using entities is a pretty heavyweight solution for shooting. In general, objects that make sense
as entities are relatively long-lived, and have a rich presence in the world. A bullet - which
would be created when it’s fired, and usually destroyed when it hits a target - is very short-lived,
and so probably isn’t worth the overhead. There’s overhead of entity creation, delay of initial
authority allocation (if you’re using a delay on handover), bandwidth usage when keeping the position
synchronised, and also potential for creating the entity to fail, which complicates it further.

But there are options where you actually want to model your projectile as an entity. Most of these
wouldn’t come under the banner of “shooting”, exactly: for example, one of these cases is for things
that are fully physical, like grenades. Another case is long-range, slow-moving things like rockets.

In general, the bigger, longer-lived, and slower your projectiles, the more it makes sense for them
to be entities. And the more likely they are to cross worker boundaries, the more having them as
entities will be helpful. A long-range missile, which will almost certainly cross large parts of the
world, will need to be an entity so that it doesn’t get lost when it moves from one server-side worker
to another.

A note about speed: entities that are moving fast may experience some
lag when crossing between workers. For this reason, in general we don’t recommend having entities that
are very fast-moving: if you do, you’ll need to look into local simulation and
prediction.

Firing at things far away

Lots of the discussion above is predicated on the idea that players can only shoot at things fairly
nearby: that is, things that their client has checked out. This makes a lot of things easier, and
is why client-side hit validation makes sense. It means you don’t have to think about how to synchronise
hit detection across worker boundaries.

If you do want players to fire beyond the boundaries of what they have checked out, a lot of the above
will be more complicated.