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
- Smooth player movement and camera rotation using the Simple KCC addon
- Predictive projectiles "spawning" using custom Network Object Buffer
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 | |
---|---|---|---|
2.0.0 | May 20, 2024 | Fusion Projectiles 2.0.0 Build 555 |
Starting The Game
Debug Start
Start a game by opening and playing the Playground
scene (Scenes/Playground
). Choose the mode in which the game should be started.
Multipeer
For testing how projectiles behave on proxies, it is highly recommended to use the multi-peer mode. To enable multipeer mode, change Peer Mode
to Multiple
in the NetworkProjectConfig
asset (Photon/Resources/NetworkProjectConfig
). Upon starting the scene with multipeer mode enabled, choose Start Host
, and the game will initiate with the specified number of clients (2 by default).
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.
💡Note: For multipeer mode to work properly it is essential to avoid referencing scene objects through static fields (access to UI, camera rig, player data etc.) but avoiding statics is a good and recommended practice in general as well. Check the SceneContext section for one solution to how common objects can be accessed throughout your codebase.
Controls
Use W
, S
, A
, D
for movement, Mouse1
for fire and Mouse2
for alternative fire if available. Weapons can be switched 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 player 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 Agent (PlayerAgent
script, PlayerAgent
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.
Network Data Buffer
NetworkDataBuffer
is the cornerstone of the Projectiles Advanced sample. It manages the projectile data buffer and handles the creation, update, and deletion of the visual representations of projectiles. The sample includes two buffer versions (child classes) based on the type of projectiles it can handle: HitscanProjectileBuffer
and KinematicProjectileBuffer
. Projectile buffer components are on the controlling game object (e.g., Agent
, Turret
) and are responsible for updating projectiles for all weapons owned by that object.
💡Note: Difference between hitscan and kinematic projectiles is explained in the Projectiles Types section.
Projectile buffers require projectile prefabs array to correctly pair projectile data to specific prefab. For simplicity projectile buffers have references to prefabs directly in the inspector.
💡Note: Better solution of accessing prefabs array will be game specific but it could be for example a scriptable object with settings data.
Projectile buffers store networked projectile data that are used to calculate trajectories and other behaviors associated with projectiles. Each data instance represents a single projectile fired by the owner (player, turret).
C#
public struct HitscanData : INetworkStruct
{
public byte PrefabIndex;
public byte BarrelIndex;
public Vector3Compressed FirePosition;
public Vector3Compressed FireDirection;
public Vector3Compressed ImpactPosition;
public Vector3Compressed ImpactNormal;
}
C#
[StructLayout(LayoutKind.Explicit)]
public struct KinematicData : INetworkStruct
{
public bool IsFinished { get { return _state.IsBitSet(0); } set { _state.SetBit(0, value); } }
public bool HasStopped { get { return _state.IsBitSet(1); } set { _state.SetBit(1, value); } }
[FieldOffset(0)]
private byte _state;
[FieldOffset(1)]
public byte PrefabIndex;
[FieldOffset(2)]
public byte BarrelIndex;
[FieldOffset(3)]
public int FireTick;
[FieldOffset(7)]
public Vector3Compressed Position;
[FieldOffset(19)]
public Vector3Compressed Velocity;
[FieldOffset(31)]
public Vector3Compressed ImpactPosition;
[FieldOffset(43)]
public Vector3Compressed ImpactNormal;
// Custom projectile data
[FieldOffset(55)]
public AdvancedData Advanced;
[FieldOffset(55)]
public SprayData Spray;
[FieldOffset(55)]
public HomingData Homing;
public struct AdvancedData : INetworkStruct
{
public int MoveStartTick;
public byte BounceCount;
}
public struct SprayData : INetworkStruct
{
public Vector3 InheritedVelocity;
}
public struct HomingData : INetworkStruct
{
public NetworkId Target;
public Vector3Compressed TargetPosition; // Used for position prediction
}
}
Hitscan projectiles require less networked data and are therefore more efficient. The KinematicData
struct, used for kinematic projectiles in this sample, is currently quite large due to the variety of projectile features demonstrated in this project. As multiple different projectile behaviors are showcased, each not using the same data values, the KinematicData
struct utilizes a union of specific projectile data (notice how AdvancedData
, SprayData
and HomingData
overlap).
In actual practice, it is recommended to keep the projectile data structure as minimalistic as possible.
Projectiles
Main projectile scripts (HitscanProjectile
and KinematicProjectile
) have two usages:
- Implement
GetFireData
andOnFixedUpdate
methods which are used to generate and manipulate projectile data (HitscanData
orKinematicData
). 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 virtual KinematicData GetFireData(Vector3 firePosition, Vector3 fireDirection) {}
public virtual void OnFixedUpdate(ref KinematicData data) {}
💡Note: Projectile is not a NetworkBehaviour
and is not associated with a NetworkObject
. So all network data used in GetFiredData
or OnFixedUpdate
must be included in the projectile data 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
,Deactivate
andRender
and are purely visual.
C#
public virtual void Activate(ref KinematicData data) {}
public virtual void Deactivate() {}
public virtual void Render(ref KinematicData data, ref KinematicData fromData, float alpha)
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
Hitscan projectile with an instant hit effect. A visual trail can be displayed extending from the weapon barrel to the target position. The trail fades over 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 the Choosing The Right Approach section in Projectile Essentials documentation.
StandaloneProjectile
Wrapper for standard projectile functionality. Creates and updates KinematicData
in a similar fashion as KinematicProjectileBuffer
, just does it only for one projectile. Can be spawned in advance using NetworkObjectBuffer
to mitigate any spawn delay when network conditions are not ideal.
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).
💡Note: Realistic approach is used in a few (hard core) games. It makes firing feel more realistic, but also less stable (fire position is dependent on player animations) and prone to unwanted hits (corner issue) because the weapon is offset from the camera.
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 y up on all kinematic projectiles.
💡Note: Projectile interpolation is needed only for kinematic projectiles. For hitscan projectiles no interpolation is needed because we already know where the projectile will impact so the dummy flying visuals can be moving directly to that point.
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
💡Note: When building various weapons the idea is to just swap one or more components while reusing the others. One example is Laser Gun where the 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 (SceneContext/SceneCamera/Shaker
object in SceneContext
prefab) 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;
public Vector3Compressed RelativePosition;
public Vector3Compressed Direction;
public PlayerRef Instigator;
public NetworkBool IsFatal;
}
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.
💡Note: Notice how 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.
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 PlayerAgent
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 PlayerAgent
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 theINetworkObjectProvider
interface and be present onNetworkRunner
game object. For more information check out Network Object Provider section in the Fusion Manual.ObjectCache
is a genericGameObject
pool that can be used for non-networked objects such as projectiles and impact effects. It has a handy feature to return objects with a specified delay.
Scene Context
SceneContext
provides safe access to common game objects or other information that are needed across the codebase without the use of statics. Scene context is assigned to all IContextBehaviour
components found in the scene after scene load is done (GameManager.OnSceneLoadDone
) and is also assigned to network behaviours (ContextBehaviour
) in NetworkObjectPool
. Inherit from ContextBehaviour
instead of NetworkBehaviour
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.Optionally assign weapon name and icon in the
Weapon
component.Assign weapon to
Initial Weapons
field in theWeapons
component on thePlayerAgent
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
HitscanProjectileBuffer
orKinematicProjectileBuffer
on the controlling object (e.g. thePlayerAgent
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.
Transition from Fusion 1
The Projectiles Advanced project remains available for Fusion 1 users. However, the Fusion 2 version introduces several substantial changes beneath the surface. Below is a summary of what has changed:
- Simplified player-related classes and input processing
- Simplified weapon component system
- Replaced the KCC addon from Fusion 1 with the Simple KCC addon
- Removed
ProjectileManager
. Instead, hitscan projectiles and kinematic projectiles are now managed separately, enhancing network efficiency. This is achieved through the genericNetworkDataBuffer
class with separate implementations for each projectile type —HitscanProjectileBuffer
andKinematicProjectileBuffer
. - Projectile visuals can persist longer than the data in network buffers, which is a notable advancement from the Fusion 1 version. This enhancement allows the use of smaller data buffers while supporting extended-duration visuals such as trails, impacts, and environmental effects.
- Spawn prediction is no longer supported in Fusion 2.
NetworkObjectBuffer
that pre-spawns selected number of objects in advance is used instead. - Added a new Deathmatch scene
- Homing projectiles were improved
- UI and scene services systems were removed
- Game scene was renamed to Playground
- + Many smaller improvements and fixes
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
- Blaster Kit by Kenney
- Prototype Bits by Kay Lousberg
IMPORTANT: To use Polygon Arsenal in a commercial project, it is required to purchase a license from the respective creator.
Back to top