What it does
An actor is any plain object with an x, a y and a kind, and optionally a radius and an alive flag. You build
a table with createInteractions and add rules with on(from, to, …): a shot meeting a mine splits the mine and
scores, a ship meeting the sun dies. A rule matches when the two are within their radii added, or a reach of its own,
or at any range, and its optional when test holds. Each frame, resolve tests every ordered pair in one list, or
pair tests two lists against each other, shots against rocks without rocks against rocks.
The sweep respects deaths as it goes: once an actor's alive turns false it stops counting for the rest of the sweep,
so a shot that has already hit cannot also take out the rock behind it. Each rule receives a context you choose,
usually the game state, so its effect is a line of code beside the rule rather than a branch in the update loop.
Some interactions are actions rather than collisions. A rule with a verb, such as steal, dock or capture, never fires
in a sweep; your game fires it when the player presses a button, and target finds the nearest candidate the verb
would match.
Features
- Rules by kind — one way round for each pair, chainable, with as many rules per pair as you need.
- Reach and conditions — the two radii by default, a fixed reach, or any range, plus a
whentest for anything else. - Death-aware sweeps — one list against itself or two lists against each other, returning how many rules fired.
- Named verbs — pressed rather than collided, with the nearest valid target found for you.
- Ready-made conditions —
where.sameLanefor tube lanes,where.samePlanefor depth planes,where.facingfor a facing cone andwhere.notOwnso a shot never hits the thing that fired it.
Good for
- Vector shooters with a handful of rules: shots and mines, ships and suns, shots and enemies.
- Games with thieves and cargo, where an enemy steals a canister and the player rescues it.
- Tube and lane shooters, where only actors in the same lane or depth plane can meet.
- Board and token games whose pieces are plain objects and whose meetings are rules.
- Any game whose rules should read as data, so a designer can see every interaction in one place.
Make it yours
The table is yours to fill: the kinds, the rules, the reach, the conditions and what each rule does. Rules can be
combined with && in a when, and the context type is the game state you pass, so a rule can score, spawn, split or
end the game.
Two very different uses: an asteroid-field shooter where shots are swept against rocks with pair, where.notOwn
keeps a ricochet off its owner, and the ship dies on touching the sun at a reach of its own; and a tow-and-dock game
where nothing collides at all, and every interaction is a verb the player presses, with target picking the nearest
ship that can be docked with.
A quick look
Three rules, one of them a verb, resolved once a frame:
import { createInteractions, where, type Kinded } from '@phaser-ae/interaction-rules';
type Actor = Kinded & { id: number; owner?: Actor };
const rules = createInteractions<Actor, Game>()
.on('shot', 'mine', { when: where.notOwn(), then: (s, m, g) => { s.alive = false; g.split(m); g.score(750); } })
.on('ship', 'sun', { radius: 40, then: (_, __, g) => g.die() })
.on('thief', 'canister', { verb: 'steal', then: (t, c) => { t.cargo = c; } });
game.step('rules', () => { rules.resolve(actors, state); });
Works with
maths— used for the facing test's angles.collision— for kernel objects with tags, its overlap rules do the same job on a spatial hash, and scale to thousands.
Good to know
- The sweep tests every pair in the list it is given: right for dozens of actors, wrong for thousands, where
collisionis the better choice. - Overlap is circles only; any other shape test goes in
when. - Rules are one way round: a rule for a shot meeting a rock does not fire for a rock meeting a shot.
- The pack is pure logic and runs headless, with no options of its own.






