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.8 exports need converting to 4.2 first: tools/spine-3.8-to-4.2/ in the repo does binary .skel and JSON; tools/spine-3.7-to-4.2/ does 3.7 JSON. When a game packs a skeleton’s images into its UI atlases instead of a Spine atlas, tools/pragmatic-wheel/build_fx_atlas.py rebuilds a Spine atlas from the sprite rectangles. When the skeleton itself is lost but the atlas survives, tools/playson-wheel/build_spine.py shows how to author one over the atlas, sector effects included. The Playson Spine recipe and the Pragmatic recipe run on those outputs.

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 ~150 lines.