pixi-wheels
Building blocks

Skins, textures and Spine

A ring owns the motion; a skin owns the pixels. The ring hands the skin two containers - disc, which rotates, and overlay, which does not - plus the geometry, and calls layout() whenever weights change.

Built-in#

SkinConfig typeWhat it draws
GraphicsRingSkingraphicsWedges, dividers, rim, hub, bulbs, fitted labels, shading. The default.
DebugRingSkindebugFlat fills, ids, start angles, degree marks, pointer lines.
TextureRingSkintextureOne painted face texture, an optional fixed frame, per-section decorations that follow the geometry.
SpineRingSkinspineA skeleton; the wheel rotates a bone (or the whole skeleton) and plays spin / win animations.
HeadlessRingSkinheadlessNothing. Tests.

Pass an instance or a config:

.skin(new TextureRingSkin({ face, frame, decorations: [{ section: 'x50', texture: plate, radius: 0.7 }] }))
.skin({ type: 'texture', face: 'wheel-face.webp', frame: 'bezel.webp' })   // keys resolved by .assets(resolver)

Spine#

import 'pixi-wheels/spine';   // registers the 'spine' config types
import { SpineRingSkin, SpinePointerSkin } from 'pixi-wheels/spine';

.skin(new SpineRingSkin({
  skeleton: 'wheelData', atlas: 'wheelAtlas',   // Assets aliases
  bone: 'wheel',                                // the bone the engine turns; omit to turn the whole skeleton
  animations: { idle: 'idle', spin: 'spin', win: 'win', winBySection: { grand: 'win_grand' } },
}))
.pointer({ skin: new SpinePointerSkin({ skeleton: 'stopperData', atlas: 'wheelAtlas', length: 80, tickAnimation: 'tick' }) })

labels: true on SpineRingSkin draws the sections’ text or rich content on the disc above the skeleton, for values the art cannot know (server-driven multipliers). winBySection maps section ids to landing animations, so a skeleton can carry one celebration per plate.

Spine 3.7 and 3.8 exports need converting to 4.2 first: tools/spine-3.8-to-4.2/ (binary and JSON) and tools/spine-3.7-to-4.2/ (JSON) in the repo do it. The Playson Spine recipe and the Pragmatic recipe run on converted skeletons; tools/README.md covers the atlas rebuilds behind them.

Your own#

class MySkin implements RingSkin {
  attach(ctx: RingSkinContext) { /* add children to ctx.disc / ctx.overlay */ }
  layout() { /* re-draw for ctx.geometry.sections */ }
  syncRotation?(deg: number) {}
  highlight?(id: string | null) {}
  onSpinStart?() {} onSpinStop?() {} onLanded?(section) {}
  destroy() {} get isDestroyed() { return false; }
}
registerRingSkin('mine', (config, assets) => new MySkin(config));

The Playson wheel on the recipes page is exactly this: authored plates composed from an atlas, in about 150 lines. The disc recipe lists every layer of art with the size, pivot and orientation it has to match; the pointer has its own.