ReelSetBuilder
Every method returns this so you can chain. build() validates and returns a ReelSet. missing required calls throw with a single error listing every problem.
Required
| Method | |
|---|---|
.reels(n) | Number of columns. |
.visibleRows(n) or .visibleRowsPerReel([n,...]) or .multiways({...}) | One of these picks the row layout. |
.symbolSize(w, h) | Per-symbol dimensions in px. |
.symbols((r) => { r.register(...) }) | At least one symbol id. |
.ticker(pixiTicker) | Drives frame updates. |
Missing any of these throws at .build() with a human-readable list of problems.
Optional
| Method | Default |
|---|---|
.symbolGap(x, y) | { x: 0, y: 0 } |
.bufferSymbols(n) | 1 (clamped to ≥ 1. the motion layer needs one buffer row above and below for wrap detection) |
.weights({ id: n }) | 10 for each registered symbol |
.symbolData({ id: { zIndex, unmask, size, weight } }) | Per-symbol metadata. Auto-routes unmask; size > 1x1 declares a big symbol. |
.reelPixelHeights([h,...]) | Per-reel pixel-box heights for pyramid layouts. Derived from visibleRowsPerReel × symbolHeight if unset. |
.reelAnchor('top' | 'center' | 'bottom') | 'center'. vertical alignment of short reels inside the tallest reel’s box. |
.speed(name, profile) | SpeedPresets.NORMAL under 'normal' |
.initialSpeed(name) | 'normal' |
.offsetConfig(config) | { mode: 'none' } |
.spinningMode(mode) | new StandardMode() |
.frameMiddleware(mw) | (none). add as many as you want; sorted by priority. |
.phases((f) => { f.register(...) }) | Built-in START/SPIN/STOP/ANTICIPATION (+ cascade phases when .tumble() is used). |
.tumble({ fall, dropIn }) | (unset; required for spin({ mode: 'cascade' }) and reelSet.refill(...)). see the Tumble section below. |
.multiways({ minRows, maxRows, reelPixelHeight }) | (unset; required for setShape(...)) |
.pinMigrationDuration(ms | fn) | 200 (MultiWays only) |
.pinMigrationEase(ease) | 'power2.out' (MultiWays only) |
.maskStrategy(strategy) | Per-reel RectMaskStrategy. Auto-switches to SharedRectMaskStrategy when big or unmasked symbols are registered with symbolGap.x > 0. |
.gsap(instance) | Bound from the package’s own gsap import. Use this when your app and the engine resolve to different gsap instances (symlinked workspaces, npm link, misconfigured dedupe). |
.initialFrame(ColumnTarget[]) | Random per weights(). One ColumnTarget per reel: { visible, bufferAbove?, bufferBelow? }. |
.build()
Validates every required field, instantiates every subsystem, returns a ReelSet. Throws synchronously with the full list of problems. you never see a half-built reel set.
Minimal example
import { ReelSetBuilder, SpriteSymbol, SpeedPresets } from 'pixi-reels';
const reelSet = new ReelSetBuilder()
.reels(5)
.visibleRows(3)
.symbolSize(140, 140)
.symbolGap(4, 4)
.symbols((r) => {
r.register('cherry', SpriteSymbol, { textures: { cherry: cherryTex } });
})
.weights({ cherry: 40 })
.speed('turbo', SpeedPresets.TURBO)
.ticker(app.ticker)
.build();
Tumble
.tumble(config?) enables cascade mechanics. It replaces the strip-spin + bounce-stop chain with a three-phase pipeline (cascade:fall · cascade:place · cascade:dropIn) and flips the default spin mode to 'cascade' for that builder.
.tumble({
fall: { duration: 280, ease: 'sine.in', rowStagger: 40 },
dropIn: { duration: 480, ease: 'back.out(1.6)', rowStagger: 50, distance: 'perHole' },
})
TumbleFallConfig
| Field | Type | Default | Notes |
|---|---|---|---|
duration | number (ms) | 300 | Each symbol’s fall-out tween length. |
ease | GSAP easing string | 'sine.in' | Gravity feel. Any GSAP easing works. |
rowStagger | number (ms) | 0 | Delay between successive rows starting their fall. 0 = every row falls together. |
rowOrder | 'bottomToTop' | 'topToBottom' | 'bottomToTop' | Which row begins first. The default pairs with setDropOrder('ltr') for the canonical “bottom-left first, top-right last” feel. |
TumbleDropInConfig
| Field | Type | Default | Notes |
|---|---|---|---|
duration | number (ms) | 600 | Each symbol’s drop-in tween length. |
ease | GSAP easing string | 'power2.out' | Clean deceleration with no overshoot. matches commercial slots that play a landing spine animation after the drop. Use 'back.out(1.5)' for soft overshoot, 'bounce.out' for cartoon bounce, 'expo.out' for slam. |
rowStagger | number (ms) | 60 | Delay between successive rows starting their drop. 0 = simultaneous (the canonical refill choice). |
rowOrder | 'bottomToTop' | 'topToBottom' | 'bottomToTop' | Which row arrives first when rowStagger > 0. |
distance | 'perHole' | 'auto' | number | 'perHole' | 'perHole' is gravity-correct (each symbol falls exactly its own offset). 'auto' makes every symbol traverse the full column in unison (“rain column” feel). A number forces a uniform pixel distance. |
Picking a feel
Five preset recipes you can copy verbatim. see the tumble feels recipe for side-by-side canvases.
| Feel | fall.ease | dropIn.ease | dropIn.rowStagger | When to use |
|---|---|---|---|---|
| Classic | sine.in | back.out(1.6) | 50 ms | Default. Start here. |
| Cartoon bounce | sine.in | bounce.out | 70 ms | Multi-bounce landing. |
| Slam | power4.in | expo.out | 25 ms | Turbo mode. sub-half-second tumbles. |
| Rain column | sine.in | sine.in | 0 ms (+ distance: 'auto') | Match-3 / puzzle / chess-board grids. |
| Wave | sine.in | back.out(2.0) | 110 ms | Strong per-row stagger. row-by-row arrival. |
Overriding a phase
.tumble() registers three named phases (cascade:fall, cascade:place, cascade:dropIn). Each is an independently overridable ReelPhase subclass:
builder
.tumble({ /* ... */ })
.phases((f) => f.register('cascade:fall', MyCometFallPhase));
See docs/recipes/tumble-cascade.md for the phase contract and an override example.
Spin options
reelSet.spin(options?) accepts SpinOptions. All fields are optional.
| Field | Type | Notes |
|---|---|---|
holdReels | number[] | Reel indices to FREEZE for this spin. Held reels skip START/SPIN/STOP entirely and stay on their current symbols. They count as already-landed for spin:allLanded. Out-of-range / duplicate / non-integer entries are silently filtered. setAnticipation([...]) and setStopDelays([...]) filter held indices too. |
mode | 'standard' | 'cascade' | Phase-chain selector for this spin. Overrides the builder default. 'cascade' requires .tumble(...) on the builder. the engine throws at spin() if cascade phases aren’t registered. |
// Hold reels 0 and 4; only the middle three reroll.
const spin = reelSet.spin({ holdReels: [0, 4] });
reelSet.setResult(serverGrid); // entries at 0/4 are ignored
await spin;
// Per-spin cascade override on a builder that registered both modes.
await reelSet.spin({ mode: 'cascade' });
See the spin lifecycle guide for how these options interact with the event timeline, and the cascades guide for the cascade-specific orchestration.