Projectiles Advanced
Overview
Projectiles Advanced demonstrates how to implement networking for different types of projectiles in a shooter game. Programming multiplayer projectiles can be a difficult task which involves balancing performance, bandwidth consumption, and precision. It also requires smooth rendering which accounts for any discrepancy between the simulated and rendered projectile path (fire from camera vs. fire from weapon barrel). Projectiles Advanced aims to clarify and simplify this task a little bit.
To contextualize the projectiles, the sample is built as a simple FPS game with other supporting systems (gameplay handling, health and damage system, weapons) and can be used as a solid groundwork for building shooter games.
For an introduction to projectiles as well as more standalone examples that can be easily copied to different projects please refer to the Projectile Essentials.
HostMode
topology.Features
- Handling of projectiles without individual
NetworkObject
s using projectile data ring buffer - Numerous projectile types from hitscan to homing projectiles
- Weapon component system
- 8 different weapons
- Pulse Gun
- Rifle
- Shotgun
- Laser Gun
- Sniper
- Homing Gun
- Ricochet Gun
- Flamethrower
- Solved interpolation from weapon barrel to real projectile path (shot from camera)
- Health and damage system
- Explosions
- Predictive spawning of projectiles
Before You Start
Requirements:
- Unity 2021.3
- Fusion AppId: To run the sample, first create a Fusion AppId in the PhotonEngine Dashboard and paste it into the
App Id Fusion
field in Real Time Settings (reachable from the Fusion menu). Continue with instruction in Starting The Game section.
Download
Version | Release Date | Download | ||
---|---|---|---|---|
1.1.6 | Apr 14, 2023 | Fusion Projectiles 1.1.6 Build 182 |
Starting The Game
Debug Start
Start a game by opening and playing the Game
scene (Scenes/Game
). A slightly modified version of the Fusion standard NetworkDebugStart
window will appear where the mode in which the game should be started in can be chosen.
Multipeer
Choosing Start Host + 1 Client
or Start Host + 2 Clients
will start the game in multi-peer mode. This approach is highly recommended for testing how projectiles behave on proxies.
To switch between peers, press the 0
, 1
, 2
, and 3
keys on the numpad.
It is also possible to switch peers using the Runner Visibility Controls
window (top menu Fusion/Windows/Runner Visibility Controls
).
To see how shooting behaves on a proxy, set only Client A as visible, and only enable Client B as an input provider. Your shooting from Client B now can be observed from Client A’s perspective.
Controls
Use W
, S
, A
, D
for movement, Mouse1
for fire and Mouse2
for alternative fire if available. Weapons can be switched with the mouse scroll wheel or by pressing the appropriate alpha number key. Check the ingame UI for information about the selected weapon and types of projectiles it shoots.
Use the ENTER
key to lock or release your cursor.
Project Organization
Prefab Type | Location |
---|---|
Player and Agent | /Prefabs |
Weapons | /Prefabs/Weapons |
Projectiles | /Prefabs/Projectiles |
Player
Player (Player
script, Player
prefab) represents a connected peer in the game and has no visuals. Use Player to sync statistics, nickname, selected hero, their desire to join gameplay etc. Player also handles input.
Agent (PlayerAgent
script, Agent
prefab) represents an ingame character that is controlled by the player, is spawned by Gameplay
, has Health
, Weapons
and other usual stuff. Can be spawned and despawned as needed.
Projectile Manager
ProjectileManager
is the core of the Fusion Projectiles sample. It handles the projectile data buffer and creation/update/deletion of the visual representation of the projectiles. Projectile manager lives on the controlling object (e.g. Agent
, Turret
) and is responsible for updating projectiles from all weapons owned by that object.
Projectile manager requires projectile prefabs array to correctl#y pair projectile data to specific prefab. For simplicity projectile manager has references to prefabs directly in the inspector.
ProjectileManager
stores ProjectileData
that is used to calculate projectile trajectory and other projectile behavior:
C#
[StructLayout(LayoutKind.Explicit)]
public struct ProjectileData : INetworkStruct
{
public bool IsActive { get { return _state.IsBitSet(0); } set { _state.SetBit(0, value); } }
public bool IsFinished { get { return _state.IsBitSet(1); } set { _state.SetBit(1, value); } }
[FieldOffset(0)]
private byte _state;
[FieldOffset(1)]
public byte PrefabId;
[FieldOffset(2)]
public byte WeaponAction;
[FieldOffset(3)]
public int FireTick;
[FieldOffset(7)]
public Vector3 FirePosition;
[FieldOffset(19)]
public Vector3 FireVelocity;
[FieldOffset(31)]
public Vector3 ImpactPosition;
[FieldOffset(43)]
public Vector3 ImpactNormal;
// Custom projectile data
[FieldOffset(55)]
public HomingData Homing;
[FieldOffset(55)]
public KinematicData Kinematic;
public struct HomingData : INetworkStruct
{
public NetworkId Target;
public Vector3 TargetPosition; // Used for position prediction
public Vector3 Position;
public Vector3 Direction;
}
public struct KinematicData : INetworkStruct
{
public NetworkBool HasStopped;
public Vector3 FinishedPosition;
public int StartTick;
public byte BounceCount;
}
}
This structure is currently quite large due to the number of projectile features being demonstrated in this project. Also, since there are multiple different projectile behaviors being demonstrated which do not use the same data values, the data struct uses union of specific projectile data.
In actual practice, it is recommended to keep this ProjectileData
structure as minimalistic as possible. For most games fewer features will be needed than are demonstrated here. Check the commented simplified version of the ProjectileData
:
C#
public struct ProjectileData : INetworkStruct
{
public bool IsActive { get { return _state.IsBitSet(0); } set { _state.SetBit(0, value); } }
public bool IsFinished { get { return _state.IsBitSet(1); } set { _state.SetBit(1, value); } }
private byte _state;
public byte PrefabId;
public byte WeaponAction;
public int FireTick;
public Vector3 FirePosition;
public Vector3 FireVelocity;
[Networked, Accuracy(0.01f)]
public Vector3 ImpactPosition { get; set; }
[Networked, Accuracy(0.01f)]
public Vector3 ImpactNormal { get; set; }
}
StructLayout(LayoutKind.Explicit)
attribute) prevents having properties inside the struct and thus prevents usage of Networked
and Accuracy
attributes. See how without the union properties could be used and the Accuracy
attribute for ImpactPosition
and ImpactNormal
to save some bandwidth.
Projectiles
Projectile script (prefab) has two usages:
- Implements
GetFireData
andOnFixedUpdate
methods which are used to generate and manipulateProjectileData
. These methods are called fromFixedUpdateNetwork
on both the State Authority and the Input Authority, and are called directly on prefabs not on prefab instances.
C#
public abstract ProjectileData GetFireData(NetworkRunner runner, Vector3 firePosition, Vector3 fireDirection);
public abstract void OnFixedUpdate(ProjectileContext context, ref ProjectileData data);
NetworkBehaviour
and is not associated with a NetworkObject
. So all network data used in GetFiredData
or OnFixedUpdate
must be included in the ProjectileData
struct.
- Projectile instances are also used as a visual representation of the projectile. This allows for shared
FUN
(simulation) andRender
functionality such as movement code. Methods that are executed for projectile instances areActivate
,OnRender
andDeactivate
and are purely visual.
C#
public void Activate(ProjectileContext context, ref ProjectileData data)
{
PrefabId = data.PrefabId;
IsFinished = false;
OnActivated(context, ref data);
}
public void Deactivate(ProjectileContext context)
{
IsFinished = true;
OnDeactivated(context);
}
public virtual void OnRender(ProjectileContext context, ref ProjectileData data)
{
}
public virtual void Discard()
{
IsFinished = true;
}
It is also possible to override the Discard
method (e.g. to play dissolve effect) for custom behavior for missed predictions, when projectile wasn’t fired on the server.
Every projectile can spawn visual effects on impact as well as a new NetworkObject
(e.g. explosion).
Projectile Types
Fusion Projectiles include several projectile types which may be used in many common scenarios.
Hitscan Projectile
Raycast with specified max distance which fires immediately on fire input, and hits are immediately evaluated.
InstantHitscanProjectile
Visual trail can be displayed all the way from weapon barrel to target position. Trail disappears in time.
Example weapon: Sniper (disappearing trail), Shotgun (instant visuals)
FlyingHitscanProjectile
Hit is processed immediately but there is a dummy flying projectile that travels with specified speed to the target position.
Example weapon: Rifle
Kinematic Projectile
Projectile that travels over time, executing short raycasts between the previous position and new position every simulation tick.
SimpleKinematicProjectile
Kinematic projectile flying in a straight line.
Example weapon: Pulse Gun
AdvancedKinematicProjectile
Kinematic projectile that can be influenced by gravity and bounce off walls and other objects. Multiple projectile behaviors can be managed by this projectile type:
Falling projectile with explosion on touch
Example weapon: Pulse Gun (alternative fire - Grenade)Falling and bouncing projectile with explosion after some time
Example weapon: Rifle (alternative fire - Grenade)Straight bounce projectile
Example weapon: Ricochet Gun
HomingKinematicProjectile
Projectile that actively turns to hit the target. It can predict target position to hit moving targets. Possibility to specify what body part should be targeted, properties of the turn and target seek behavior.
Example weapon: Homing Gun (primary fire for fast homing projectile, secondary fire - Rocket - for slower retargeting projectile)
SprayProjectile
A projectile with very specific behavior that mimics a spray. To achieve this effect, the projectile can slow down, rise up or down, or change its diameter over time. When fired with higher dispersion, this projectile can represent the spray-like behavior of various weapons such as flamethrowers, acid guns, healing sprays, icing guns, and similar devices.
Example weapon: Flamethrower
Ray
Constant ray which does damage over time. Ray is implemented using the weapon component WeaponBeam
(more in Weapon Components section), it isn’t really a projectile in this project.
Example weapon: Laser Gun
Standalone Projectile
Projectile that can be spawned as a standalone network object. Should be used only for some special circumstances. See also Choosing The Right Approach section.
DataStandaloneProjectile
Wrapper for standard projectile functionality. Creates and updates ProjectileData
in a similar fashion as ProjectileManager
, just does it only for one projectile. Can be spawned predictively.
Example weapon: Ricochet Gun (alternative fire - Bouncing Drop)
Projectiles Interpolation
When firing projectiles in first person games one can choose to do the projectile calculations directly from the weapon barrel (realistic approach) or from center of the camera (standard approach).
Fusion Projectiles uses the more standard approach of firing from the camera center. This method however creates a discrepancy between the simulated path (originating at the camera), and the rendered path (originating from the weapon barrel). This is solved by interpolating projectile position over time from the weapon barrel to the real projectile path. Interpolation values (interpolation time, interpolation ease) can be setted up on all kinematic projectiles.
Weapons
Overview
Fusion Projectiles contain a small weapon handling system. It is a basic implementation which only handles weapon switching. For more elaborate usages like weapon drop, pickup, reload, recoil patterns, dynamic dispersion and other check Fusion BR sample.
To manage all kinds of weapons however, a convenient weapon action-component system is included.
Weapon Actions
A weapon action is a set of weapon components that represent a single weapon action. One weapon action is for example a standard fire and other action is an alternative fire. Weapons can have various weapon actions.
Weapon Components
The Weapon component represents part of the weapon that has its own logic and can be reused in different scenarios. Weapons can be assembled from multiple components to form the desired functionality. Fusion Projectiles comes with several weapon components:
WeaponMagazine
- Provides ammoWeaponTrigger
- Says when the weapon should fire (checks player input, controls weapon cadence)WeaponBarrel
- Fires the projectileWeaponFireEffect
- Shows muzzle, plays fire sound, applies knockback, starts camera shakeWeaponBeam
- Fires continuous beamWeaponSpray
- Fires spray projectiles
Weapon components are loosely coupled through WeaponDesires
struct.
WeaponBarrel
component is swapped for WeaponBeam
- magazine still provides ammo, trigger still tells when to fire, just the fire effect is different. Another example would be a minigun where it takes time for the rotating barrel to gain speed before fire starts - in this instance, a DelayedTrigger
component could be created to replace the standard trigger.
Shake Effect
ShakeEffect
utility is used for driving camera shake (GameplayScene/SceneCamera/Shaker
object in Game
scene) in response to weapon fire. The effect is triggered from the WeaponFireEffects
component, where position and rotation shake ranges can be set. The ShakeEffect
utility supports stacking of different camera shakes and continuous shaking (continuous fire).
Health and damage system
For hit synchronization an approach similar to projectile data is used. Hits are stored in a small networked circular buffer of Hit
structures.
C#
public struct Hit : INetworkStruct
{
public EHitAction Action;
public float Damage;
[Networked, Accuracy(0.01f)]
public Vector3 RelativePosition { get; set; }
[Networked, Accuracy(0.1f)]
public Vector3 Direction { get; set; }
public PlayerRef Instigator;
}
From the Hit
struct the appropriate hit reactions can be constructed - e.g. playing hit animation, showing hit direction in UI, showing hit confirmation and numbers to damage instigator, spawn of blood effect, etc.
RelativePosition
of the hit is stored in Hit
data instead of an absolute position. Since position of the proxies is interpolated between last two received ticks from the server, relative position is better to place hit effects such as blood splash correctly onto the body.
Gameplay Elements
Explosion
Simple spawned Network Object
that deals damage within a certain radius.
Damage Area
Damage is applied to all targets inside this area over time. Use it to test incoming damage.
Ragdoll
Agent death animation is for simplicity handled just by enabling rigidbody physics when the agent dies. Check the SimpleAgentRagdoll
script.
Turret
SimpleTurret
is used to spawn standalone projectiles in some intervals. There is no search for target logic nor rotation toward a target. Used for testing weapons outside of an Agent
prefab.
Game Core
GameManager
Handles joining and leaving of connected players and spawns Player
prefab. For more elaborate handling of connected players with saving of player data for reconnection refer to the Fusion BR sample.
Gameplay
Gameplay
is responsible for spawning/despawning Agent
prefabs. This base implementation acts as an endless deathmatch. Can be inherited from and extended to add different gameplay functionality such as proper deathmatch, capture the flag, elimination etc.
Pooling
In projects that involve projectiles, a large amount of object spawning is expected (projectiles, muzzle effect, impacts, hit effects,...). Since instantiation of a new object is an expensive operation everything in Fusion Projectiles is pooled.
There are two types of pool:
NetworkObjectPool
is used to pool theNetworkObject
s. For network object pool to work it needs to implement theINetworkObjectPool
interface and be assigned as a starting parameter whenNetworkRunner
is started (this happens inCustomNetworkDebugStart
script). For more information check out Network Object Pool section in the Fusion Manual.ObjectCache
is a genericGameObject
pool that can be used for non-networked objects such as impact effects. It has a handy feature to return objects with a specified delay.
Scene and SceneContext
Scene
handles scene specific functionality and services (SceneService
) such as UI, camera, music, minimap and others. Scene services are updated manually from here so they can be initialized, activated, deactivated and updated at specific times needed for the project.
SceneContext
provides safe access to common services or other information that are needed across the codebase without the use of statics. Scene context is passed to scene services automatically and is also assigned to networked objects in GameplayScene
and NetworkObjectPool
. Inherit from ContextBehaviour
and ContextSimulationBehaviour
instead of NetworkBehaviour
and SimulationBehaviour
if access to the SceneContext
is needed.
Expanding project
Adding new weapon
Create a new prefab or duplicate an existing weapon.
Make sure that there is at least one
WeaponAction
component and appropriate weapon components on the prefab.WeaponAction
and weapon components can be all on oneGameObject
(seeSniper
prefab) or in case of multiple weapon actions it needs to be placed in hierarchy (seeRifle
prefab).Set the
WeaponSlot
in theWeapon
component to specify which place it should occupy in the player weapons array - Slot 1 is the first visible weapon.Optionally assign weapon name and icon in the
Weapon
component.Assign weapon to
Initial Weapons
field in theWeapons
component on theAgent
prefab.
Adding new projectile
Create a new prefab or duplicate existing projectile. Make sure there is a component that inherits from
Projectile
.Assign projectile prefab to weapon prefab in
WeaponBarrel
component.
- Assign projectile prefab in the
ProjectileManager
on the controlling object (e.g. theAgent
prefab) to make sure it will spawn the projectile when fired.
Switch to third person
Fusion Projectiles is built as an FPS game but most of the functionality applies for TPS games as well. When firing in TPS games, first a ray needs to be cast from the camera to find the point at which the player is trying to shoot. The actual projectile cast will then be from a position on character (fixed position near character shoulder usually works well) to the point obtained from the camera cast. There are usually some tricks (like ignoring some collisions with second cast) involved so players won’t be hitting unwanted objects too much (corners issue). Check out Fusion BR for a TPS handling example.
3rd Party Assets
The Projectiles Sample includes several assets provided courtesy of their respective creators. The full packages can be acquired for your own projects at their respective site:
- Polygon Arsenal by Archanor VFX
IMPORTANT: To use them in a commercial project, it is required to purchase a license from the respective creators.
Back to top