[Overview Video coming soon]
The Projectile System was built after Lightfall 2D in anticipation of being used in later 3D projects which need weapons and equipment with advanced ballistics. It was built from the ground up with no 3rd party assets.
By using “raycasts over time”, the Projectile System offers a “hitscan-like” ballistic movement allowing for far greater projectile “velocity with accuracy” than Unity’s rigidbody physics system. This system does hitscan as well, simply crank up the velocity of the projectile and set it’s “lifetime” to 0 (which is 1 frame).
The Projectile System is data-oriented, lightweight, and uses the Unity Addressable System.
Data Oriented: The Projectile System uses a data processor to manage and manipulate projectile data every fixed update. Each weapon has a list of the projectiles it fired, and each projectile is nothing more than a class wrapping position, velocity, and extra behaviors such as ricochet, penetration, and drag/ mass for slowdown and arc. Every fixed update the weapon, which extends the data processor class, iterates through all its projectiles and fires a raycast from the projectile’s current position to its new position, handling collision and all extra behaviors that are triggered.
Lightweight: Since each projectile is just a collection of data, it is performant by default, able to handle hundreds of weapons and thousands of behaviorally and graphically complex projectiles. For graphics, I use Unity’s Shuriken Particle system. Each weapon/ projectile data processor has reference to a particle system which handles visualizing the projectile. When a projectile is emitted, so is a particle. The projectile data then gets a reference to that particle’s index, and the data processor mirrors the direction, position and lifetime of the projectile with the particle. This is a highly performant way to visualize the projectiles. An option to mirror the projectile with a proper gameobject is an option, which can be useful if an enemy launches a smaller enemy, among other scenarios.
Unity Addressable System: The Projectile System utilizes Addressables to instantiate all its audio and graphic pools, allowing for flexible resource handling, and enforcing asynchronous performance for all audio and graphics. This allows a weapon to emit projectiles that behave normally even if the associated graphics and audio have not yet been loaded.
Surface system: The Projectile System includes a surface and material system. Objects of a certain type have a “material” script attached to them. The Material script defines a density and a surface type, which is a scriptable object “tag” that simply says “sand”, “metal”, etc. The surface type is used when figuring out impact sounds and graphics, such as “laser on wood” or “bullet on metal”, and the density is used when calculating drag from liquid or velocity change after penetration. An “Atmosphere” scriptable object is provided to weapons for calculating the effects of air drag and gravity on a trajectory. Trigger “Zones” can be set up which will change the atmospheric conditions of a projectile on contact with and within the zone. eg. water.
Local timescale: The projectile system also has a local timescale system, allowing each weapon and projectile to have its timescale changed. This allows for projectiles to make contact with “slowdown-zones” and travel slower, or otherwise have their speed changed without affecting their velocity.
Improvements: I built the foundations for the Projectile System over the course of three weeks, then rebuilt the foundations a second time and continued to add features and improvements over the course of three months. I intend to rebuild the system again because I have since improved my ability to write SOLID code and write in an event oriented pattern. I would also like to make some foundational improvements to how projectile “modules” such as seeking, ricochet, and gravity, are added and handled.
- Weapons will be provided a reference to a Scriptable Object “Projectile Data Processor” which will handle managing and manipulating projectile data. This will allow for a property injection pattern, where different processors can be created and provided to different types of weapons. This will also eliminate the issue of “when the gun disappears, so do the projectiles it was managing”
- Projectile Modules will be separate scripts, and will be able to be added as needed to the Projectile behavior Scriptable Object.
- Use Events for behavior triggers like OnHit, OnPenetrate, OnProjectileDeath, rather than providing abstract methods to override.
- Since using Unity Addressables overcomplicated pooling and graphics, and because Unity Addressables has improved how it can be interfaced with, I would remove Addressable altogether and create a separate Addressable Projectile Integration to interface between the Addressable system and the Projectile System.
- See where Unity’s Data Oriented Technology Stack, specifically the Entity Component System is at with regard to usability and stability. If it seems stable and developer friendly, then I will utilize ECS for massive performance improvements, with the possibility of handling hundreds of thousands of projectiles.