This document is about: QUANTUM 3
SWITCH TO

Implementing Different Weapons with Data-Driven Polymorphism

This page accompanies the Implement Different Weapons in a Multiplayer Game | Photon Quantum (Data-Driven Polymorphism) video and explains how to implement multiple weapon types in Quantum by using data assets and polymorphism.

Use this page when you want to extend a Quantum project with different weapon behaviors, such as machine guns, bazookas, shotguns, or other weapons that share common configuration but differ in firing logic.

Implement Different Weapons in a Multiplayer Game | Photon Quantum (Data-Driven Polymorphism)

Overview

Quantum components are structs. Structs are value types and do not support inheritance. This is useful for performance and deterministic ECS-style simulation, but it means that classic object-oriented inheritance should not be placed directly in components.

For gameplay cases where inheritance or polymorphism is useful, Quantum data assets can be used instead.

Quantum data assets are C# classes. In Unity projects, they are also ScriptableObjects. They act as immutable data containers during runtime and can safely be referenced by the deterministic simulation.

This video shows how to move weapon behavior from a system into WeaponData, then create a derived ShotgunWeaponData class that overrides bullet spawning logic.

The result is a data-driven weapon setup where different weapon assets can share common behavior but override specific parts of the implementation.

Video Timeline

Time Section
00:00 Introduction
00:17 Data assets and polymorphism
01:45 Asset objects as data containers
02:40 Offsets in guns in Quantum
03:14 Characters and weapon inventory
03:51 Asset objects in code
04:20 Weapon system implementation
04:50 Extracting system methods to WeaponData
05:53 Creating ShotgunWeaponData and overriding SpawnBullet
07:35 Creating the ShotgunWeaponData asset
08:35 Updating the character weapon inventory

Data Assets and Polymorphism

Quantum data assets are C# classes that inherit from AssetObject.

In a Unity context, these assets are represented as ScriptableObjects. During runtime, they act as immutable data containers.

This means:

  • asset data can be read during runtime;
  • asset references can be switched safely during runtime;
  • asset objects are rollback-compatible;
  • asset data must not be changed during runtime.

Changing data inside an asset during simulation breaks determinism. Treat asset objects as read-only configuration.

Why Use Assets for Polymorphism?

Quantum components are structs. Because structs do not support inheritance, components are not the right place for polymorphic behavior.

Data assets are classes, so they can use inheritance and virtual methods.

This makes them a good place for behavior that varies by configuration type.

For example:

  • WeaponData can define default weapon behavior.
  • ShotgunWeaponData can inherit from WeaponData.
  • ShotgunWeaponData can override only the method that needs different behavior.

This keeps entity components simple while allowing weapon behavior to remain extensible.

Example Goal

The video expands the Quantum 2D Platformer sample.

The existing weapon setup supports weapons that spawn a single bullet while the fire button is pressed and the weapon cooldown has passed.

The goal is to add a shotgun-style weapon.

Unlike the default weapon, the shotgun should spawn multiple bullets at once and spread them across an angle.

Asset Objects as Data Containers

In the sample project, weapon data assets are stored under:

QuantumUser/Resources

This folder contains prefabs and several ScriptableObjects.

One relevant asset is:

WeaponDataMachineGun

This asset stores the configuration for the machine gun used in the game.

A Quantum asset has a deterministic GUID. The GUID is generated when the asset is created and is used by Quantum to reference the asset deterministically.

Creating Quantum Asset Objects

To create a new Quantum asset:

Right Click in Resources > Create > Quantum > Asset

If the selected asset class is non-abstract, Unity can create an instance of it.

In the original setup, WeaponData is a non-abstract class. This means it can be selected directly when creating a new asset.

Later in the video, ShotgunWeaponData is created as a class that inherits from WeaponData. Unity then exposes it as a selectable derived asset type.

WeaponData Fields

The machine gun asset contains configuration fields used by the weapon system.

Common fields include:

Field Purpose
FireRate Defines how many shots can be fired per second.
ShootForce Defines the bullet speed or applied force.
FireSpotOffset Defines where the bullet should spawn relative to the character.
PositionOffset Defines additional spawn positioning data.
BulletData References another asset that describes the bullet to spawn.

Weapon Offsets in Quantum

Quantum ECS does not use Unity-style GameObject hierarchy inside the deterministic simulation.

Because there is no transform hierarchy for simulation entities, weapon muzzle positions and spawn positions need to be calculated explicitly.

That is why weapon assets use offset fields such as:

  • fire spot offset;
  • position offset.

These offsets define where bullets should be instantiated relative to the character or weapon direction.

BulletData Assets

BulletData is also an asset object.

It stores bullet-specific configuration, such as:

  • which bullet entity should be created;
  • damage;
  • range;
  • other bullet behavior settings.

This means both weapons and bullets can use data-driven configuration.

The video also notes that bullets themselves can use data-driven polymorphism.

Characters and Weapon Inventory

The sample contains multiple character prefabs.

There is a base character and prefab variants. The variants share common components, including the weapon inventory.

The relevant component is:

QPrototypeWeaponInventory

The weapon inventory contains weapon slots. Each slot stores runtime state while referencing immutable weapon data from a WeaponData asset.

This allows the character to switch weapons during gameplay.

In the sample, the player can switch between weapon indexes 0 and 1 with right-click.

Asset Objects in Code

Open the WeaponData class.

WeaponData inherits from:

c#

AssetObject

All Quantum data asset classes should inherit from AssetObject.

Some fields may be wrapped in:

c#

#if QUANTUM_UNITY
#endif

Fields inside this block are only available in Unity and should not be accessed from the Quantum simulation.

Use this pattern for Unity-only references or editor-specific data that should not be part of the deterministic simulation.

Original Weapon System Structure

The existing weapon system uses a filter that includes:

  • Entity;
  • PlayerLink;
  • Status;
  • WeaponInventory.

The update loop calls methods that:

  1. update weapon recharge;
  2. update weapon fire;
  3. spawn bullets.

Before refactoring, this behavior lives in the system itself.

Since the project now needs different weapon types, the weapon-specific behavior should move into WeaponData.

Moving Weapon Logic into WeaponData

The main refactoring step is to move weapon-related methods from the weapon system into WeaponData.

This enables data-driven polymorphism.

Instead of the system deciding exactly how every weapon fires, the system retrieves the current weapon asset and calls methods on it.

The asset then determines the weapon-specific behavior.

Method Access Modifiers

Before moving the methods, update their access modifiers.

Methods called from the system or overridden by derived weapon classes need to be accessible and virtual.

Use:

c#

public virtual unsafe

for methods that are called externally and may use pointers.

For the bullet spawning method, use:

c#

protected virtual

This allows derived weapon data classes to override bullet spawning while keeping the method scoped to the class hierarchy.

Updating Method Parameters

After moving the methods into WeaponData, update the parameter type from the local filter type to:

c#

WeaponSystem.Filter

Because the methods now live outside the system class, the filter type needs to be referenced through the system type.

Also remove redundant weaponData retrieval inside WeaponData, because the code is already executing on the current weapon data instance.

Retrieving WeaponData in the System

The weapon system should retrieve the currently selected weapon data from the weapon inventory.

Example:

c#

var weaponData = frame.FindAsset<WeaponData>(
    filter.WeaponInventory->Weapons[
        filter.WeaponInventory->CurrentWeaponIndex
    ].WeaponData
);

The system can then call the relevant methods on weaponData.

This is the key step that enables polymorphic behavior. If the referenced asset is a ShotgunWeaponData, the overridden method is called.

Creating ShotgunWeaponData

Create a new class in the simulation folder:

ShotgunWeaponData

Make it inherit from:

c#

WeaponData

The shotgun should add its own configuration fields.

Example fields:

c#

public int NumberOfBullets = 3;
public FP SpreadAngle = 22;

NumberOfBullets defines how many bullets are spawned per shot.

SpreadAngle defines how far the bullets spread.

The video uses 22 as the default spread angle and 3 as the default number of bullets.

Overriding SpawnBullet

The default weapon behavior can keep using the existing recharge and fire logic.

For the shotgun, only bullet spawning needs to change.

Override:

c#

SpawnBullet

The shotgun implementation wraps bullet creation and initialization in a loop.

The character transform can be retrieved once before the loop because it is shared by all spawned bullets.

The spread angle should be converted from degrees to radians because Quantum rotation calculations use radians internally.

Calculating Bullet Spread

Each spawned bullet receives a different direction.

The final direction is calculated by rotating the base direction across the configured spread range.

Example:

c#

bulletFields->Direction = FPVector2.Rotate(
    direction * ShootForce,
    FPMath.Lerp(
        -spreadAngleRad,
        spreadAngleRad,
        (FP)i / (NumberOfBullets - 1)
    )
);

This distributes bullets evenly between -spreadAngleRad and spreadAngleRad.

For example, with three bullets:

  • the first bullet rotates toward the negative spread angle;
  • the second bullet stays near the center;
  • the third bullet rotates toward the positive spread angle.

Creating a ShotgunWeaponData Asset

After implementing the class, return to Unity.

Create a new Quantum asset:

Right Click in Resources > Create > Quantum > Asset

Under WeaponData, a dropdown arrow appears because a derived class now exists.

Select:

ShotgunWeaponData

This creates a new asset using the default values from the class.

Faster Asset Creation by Duplicating

There is also a faster way to create a derived asset while preserving values from an existing asset.

To create the shotgun asset from the machine gun asset:

  1. Duplicate WeaponDataMachineGun.
  2. Rename the duplicate to WeaponDataShotgun.
  3. Open the three-dot menu.
  4. Switch the Inspector to Debug.
  5. Change the script from WeaponData to ShotgunWeaponData.
  6. Switch the Inspector back to Normal.

This keeps the existing values and changes only the asset class.

Then adjust the shotgun values.

For example:

Field Example Change
FireRate Reduce from 15 to 5.
BulletData Assign a shotgun-specific bullet data asset.

Creating Shotgun BulletData

To create shotgun-specific bullet data:

  1. Open the BulletData field.
  2. Duplicate the existing bullet data asset.
  3. Rename it to BulletDataShotgun.
  4. Adjust values for shotgun behavior.

Example changes:

Field Example Value
Damage 10
Range 15

Then drag the BulletDataShotgun asset into the shotgun weapon data asset.

Updating the Character Weapon Inventory

To give the shotgun to one character:

  1. Select the character prefab or prefab variant.
  2. Open its QPrototypeWeaponInventory component.
  3. Expand Weapons.
  4. Select the desired element, for example Element 0.
  5. Change WeaponData from WeaponDataMachineGun to WeaponDataShotgun.

Now that character uses the shotgun asset instead of the machine gun asset.

Other characters can keep the machine gun or use different weapon assets.

Testing Locally

Run the game locally and select the updated character.

The character should now fire multiple bullets at once in a spread pattern.

This confirms that:

  • the weapon system correctly retrieves the current weapon asset;
  • the shotgun asset is used through the base WeaponData reference;
  • the overridden SpawnBullet method is executed;
  • weapon behavior is driven by the selected asset.

Key Concepts

Concept Description
Data asset A Quantum AssetObject used as immutable runtime configuration.
Asset object Rollback-compatible asset reference that can be safely used in simulation.
Immutable runtime data Asset data may be read during runtime but must not be modified.
Polymorphism A base class reference can execute overridden behavior from a derived class.
WeaponData Base asset class for shared weapon configuration and behavior.
ShotgunWeaponData Derived weapon asset class that overrides bullet spawning.
BulletData Asset object containing bullet configuration.
WeaponInventory Component that stores weapon slots and the current weapon index.
FindAsset<T> Used to resolve an asset reference in simulation code.
SpawnBullet Weapon behavior method overridden to customize projectile spawning.

Best Practices

Use this checklist when implementing weapon variants with data-driven polymorphism:

  • Keep mutable runtime state in components.
  • Keep immutable configuration in data assets.
  • Do not modify asset data during simulation runtime.
  • Use asset inheritance for behavior that differs between weapon types.
  • Use components for entity state, not inheritance-based behavior.
  • Move weapon-specific logic from systems into WeaponData when behavior needs to vary per weapon.
  • Mark methods as virtual when derived assets need to override them.
  • Keep shared logic in the base class.
  • Override only the behavior that actually changes.
  • Use FindAsset<T> to retrieve the currently selected asset from the simulation.
  • Use explicit offsets for spawn positions instead of relying on Unity transform hierarchy.
  • Create separate BulletData assets when bullets need different damage, range, visuals, or behavior.
  • Test locally before combining multiple weapon types in online sessions.

Common Pitfalls

Avoid these mistakes when working with Quantum data assets:

Pitfall Why It Is a Problem
Modifying asset fields during runtime Breaks determinism. Assets must be treated as immutable.
Putting inheritance into components Quantum components are structs and do not support inheritance.
Forgetting to retrieve the current weapon asset The system will not call the correct polymorphic behavior.
Keeping all weapon behavior inside the system Makes weapon variants harder to extend and maintain.
Using Unity hierarchy assumptions in simulation code Quantum simulation does not use Unity transform hierarchy.
Forgetting to assign the new asset in the inventory The character will keep using the previous weapon data.

Summary

Quantum data assets are a practical place to use inheritance and polymorphism because they are classes, while Quantum components are structs.

In this video, weapon behavior is refactored from the weapon system into WeaponData. The system retrieves the currently selected weapon asset and calls methods on it. A new ShotgunWeaponData class inherits from WeaponData and overrides bullet spawning to create multiple bullets in a spread pattern.

This allows weapon behavior to be configured and extended through assets while keeping the simulation deterministic and ECS-compatible.

Back to top