Skip to main content

Shooting

Turret Rotation

Just like movement, shooting has different control schemes for mobile/touch and non-touch devices too. On mobile platforms, the right joystick on the screen calculates an input direction for the Player script, but this time the direction is used to rotate the player's turret by passing the Vector2 direction value to the RotateTurret() method. Internally, the turret rotation is directly applied locally, to avoid any noticeable lag on the user's end. Additionally, the player sends its turret rotation to the server, who distributes it to other clients. This is done using a Remote Procedure Call (RPC).

Calling methods from a client to be executed on the server is called a Remote Procedure Call (RPC). For this, Netcode uses the [ServerRpc] attribute above a method definition. Additionally, the method name needs to have ServerRpc appended, like RotateTurretServerRpc(). The simplest example would be a client sending its turret rotation to the server, which sets it to a NetworkVariable, that in turn updates the value on all other clients:

//this is executed on clients
RotateTurretServerRpc(...);

//this is executed on server
[ServerRpc]
void RotateTurretServerRpc(int newRotation)
{
turretRotation.Value = newRotation;
}

On non-touch devices, instead of a joystick, the mouse position on the screen is used to rotate the turret. This is done by calculating the mouse position on a 2D screen around the player and sending that value to the RotateTurret() method. To further illustrate the current turret rotation and potential shot direction, on desktop builds the mouse cursor has been replaced with a crosshair like icon, set under Edit > Project Settings > Player – Default Cursor. Since mobile devices do not have a cursor visible at all times, there is an aiming indicator object set on the UIGame script that is instantiated and attached to the player’s turret at runtime.

Shooting010

Firing

On mobile devices, shooting happens automatically after a short delay after the turret rotation joystick has been dragged. As long as the joystick has not been released, the player keeps firing bullets. The delay at the beginning is in place to give the user a chance to position the turret before the first bullet is fired. On non-touch devices, firing a bullet is triggered by clicking the left mouse button (or holding it down to keep firing).

In the event of shooting, a client processes its input in the Player script and determines to shoot a bullet by calling the Shoot() method. It then sends a shot request to the server using a RPC. In order to reduce lag, the client sends its own, locally calculated position of the bullet along with the RPC. The server receives the request, processes it and calculates an intermediate spawn position based on the client and current server position. It then spawns the bullet over the network at that position. This is the signal for all other clients to spawn their own local copy of the bullet using our pooling system. At this point, a new bullet has been created successfully.

But wait, there's more! A RPC can also be initiated from the server and sent to all clients. This is specified using the [ClientRpc] attribute above a method definition. Similar to ServerRpc, a method using this needs to have ClientRpc appended to it. As mentioned with the bullet spawn logic above, the server spawns a bullet and then e.g. tells all clients to instantiate a muzzle flash and sound effect at that position. For example:

//this is executed on server
[ServerRpc]
void ShootServerRpc(...)
{
...
OnShotClientRpc();
}

//this is executed on clients
[ClientRpc]
protected void OnShotClientRpc()
{
...
}

Only the server is responsible for determining whether a bullet hit a player or not. This is because even though collisions are registered on all clients, e.g. to spawn particle effects, only the server executes the logic for calculating player damage. It does so by getting the damage value from the bullet and applying it to the player's health value (or shield, if any).

There are three types of bullet prefabs in this asset, but they all share the Bullet component with slightly different values. While there is endless ammunition for the default bullet a player can shoot, the other two bullets have to be collected via powerups and also have limited ammunition – read more about powerups in the Collectibles section. The currently available bullet to shoot is controlled by the currentBullet variable on the Player script. Changing bullets only means changing this index variable, as all available bullet prefabs are referenced in an array on the Player script per player prefab. This also means that different player prefabs can have different bullets, allowing for highly customized player prefabs and awesome gaming opportunities.

Shooting020

Shooting030