This document is about: FUSION 2
SWITCH TO

Projectiles Advanced

Level 4

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.

This sample uses the HostMode topology.

Features

  • Handling of projectiles without individual NetworkObjects 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 Apr 26, 2024 Fusion Projectiles 2.0.0 Build 529

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).

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.

runner visibility controls

💡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.

first play

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.

photon dummy

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 manager

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:

  1. Implement GetFireData and OnFixedUpdate methods which are used to generate and manipulate projectile data (HitscanData or KinematicData). These methods are called from FixedUpdateNetwork 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.

  1. Projectile instances are also used as a visual representation of the projectile. This allows for shared FUN(simulation) and Render functionality such as movement code. Methods that are executed for projectile instances are Activate, Deactivate and Render 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

Also known as Raycast or Instant projectiles

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)

sniper
Hitscan projectile with trail from barrel to impact

FlyingHitscanProjectile
Hit is processed immediately but there is a dummy flying projectile that travels with specified speed to the target position.
Example weapon: Rifle

rifle
Hitscan projectile with flying dummy bullets

Kinematic Projectile

Also known as just 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

pulse gun
Kinematic projectile

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

ricochet gun
Ricocheting projectile of the 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)

homing gun
Homing projectiles of the Homing Gun

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

flamethrower
Spray projectiles of the Flamethrower

Ray

Also known as Beam or Laser

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

laser gun
Laser ray

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.

projectile interpolation
Projectile interpolation from the top-down view
kinematic projectile setup
Kinematic projectile setup

💡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 action
Primary weapon action on Pulse Gun with all necessary weapon components

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 ammo
  • WeaponTrigger - Says when the weapon should fire (checks player input, controls weapon cadence)
  • WeaponBarrel - Fires the projectile
  • WeaponFireEffect - Shows muzzle, plays fire sound, applies knockback, starts camera shake
  • WeaponBeam - Fires continuous beam
  • WeaponSpray - 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).

camera shake
Camera shake setup

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.

explosion

Damage Area

Damage is applied to all targets inside this area over time. Use it to test incoming damage.

damage area

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.

turret

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 the NetworkObjects. For network object pool to work it needs to implement the INetworkObjectProvider interface and be present on NetworkRunner game object. For more information check out Network Object Provider section in the Fusion Manual.

  • ObjectCache is a generic GameObject 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

  1. Create a new prefab or duplicate an existing weapon.

  2. 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 one GameObject (see Sniper prefab) or in case of multiple weapon actions it needs to be placed in hierarchy (see Rifle prefab).

  3. Set the WeaponSlot in the Weapon component to specify which place it should occupy in the player weapons array.

  4. Optionally assign weapon name and icon in the Weapon component.

  5. Assign weapon to Initial Weapons field in the Weapons component on the PlayerAgent prefab.

weapons inspector

Adding new projectile

  1. Create a new prefab or duplicate existing projectile. Make sure there is a component that inherits from Projectile.

  2. Assign projectile prefab to weapon prefab in WeaponBarrel component.

weaponbarrel inspector
  1. Assign projectile prefab in the HitscanProjectileBuffer or KinematicProjectileBuffer on the controlling object (e.g. the PlayerAgent 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 generic NetworkDataBuffer class with separate implementations for each projectile type — HitscanProjectileBuffer and KinematicProjectileBuffer.
  • 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:

IMPORTANT: To use Polygon Arsenal in a commercial project, it is required to purchase a license from the respective creator.

Back to top