pixi-reels
Docs · Architecture
Objects

Classes

The composition tree. what owns what, and what passes through.

Every class in pixi-reels lives on one of three layers: theouter shell the user builds, the scene graphPixiJS renders, or the internals that exist for the duration of a spin. Ownership reads top-to-bottom: the parent always destroys its children on destroy().

What each class does. in plain English

If a name feels opaque, read its one-liner here first. The SVG after this section shows how the names are wired together.

ReelSetBuilderbuilder

A fluent, chainable configurator. You call .reels(5).visibleCells(3)... and end with .build(). It exists so every required piece of setup is forced into one place. Forget .ticker(app.ticker) and .build() throws before your game boots.

ReelSetscene graph root

The thing you app.stage.addChild(reelSet). A PixiJS Container that owns every reel, the spin controller, the speed manager, and the win spotlight. All the public methods you'll reach for live here: spin(), setResult(), skipSpin(), destroy().

Reelone column

One vertical column of symbols. A Reel owns the symbols currently on screen, their vertical position, and the "what do I land on" queue. You rarely touch a Reel directly. you drive the whole ReelSet and let it fan out.

ReelViewportclip + layer

The clipped window you see through. Holds the mask so symbols scrolling above or below the visible area are hidden, plus a "promoted" layer where winning symbols get temporarily lifted above the mask so their celebration animation isn't clipped.

ReelMotionvertical scroll

The physics of one reel. Adds a Y delta every tick and wraps symbols that fall off the bottom back to the top. When a symbol wraps, it fires a callback that tells the reel "time to swap identity". that's how a spinning reel eventually shows the target grid.

StopSequencerlanding queue

A tiny queue that holds the symbols a reel must land on, in order. On every wrap, the sequencer hands out the next target. When the queue is empty the reel stops.

SpinControllerconductor

The conductor of a spin. Hands every reel its next phase (StartSpin → optional AnticipationStop), coordinates with setResult(), fires the spin:* events, and resolves the promise you awaited on.

ReelPhasestate

Abstract base for a single stage of a reel's spin. Each stage gets onEnter, update (per-tick), and onSkip. Stock phases: StartPhase (wind-up), SpinPhase (steady-state blur), AnticipationPhase (slow-down for tension), StopPhase (decelerate, snap, bounce).

SpinningModestrategy

The top-level "what does a spin even mean" strategy. StandardMode runs every reel through the phase machine. CascadeMode replaces whole columns atomically for tumble mechanics. ImmediateMode snaps straight to the result. useful in tests.

SpeedManagertempo

Named bundles of timings (spin speed, stop delay, bounce distance, easings). Ship with normal, turbo, superTurbo presets. Switch at runtime with reelSet.setSpeed('turbo'); add your own with addProfile().

ReelSymbolabstract cell

One visible cell on a reel. Abstract base class. subclass it to draw whatever you like. Built-in subclasses: SpriteSymbol (plain texture), AnimatedSpriteSymbol (sprite sheet), SpineSymbol (Spine skeleton, optional peer). Implement onActivate, onDeactivate, playWin, stopAnimation, resize for a new kind.

SymbolRegistry / SymbolFactorypool

SymbolRegistry records "symbol id X is rendered by class Y with options Z". SymbolFactory is the cache on top. it pools instances so scrolling a 5x3 reel for five seconds allocates zero new symbol objects.

FrameBuilder + middlewarepipeline

A middleware pipeline that decides which symbol identity fills each new buffer slot when a reel needs another row. Random fill is priority 0, target-frame placement is priority 10. Register your own middleware to implement rules like "no three-in-a-row" or "inject a mystery symbol every 7th spin".

SymbolSpotlightwins

The win-animation primitive. show(positions) dims the losers and runs playWin() on the winners, promoted above the mask. cycle(lines) iterates through multiple lines with a configurable cadence and emits spotlight:start / spotlight:end events.

EventEmitterpub / sub

Typed pub/sub. Event names are colon-namespaced (spin:start, spin:reelLanded, speed:changed). Every exit path from a spin fires an event. wire audio and HUD to events, not to method calls, so skipSpin() and destroy() stay correct without your HUD knowing about them.

TickerRefsafe ticker

A thin wrapper around PIXI.Ticker that tracks every callback it adds and removes them all on destroy(). Nobody in this codebase calls ticker.add() directly. that's how we keep teardown correct.

Disposableinterface

The cleanup contract. Anything that allocates implements it. reelSet.destroy() cascades through the whole tree. you never have to chase individual pieces yourself.

FakeTicker + HeadlessSymboltests

The headless harness. A manual ticker you step frame by frame, plus a symbol class that draws nothing. With these you can run an entire spin lifecycle in Node, assert on the final grid, and never load PixiJS's renderer.

flowchart TB
  Builder["ReelSetBuilder<br/><i>.reels() · .symbols() · .build()</i>"]
  ReelSet["ReelSet<br/><i>extends PIXI.Container · implements Disposable</i>"]

  Builder -.->|creates| ReelSet

  ReelSet --> SpinController["SpinController<br/><i>phase orchestrator</i>"]
  ReelSet --> SpeedManager["SpeedManager<br/><i>named profiles</i>"]
  ReelSet --> SymbolSpotlight["SymbolSpotlight<br/><i>win cycle</i>"]
  ReelSet --> SymbolFactory["SymbolFactory<br/><i>pool owner</i>"]
  ReelSet --> FrameBuilder["FrameBuilder<br/><i>middleware pipeline</i>"]

  subgraph ReelsGroup["Reels[]"]
    direction LR
    Reel0["Reel #0"]
    Reel1["Reel #1"]
    Reel2["Reel #2"]
    Reel3["Reel #3"]
    Reel4["Reel #4"]
  end

  ReelSet --> Reel0

  subgraph PerReel["Per reel (one Reel contains)"]
    direction TB
    Container["container: PIXI.Container<br/><i>child of ReelViewport.maskedContainer</i>"]
    Symbols["symbols: ReelSymbol[]<br/><i>buffer + visible + buffer · pooled by SymbolFactory</i>"]
    Events["events: EventEmitter&lt;ReelEvents&gt;<br/><i>phase:enter · phase:exit · landed · symbol:created</i>"]
    Motion["motion: ReelMotion<br/><i>displace · wrap · snapToGrid</i>"]
    StopSequencer["stopSequencer: StopSequencer<br/><i>target frame queue · next() pops each wrap</i>"]
    SpinningMode["spinningMode: SpinningMode<br/><i>Standard · Cascade · Immediate · computeDeltaY()</i>"]
  end

  ReelsGroup -.->|one reel contains| PerReel
  
One reel contains the boxes inside the lower group.
Public API· Builder. you construct this
Scene graph· ReelSet and its reels
Internals· Subsystems, motion, sequencer
Grouping· conceptual, not a class

Reading the ownership chain

ReelSet.destroy() is the single source of truth for teardown. Internally it:

  1. Destroys the SymbolSpotlight (releases its tween handles).
  2. Destroys the SpinController, which removes the TickerRef callback.
  3. For each Reel: releases every pooled symbol back to the SymbolFactory, removes event listeners, destroys the PixiJS container tree.
  4. Destroys the SymbolFactory, which disposes every class instance in its pool.
  5. Destroys the ReelViewport (masked container).
  6. Emits destroyed, removes every listener, calls super.destroy({ children: true }).

If you extend pixi-reels with a new subsystem that holds resources, it must implement the Disposable interface and be destroyed from this chain. there's no hidden GC to catch you.

Disposable everywhere

ReelSetowns: viewport · reels · factory · subsystems
Reelowns: motion · stopSequencer · events · container
SpinControllerowns: TickerRef · active phases
SymbolSpotlightowns: gsap timelines · overlay containers
SymbolFactoryowns: ObjectPool<ReelSymbol>
TickerRefowns: registered ticker callbacks
ReelSymbolowns: view container · subclass resources

Extension points

The classes on the outer edges are where you plug in custom code:

  • Extend ReelSymbol for a new rendering style (Spine, animated sprite, custom Graphics).
  • Extend ReelPhase<TConfig> for a new spin phase, register it via builder.phases(f => f.register(...)).
  • Implement SpinningMode for a different motion model (cascade, hover, immediate).
  • Implement FrameMiddleware to hook into per-frame symbol generation. random fill and target placement are themselves just middleware.