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.
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:
WeaponDatacan define default weapon behavior.ShotgunWeaponDatacan inherit fromWeaponData.ShotgunWeaponDatacan 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:
- update weapon recharge;
- update weapon fire;
- 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:
- Duplicate
WeaponDataMachineGun. - Rename the duplicate to
WeaponDataShotgun. - Open the three-dot menu.
- Switch the Inspector to
Debug. - Change the script from
WeaponDatatoShotgunWeaponData. - 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:
- Open the
BulletDatafield. - Duplicate the existing bullet data asset.
- Rename it to
BulletDataShotgun. - 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:
- Select the character prefab or prefab variant.
- Open its
QPrototypeWeaponInventorycomponent. - Expand
Weapons. - Select the desired element, for example
Element 0. - Change
WeaponDatafromWeaponDataMachineGuntoWeaponDataShotgun.
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
WeaponDatareference; - the overridden
SpawnBulletmethod 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
WeaponDatawhen behavior needs to vary per weapon. - Mark methods as
virtualwhen 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
BulletDataassets 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- Overview
- Video Timeline
- Data Assets and Polymorphism
- Example Goal
- Asset Objects as Data Containers
- Creating Quantum Asset Objects
- WeaponData Fields
- Weapon Offsets in Quantum
- BulletData Assets
- Characters and Weapon Inventory
- Asset Objects in Code
- Original Weapon System Structure
- Moving Weapon Logic into WeaponData
- Updating Method Parameters
- Retrieving WeaponData in the System
- Creating ShotgunWeaponData
- Overriding SpawnBullet
- Calculating Bullet Spread
- Creating a ShotgunWeaponData Asset
- Faster Asset Creation by Duplicating
- Creating Shotgun BulletData
- Updating the Character Weapon Inventory
- Testing Locally
- Key Concepts
- Best Practices
- Common Pitfalls
- Summary