Projectiles in Warman are everywhere. Fireballs, arrows, chain lightning bolts, wind blasts, enemy ranged attacks, trap darts. They all run through the same system: a pooled ProjectileManager that handles spawning, movement, collision, and cleanup.
The Projectile Lifecycle
Every projectile type is registered in a global array, indexed by a ProjectileID enum. When the game needs a fireball, it asks the manager for a projectile of that ID. The manager checks its per-type pool first. If there is an idle instance, it gets reactivated. If not, a new one is instantiated. Either way, the projectile enters the active list and starts ticking.
Each tick, the manager updates all active projectiles: move them along their path, check for collisions against valid targets in the room, and handle despawn timers. Projectiles that finish (either by hitting something, expiring, or colliding with a wall) get returned to the pool for reuse.
Path Manipulators
The base class defines two methods: SetupStorage (called once on spawn to initialise state) and ManipulatePath (called every tick to update position). State is stored in float and vector arrays on a wrapper object, not on the manipulator itself, so a single ScriptableObject instance can be shared across thousands of projectiles without conflicts.
The built-in manipulators include DirectionPathManipulator (straight line, scaled by the owner's projectile speed stat), SinePathManipulator (forward motion combined with a lateral sine oscillation, creating a weaving pattern), and CirclePathManipulator (orbital motion around a center point that can optionally track the owner, with configurable expansion speed). The sine and circle variants use simple trig. No physics, no raycasts, just position math.
Damage and Collision
Each projectile carries a ProjectileCreateData asset that defines its damage group, pierce amount, despawn time, and optional on-impact effects. Collision uses the custom WarmanPhysics radius checks. No Unity physics involved. The projectile's collider radius is checked against all valid DamageTarget entities in the current room, filtered by height difference (a projectile on a cliff won't hit units on the ground below).
Pierce is simple: each projectile tracks how many targets it has hit. If the hit count reaches the pierce limit, the projectile finishes. A pierce amount of 255 (byte max) means infinite pierce. The projectile flies through everything until its timer expires.
Projectile Groups
On-Despawn Skills
One useful trick: projectiles can cast a skill when they despawn. A SkillOnDespawn reference on the create data fires when the projectile expires or hits a wall. This creates things like exploding arrows (projectile hits wall, casts an area damage skill at the impact point), splitting bolts (projectile expires, casts a new multi-projectile skill), or lingering effects (projectile despawns, creates a ground effect at its final position). The on-despawn skill flows through the normal skill casting system, so it gets all the same modifier interactions and proc chain opportunities.