Phaser AE

Light gun

Pro packv3.0.0-3Arcade

Aiming and shooting for gun games: a crosshair, triggers, weapons, magazines and reloads, hit regions, shields, hostages and accuracy. It knows nothing about what the targets are, so the same gun serves a 2D shooting gallery and a 3D rail shooter.

This is a Pro pack. Upgrade to Phaser Pro to use it in your games.See Phaser Pro →

Released October 14, 2026 · updated October 20, 2026

Light gun: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

lightGun

function lightGun(): Pack<{ lightGun: typeof api }>

Install the light gun API.

Helpers

createGun

function createGun(def: GunDef): Gun

Build a gun.

emptyWorld

const emptyWorld: HitWorld

The empty world: every shot misses.

frontToBack

function frontToBack(targets: readonly ScreenTarget[]): ScreenTarget[]

Targets sorted front to back: higher z first; ties, later in the list first.

GUN_STEP

const GUN_STEP: number

The default fixed step: 1/60 s.

layeredWorld

function layeredWorld(...worlds: HitWorld[]): HitWorld

Combine worlds: candidates from earlier worlds come first (an overlay in front of a scene).

rayWorld

function rayWorld(def: RayWorldDef): HitWorld

Wrap a ray cast (3D picking) as a hit world; candidates are re-sorted by distance when given.

regionContains

function regionContains(t: ScreenTarget, r: ScreenRegion, x: number, y: number): boolean

Is a screen point inside a region of a target.

regionMeetsCircle

function regionMeetsCircle(t: ScreenTarget, r: ScreenRegion, x: number, y: number, radius: number): boolean

Does a region of a target meet a circle.

screenWorld

function screenWorld(targets: readonly ScreenTarget[] | (() => readonly ScreenTarget[])): HitWorld

A hit world over 2D screen targets. The list is read at query time, so it can be a live array.

validateGun

function validateGun(def: GunDef): void

Validate a gun definition; throws with a clear message.

Types

AimDef

interface AimDef

How the crosshair follows its input.

  • smoothing?: number — Time constant in seconds of an exponential follow toward the input (0, the default, is exact: a positional gun).
  • stickSpeed?: number — Crosshair speed in pixels per second at full stick deflection (stick or keyboard aiming). Default 240.
  • clamp?: boolean — Keep the crosshair inside the view (off-screen detection still uses the raw input). Default true.
  • start?: { x: number; y: number } — Where the crosshair starts (default the view centre).

BoostState

interface BoostState

A temporary weapon boost (the Super Machine Gun).

  • weapon: string — Weapon id.
  • rate: number — Fire-rate multiplier.
  • free: boolean — Shots do not use ammunition.
  • left: number — Seconds left.

ComboDef

interface ComboDef

Streak (combo) rules.

  • window?: number — Seconds between hostile hits that keep a streak going (default unlimited: only a miss ends it).

Gun

interface Gun

A running gun.

  • readonly def: GunDef — The definition.
  • readonly state: GunState — Live state (plain data).
  • tick(seconds: number, input: GunInput, world?: HitWorld): void — Advance by seconds in fixed steps; trigger presses shorter than a step are latched, never lost.
  • step(input: GunInput, world?: HitWorld): void — Advance one fixed step.
  • drain(): GunEvent[] — Take the events since the last drain.
  • weapon(id: string): Readonly<WeaponDef> — A weapon's definition.
  • fireAt(x: number, y: number, weapon?: string, world?: HitWorld, opts?: { free?: boolean }): boolean — Fire a weapon at a point now (ignores the trigger and cooldown; uses ammunition unless free).
  • reload(weapon?: string): boolean — Start a reload of the primary (or a named) weapon, as the reload button does.
  • give(weapon: string, supply: Supply): void — Add ammunition.
  • setAmmo(weapon: string, ammo: { rounds?: number; magazines?: number | null; stock?: number | null }): void — Set ammunition outright (a stage restock).
  • boost(weapon: string, b: { rate?: number; free?: boolean; seconds: number }): void — Start or extend a boost: fire-rate multiplier and free rounds for seconds.
  • select(weapon: string): void — Switch the primary weapon.
  • setReady(ready: boolean): void — Allow or stop firing (cover, cut-scenes).
  • cue(fx: "shake" | "flash", amount: number, seconds: number, color?: string): void — Queue a presenter cue (shake, flash) through the gun's event stream.
  • peek(world: HitWorld, x?: number, y?: number): HitCandidate | null — The first candidate under the crosshair (or a point), skipping unhittable ones.
  • inReloadBand(x?: number, y?: number, weapon?: string): boolean — Is the crosshair (or a point) in the weapon's off-screen reload band (reload.margin).
  • canFire(weapon?: string): boolean — Can this weapon fire right now (ready, loaded or able to fire empty, not reloading).
  • accuracy(): number — Accuracy so far: hits / shots (0 with no shots).
  • shakeOffset(): { x: number; y: number } — Current shake offset for presenters (deterministic from the gun's time).
  • snapshot(): GunState — Deep copy of the state.
  • restore(s: GunState): void — Restore a snapshot from a gun built from the same definition.
  • reset(): void — Back to the start.

GunDef

interface GunDef

A light gun.

  • view: ViewSize — The screen-space view the gun points at.
  • aim?: AimDef — Crosshair behaviour.
  • weapons: WeaponDef[] — Weapons; at least one.
  • primary?: string — Weapon on the trigger (default the first).
  • special?: string — Weapon on the special button (rockets); optional.
  • parts?: Record<string, PartDef> — Body-part damage multipliers.
  • protect?: ProtectDef — Protected-target rules.
  • combo?: ComboDef — Streak rules.
  • seed?: number — Seed for spread and recoil (default 1).
  • step?: number — Fixed step in seconds (default 1/60).

GunEvent

type GunEvent = | { type: "shot"; shot: number; weapon: string; x: number; y: number; points: Array<{ x: number; y: number }>; free: boolean; onScreen: boolean } | { type: "launch"; shot: number; weapon: string; x: number; y: number; from: { x: number; y: number }; travel: number } | { type: "hit"; shot: number; weapon: string; target: string; region: string; part: string; damage: number; x: number; y: number; kind?: string; data?: Record<string, number | string | boolean>; streak: number; blast: boolean } | { type: "blocked"; shot: number; weapon: string; target: string; region: string; x: number; y: number; kind?: string } | { type: "protected-hit"; shot: number; weapon: string; target: string; region: string; protect: string; warning: boolean; penalty: Record<string, number> | null; x: number; y: number; kind?: string; data?: Record<string, number | string | boolean> } | { type: "miss"; shot: number; weapon: string; x: number; y: number } | { type: "blast"; shot: number; weapon: string; x: number; y: number; radius: number } | { type: "dry"; weapon: string } | { type: "reload"; weapon: string; time: number; via: ReloadMode } | { type: "reloaded"; weapon: string; rounds: number; magazines: number | null } | { type: "empty"; weapon: string } | { type: "boost"; weapon: string; seconds: number } | { type: "boost-end"; weapon: string } | { type: "streak-end"; streak: number } | { type: "fx"; fx: "muzzle" | "shake" | "flash"; x: number; y: number; amount: number; seconds: number; color?: string }

Events, drained with drain().

GunInput

interface GunInput

One step of gun input. Every field is optional.

  • x?: number — Pointer x in view pixels (absolute aim).
  • y?: number — Pointer y in view pixels (absolute aim).
  • onScreen?: boolean — Is the gun pointed at the screen (default: the raw point is inside the view).
  • stickX?: number — Stick x in -1..1 (relative aim).
  • stickY?: number — Stick y in -1..1, positive down (relative aim).
  • snap?: boolean — Jump the crosshair to the input with no smoothing (a touch tap).
  • trigger?: boolean — Trigger held.
  • special?: boolean — Special button held (fires the special weapon).
  • reload?: boolean — Reload button held.
  • weapon?: string — Switch the primary weapon to this id.

GunState

interface GunState

The gun's state (plain data).

  • time: number — Seconds since creation.
  • aim: { x: number; y: number } — The crosshair as drawn and fired: smoothed aim plus recoil, in view pixels.
  • base: { x: number; y: number } — The smoothed aim without recoil.
  • raw: { x: number; y: number } — The raw input point (may be off screen).
  • recoil: { x: number; y: number } — Accumulated recoil offset.
  • onScreen: boolean — Is the gun pointed at the screen.
  • ready: boolean — Can the gun fire (false while holstered: cover, cut-scenes).
  • primary: string — Weapon on the trigger.
  • special: string | null — Weapon on the special button, or null.
  • weapons: Record<string, WeaponState> — Weapons by id.
  • boosts: BoostState[] — Active boosts.
  • pending: PendingShot[] — Shots in flight.
  • stats: GunStats — Statistics.
  • warningsLeft: number — Protected-hit warnings left.
  • flash: number — Seconds of muzzle flash left.
  • shake: { amount: number; left: number; total: number } — Current screen shake: amplitude in pixels and seconds left of the longest shake.
  • serial: number — Last shot serial number.
  • rng: number[] — The seeded random stream's state: four words, from the kernel's rng.snapshot(). It advances in place and is saved with the state.
  • specialHeld: boolean — Special button held last step.
  • reloadHeld: boolean — Reload button held last step.

GunStats

interface GunStats

Accuracy and streak statistics.

  • shots: number — Trigger shots fired (a multi-pellet shot counts once).
  • hits: number — Shots that hit at least one hostile target.
  • misses: number — Shots that hit nothing hostile or protected.
  • blocked: number — Shots stopped by a shield.
  • protectedHits: number — Shots that hit a protected target.
  • warnings: number — Warnings used.
  • parts: Record<string, number> — Hits per body part.
  • byWeapon: Record<string, { shots: number; hits: number }> — Shots and hits per weapon.
  • streak: number — Current streak of consecutive hitting shots.
  • bestStreak: number — Best streak.
  • lastHit: number — Gun time of the last hostile hit (-1: none).

HitCandidate

interface HitCandidate

One thing a shot could hit, in the consumer's occlusion order.

  • target: string — Target id (the consumer's).
  • region?: string — Region id within the target (default 'body').
  • part?: string — Body part for damage multipliers and statistics (default the region id).
  • protected?: string | boolean — Protected: true or a kind label ('hostage', 'friend'); hits are penalties, never damage.
  • shield?: boolean — A shield: stops the shot without harm (a bullet-proof vest, a white eye).
  • solid?: boolean — Stops the shot after a hit (default true). false lets it continue (thin cover that still counts a hit).
  • damage?: number — Damage multiplier for this region (default 1).
  • hittable?: boolean — Shots ignore it (default true).
  • distance?: number — Distance along a ray (3D) or depth (2D), for reports.
  • kind?: string — The consumer's kind id, copied into events.
  • data?: Record<string, number | string | boolean> — Free data copied into events.

HitWorld

interface HitWorld

Answers "what is under this screen point" for the gun.

  • at(x: number, y: number, shot: ShotInfo): HitCandidate[] — Candidates under a screen point, front to back.
  • within?(x: number, y: number, radius: number, shot: ShotInfo): HitCandidate[] — Candidates whose regions meet a circle (blasts); order does not matter. Default: at at the centre.

PartDef

interface PartDef

Damage multiplier for a named body part (head, body, limb ...).

  • damage?: number — Damage multiplier (default 1).

PendingShot

interface PendingShot

A shot travelling to its impact point.

  • shot: number — Shot serial number.
  • weapon: string — Weapon id.
  • x: number — Impact x.
  • y: number — Impact y.
  • from: { x: number; y: number } — Launch point.
  • t: number — Seconds since launch.
  • travel: number — Seconds to impact.

ProtectDef

interface ProtectDef

Protected targets: hostages, civilians, travellers.

  • warnings?: number — Free warnings per session before penalties apply (Crossbow: "DON'T SHOOT YOUR FRIENDS!"). Default 0.
  • warnKinds?: string[] — Protected kinds the warnings cover (default every kind).
  • penalties?: Record<string, Record<string, number>> — Penalty per protected kind ('*' for any): free-form numbers the consumer applies (score, damage, lives ...).

RayWorldDef

interface RayWorldDef

A consumer's 3D picking, wrapped as a hit world.

  • cast(x: number, y: number, shot: ShotInfo): HitCandidate[] — Candidates along the ray through a screen point, nearest first (for rail-route: screenToAim, then pick).
  • within?(x: number, y: number, radius: number, shot: ShotInfo): HitCandidate[] — Candidates inside a blast centred on a screen point (optional).

RecoilDef

interface RecoilDef

Felt recoil: the crosshair kicks per shot and recovers.

  • kick: number — Pixels the crosshair kicks up per shot.
  • jitter?: number — Random sideways kick, up to this many pixels either way (default 0).
  • recover?: number — Recovery time constant in seconds (default 0.12).
  • max?: number — Largest accumulated kick in pixels (default 4 x kick).

ReloadDef

interface ReloadDef

Reload rules for a magazine weapon.

  • modes?: ReloadMode[] — Ways to reload (default ['auto']): auto when the magazine runs dry, button on the reload input or reload(), offscreen by pulling the trigger with the gun pointed off the screen.
  • time?: number — Seconds a reload takes (default 0: instant). A reload fills the magazine and uses one spare; rounds left in the old one are dropped.
  • margin?: number — With offscreen: a band around the edge of the view, as a fraction of its width and height, that counts as "off the screen" for reloading (default 0). A mouse or a finger cannot leave a browser game, so a trigger pulled with the crosshair in the band reloads a magazine that is not full; with a full magazine the shot fires as usual.

ReloadMode

type ReloadMode = "auto" | "button" | "offscreen"

How a magazine is refilled.

ScreenRegion

interface ScreenRegion

A hit region of a screen target, in target units relative to its centre (scaled by the target's scale).

  • id: string — Region id.
  • part?: string — Body part for damage multipliers and statistics (default the id).
  • shape?: "circle" | "rect" — Shape (default 'rect').
  • x?: number — Centre x offset (default 0).
  • y?: number — Centre y offset, y down (default 0).
  • r?: number — Circle radius.
  • w?: number — Rectangle width.
  • h?: number — Rectangle height.
  • protected?: string | boolean — Protected region (overrides the target).
  • shield?: boolean — Shield: stops shots without harm.
  • solid?: boolean — Stops the shot after a hit (default true).
  • damage?: number — Damage multiplier (default 1).
  • hittable?: boolean — Shots ignore it (default true).

ScreenTarget

interface ScreenTarget

A target on the screen.

  • id: string — Target id.
  • x: number — Centre x in view pixels.
  • y: number — Centre y in view pixels.
  • scale?: number — Scale applied to the regions (default 1).
  • z?: number — Draw order: higher is in front and is tested first (default 0; ties: later in the list is in front).
  • w?: number — Default rectangle width when there are no regions.
  • h?: number — Default rectangle height when there are no regions.
  • regions?: ScreenRegion[] — Regions, tested in order (default one w x h rectangle 'body').
  • protected?: string | boolean — The whole target is protected (true or a kind label).
  • shield?: boolean — Every region is a shield unless the region says otherwise.
  • hittable?: boolean — Shots ignore the whole target.
  • kind?: string — Kind id copied into candidates.
  • data?: Record<string, number | string | boolean> — Free data copied into candidates.

ShotInfo

interface ShotInfo

What a resolver is told about the shot it is resolving.

  • shot: number — Shot serial number.
  • weapon: string — Weapon id.
  • radius: number — Blast radius for area queries (0 for point shots).

Supply

interface Supply

What give adds to a weapon.

  • rounds?: number — Rounds into the current magazine (clamped to the magazine size unless overfill).
  • magazines?: number — Spare magazines.
  • stock?: number — Stock.
  • overfill?: boolean — Allow rounds beyond the magazine size (Operation Wolf's pickups add 30 rounds to the count).

TriggerMode

type TriggerMode = "single" | "auto" | "burst"

Trigger behaviour: one shot per press, repeat while held, or a burst per press.

ViewSize

interface ViewSize

A screen-space area in logical pixels (the gun's view).

  • w: number — Width.
  • h: number — Height.

WeaponDef

interface WeaponDef

A weapon as data.

  • id: string — Unique id.
  • trigger?: TriggerMode — Trigger mode (default 'single').
  • rate?: number — Shots per second for auto and within a burst (default 10).
  • burst?: number — Shots per burst (default 3).
  • cooldown?: number — Seconds after a single shot or a burst before the next can start (default 0).
  • damage?: number — Damage per hit (default 1).
  • spread?: number — Uniform spread radius in pixels around the crosshair (default 0).
  • pellets?: number — Pellets per shot, each spread independently (default 1).
  • pierce?: number — Extra targets a shot passes through after the first solid hit (default 0).
  • pierceShields?: boolean — Shots go through shields (default false).
  • radius?: number — Blast radius in pixels: every target in the circle is hit (rockets, grenades). Default none.
  • blastShields?: boolean — Shields also stop blasts (default false).
  • spareProtected?: boolean — Blasts do not hurt protected targets (default false).
  • travel?: number — Seconds from the trigger to impact; resolved against the world when it lands (default 0: hitscan).
  • launch?: { x: number; y: number } — Where a travelling shot starts on the screen, for presenters (default the bottom centre).
  • magazine?: number — Rounds per magazine; omit for no magazine.
  • magazines?: number — Spare magazines at the start; omit for unlimited spares.
  • maxMagazines?: number — Most spare magazines carried (default unlimited).
  • stock?: number — A simple stock of shots (rockets, grenades) when there is no magazine; omit both for unlimited.
  • maxStock?: number — Most stock carried (default unlimited).
  • reload?: ReloadDef — Reload rules (magazine weapons).
  • whenEmpty?: { rate: number } | null — With no rounds and no spare magazines left, keep firing at this rate (shots per second) instead of clicking dry.
  • recoil?: RecoilDef — Crosshair recoil.
  • fx?: WeaponFx — Presenter cues.

WeaponFx

interface WeaponFx

Presenter cues a weapon emits.

  • flash?: number — Muzzle flash seconds per shot (default 0.05).
  • shake?: number — Screen shake in pixels per shot (a recoil motor; default 0).
  • shakeTime?: number — Seconds each per-shot shake lasts (default 0.08).
  • blast?: number — Screen shake in pixels when a blast lands (default 0).
  • blastTime?: number — Seconds a blast shake lasts (default 0.4).

WeaponState

interface WeaponState

Live state of one weapon.

  • id: string — Weapon id.
  • rounds: number — Rounds in the magazine (magazine weapons).
  • magazines: number | null — Spare magazines; null is unlimited.
  • stock: number | null — Stock (stock weapons); null is unlimited or not a stock weapon.
  • reloading: number — Seconds left of a reload in progress (0: none).
  • cooldown: number — Seconds before the next shot may fire.
  • burstLeft: number — Shots left in the current burst.
  • held: boolean — The trigger driving this weapon was held last step.