Skip to main content

Weapon System

WeaponSystem is the ECS system responsible for keeping weapon entities attached to their owner, rotating them to match the aim direction, and spawning projectiles when the owner fires. It runs at priority: 50.

Responsibilities

ResponsibilityDescription
Position weaponMoves the weapon entity to the owner's position plus the configured offset each frame
Rotate weaponRotates the weapon to face the current InputComponent.aimDirection, mirroring the angle when facing left
Sync facingUpdates FacingComponent on the weapon to match the owner so the sprite flip is correct
Fire projectileSpawns a projectile entity via EntityFactory.makeProjectile when the owner is shooting and the cooldown has elapsed

Update Loop

WeaponSystem.update iterates over all entities that have all four of:

  • WeaponComponent
  • OwnerComponent
  • FacingComponent
  • TransformComponent

For each such weapon entity it:

  1. Resolves the owner — reads TransformComponent and InputComponent from the owner entity. Skips the weapon if either is missing.
  2. Mirrors the offset — flips the x-component of OwnerComponent.offset when the owner is facing left.
  3. Computes rotationatan2(aimDir.y, aimDir.x) when facing right; -atan2(aimDir.y, -aimDir.x) when facing left.
  4. Writes position & rotation back to the weapon's TransformComponent.
  5. Syncs facing — sets the weapon's FacingComponent to match the owner's.
  6. Checks fire input — if ownerInput.isShooting and the cooldown has elapsed (gameTime - lastFiredAt >= coolDownInterval), calls EntityFactory.makeProjectile and records lastFiredAt = gameTime.

Facing & Aiming Priority

Facing and aim direction feed into two separate decisions each frame. Both follow an explicit priority/fallback order.

1. Determining facingRight (the weapon should be on left or right side of the owner)

PrioritySourceCondition
1ownerFacing.facing from owner's FacingComponentOwner has a FacingComponent
2Default — facing rightOwner has no FacingComponent (ownerFacing is nil)

facingRight is true unless the owner's facing is explicitly .left:

let facingRight = ownerFacing?.facing != .left

This facingRight value is used for both mirroring the weapon's position offset and determining the rotation formula to apply to the aim vector. See Weapon Rotation and Fire Direction below.

2. Weapon rotation (which direction to aim)

facingRight is a subdivision criterion: it selects which rotation formula to apply to the aim vector.

PriorityValueCondition
1atan2(aimDir.y, aimDir.x) (facing right) or -atan2(aimDir.y, -aimDir.x) (facing left)simd_length(aimDir) > 0.001
20 (no rotation)Aim vector is near-zero

The left-facing formula negates both the result and the x-component of the input so that the angle is mirrored correctly against the flipped sprite xScale.

3. Fire direction (which direction to shoot)

PriorityValueCondition
1ownerInput.aimDirectionsimd_length_squared(aimDir) >= 0.001²
2(1, 0) or (-1, 0) based on facingRightAim vector is near-zero

This fallback ensures a projectile is never spawned with a zero-length velocity vector (see Fire Direction Fallback below).

Fire Direction Fallback

If the aim vector has near-zero length (the player isn't actively aiming), the system falls back to the owner's facing direction:

if simd_length_squared(fireDirection) < 0.001 * 0.001 {
fireDirection = facingRight ? SIMD2<Float>(1, 0) : SIMD2<Float>(-1, 0)
}

This prevents projectiles being spawned with a zero-length velocity vector (which would cause them to not move never get destroyed).


Cooldown

The cooldown is tracked using an internal gameTime: Float accumulator (sum of all deltaTime values) compared against WeaponComponent.lastFiredAt.

let isReadyToFire = (gameTime - weaponComponent.lastFiredAt) >= Float(weaponComponent.coolDownInterval)

The default handgun coolDownInterval is 0.2 s.

Usage Example

// Weapon entity is created once via EntityFactory.makeWeapon.
// WeaponSystem handles positioning and firing automatically every frame.
let weapon = EntityFactory.makeWeapon(in: world, ownedBy: playerEntity, offset: SIMD2<Float>(20, -5))

Dependencies

DependencyRole
WeaponComponentStores fire stats (cooldown, mana cost, lastFiredAt)
OwnerComponentProvides the owner entity reference and positional offset
FacingComponentDetermines left/right mirroring for position and rotation
InputComponentSupplies aimDirection and isShooting from the owner
TransformComponentRead from the owner; written to the weapon entity
EntityFactory.makeProjectileCreates projectile entities when the weapon fires