This document is about: FUSION 2
SWITCH TO

Weapons & Shooting

Overview

The Weapons component is responsible for handling player weapons. It maintains a list of current weapons and handles weapon switches. Additionally, it provides an interface for common weapon actions such as Fire and Reload. Weapon objects can be picked up or dropped to the ground, see more in Weapon Drops section.

There are two base classes for weapons: Weapon and WeaponFirearm. Weapon holds the bare weapon minimum and WeaponFirearm encapsulates the basic firearm functionality like fire logic, magazine handling, dispersion, and recoil.

Weapons and projectiles in BR200 are mostly based around hitscan functionality as it was convenient for this project. For a more elaborate approach to weapons and projectiles as well as more elaborate documentation about different projectile approaches please check the Projectiles Essentials and Projectiles Advanced samples.

Hitscan Weapons

Hitscan weapons are the primary weapons in the game. When fired, hitscan weapons use raycast to evaluate hits. There is no network object spawned for every projectile, instead only a small ProjectileData struct is stored in a circular buffer to calculate visual effects on peers. Hitscan weapons have the option to create a dummy visual-only projectile that is flying to the target (check the DummyProjectile script).

C#

public struct ProjectileData : INetworkStruct
{
    public Vector3 Destination;
    public Vector3 ImpactNormal;
    public int     ImpactTagHash;
}

[Networked, Capacity(10)]
private NetworkArray<ProjectileData> _projectileData { get; }

Projectile Weapons

Projectile weapons (ProjectileWeapon, ThrowableWeapon) spawn standalone projectile objects that travel over time. The projectiles execute short raycasts between their previous position and new position every simulation tick. Projectile weapons are only used for grenades in this project. For a more in-depth implementation check the Projectiles Essentials and Projectiles Advanced samples.

Recoil

Weapon recoil is a common game behavior based on real weapons where weapons tend to aim up during continuous shooting. Usually in games weapons follow a fixed or semi-random path that players can learn and muscle memorize through extensive play which leads to players being able to counter the recoil effect with their input.

BR200 comes with a recoil system. Every weapon can have a recoil pattern (also known as spray pattern) assigned. The recoil pattern is a scriptable object that defines the weapon's path during continuous shooting. Recoil directly influences player look rotation and players can fight against it. The countering process is called Recoil Reduction, and you can find it in the SetLookRotation method in the Agent script. After shooting stops, the look rotation returns automatically to the initial value.

Recoil pattern
Recoil pattern and actual bullet holes when firing without dispersion

The recoil pattern defines the exact position of every bullet in a sequence. After the initial recoil sequence (recoil start values) completes, recoil follows an endless loop (recoil endless values).

Be aware that weapon dispersion is applied on top of the recoil position, so there is still some shooting randomness based on the dispersion values.

Dynamic Dispersion

Every firearm weapon has a dispersion behavior setup.

Weapon Dispersion

Dispersion is increased during continuous fire and automatically decreased back to the default value after fire halt. Weapon dispersion is further multiplied by the character's state (running, airborne, aiming).

Weapon Dispersion
Dispersion and recoil setup

Piercing

Projectile piercing (also known as penetration) is the ability to shoot through certain objects in the game world. This usually comes with a damage penalty.

Piercing is calculated directly when processing hits in the HitscanWeapon. Every projectile can have a piercing setup assigned that specifies a damage multiplier for different materials. Projectiles don’t penetrate an object with a damage multiplier of zero. Materials are differentiated based on the Unity Tag assigned to game objects and checked in runtime based on tag's hash.

Piercing Setup

Damage Drop

Projectile damage drops with distance from the fire origin. Damage drop off is controlled by the setup defined in the DummyProjectile component.

Dummy Projectile

Third Person Shooting

Third person shooting needs to handle the difference between what the player sees through the camera and what the player character can actually hit from their position in the game world.

When calculating hits we first need to know the target point. The target point specifies where the player is aiming and is calculated by firing a raycast directly from the camera in the weapon's script every FixedUpdateNetwork(). When fire input is processed, the weapon will take the target point and shoot another raycast from the character fire position to the target point to get the actual hits.

Third Person Shooting
Third person shooting diagram
Note: The fire position is not at the end of the barrel of the gun. Originally we used the barrel position, but since the weapon position is influenced by animations, the firing experience was less stable during rapid movement like strafing. Therefore, we switched to a fixed fire position near the character's shoulder.

On top of these basic calculations there are some additional improvements for a better shooting experience:

  • When the target point cannot be reached from the player's character, the red cross is shown to indicate the actual landing point
Red Cross
A red cross is shown when target point cannot be reached
  • Raycast from the fire position to the target point ignores environment hits that aren’t either close to the fire position or target point to mitigate problematic behavior that we call The Corner Issue. This vastly improves shooting behavior around corners, trees, and other objects. The player is actually shooting where the crosshair is pointing - but introduces a small margin to shoot players behind a corner if aiming right next to the corner. This ignore behavior can be found in the ProjectileUtility that is used by all projectile calculations.
Corner Issue
Projectile will ignore the corner to reach the target point
  • When the target point is close to the player character and the angle between direction to target point and character forward is too large, target point is calculated some distance in forward direction instead.
Angle Ignore
If angle to crosshair is too large, weapon shoots forward

Projectiles

Projectiles that travel through the game world over time aren't really used in BR200 except for grenades. Check the Projectiles Essentials and Projectiles Advanced samples for more elaborate projectiles examples.

Grenades

Grenades can be found in item boxes. When a grenade is armed, a small countdown decreases grenade detonation time up until a certain minimum (see ThrowableWeapon). Grenade projectiles are handled by special weapons as if shot out of an invisible grenade launcher.

Note: Players can cycle through grenades with alphanumeric key 4.

Explosive Grenade

Grenade that spawns an explosion object.

Explosive Grenade

Flash Grenade

Grenade that blinds players within a certain distance if they look in the direction of the grenade when it detonates. The simple player blindness effect is calculated in the AgentSenses component.

Flash Grenade

Smoke Grenade

Grenade that spawns a smoke effect.

Smoke Grenade

Health & Damage System

Every hit is processed in HitUtility where the HitData struct is created, and damage is multiplied by the possible body part multiplier. BodyPart is a child class of default lag compensated Hitbox with the addition of a damage multiplier (e.g., the head has a multiplier higher than 1 while limbs have a multiplier lower than 1). HitData is then processed by the target’s Health component.

The health component creates a BodyHitData struct with only the necessary information to synchronize hits to all clients. Hits are stored in a small networked circular buffer of BodyHitData structures.

C#

public struct BodyHitData : INetworkStruct
{
    public EHitAction Action;
    public float      Damage;
    public Vector3    RelativePosition;
    public Vector3    Direction;
    public PlayerRef  Instigator;
}

[Networked, Capacity(4)]
private NetworkArray<BodyHitData> _hitData { get; }
Note: Notice how the RelativePosition of the hit is stored in BodyHitData instead of an absolute position. Since the position of the proxies is interpolated between the last two received ticks from the server, the relative position is better to place hit effects such as blood splashes correctly onto the body.

Based on the changes in the hit buffer, the appropriate hit reactions are triggered - Hit direction in UI, showing hit confirmation and numbers to damage instigator, spawn of blood effect.

When a player hits another player, the impact of the bullet and hit effects (blood) are shown immediately. However, the hit confirmations (red cross in UI and hit numbers) are shown to the player after the hit is received as part of the new server state (the hit is “confirmed” by the server). This introduces a small delay (based on network conditions) but ensures only valid hits are presented to the player.
Back to top