SpriteAnimation
Sprite-sheet animation, as an aspect on a Sprite — accessor node.anim. Clips are
declared as data (a frame cell size plus named frame-index lists) and then driven by name; once
defined, playback advances entirely in native code.
At a glance
const hero = new Sprite({ texture: tex, anchor: [0.5, 1] })
.aspect(SpriteAnimation, {
size: [32, 48], // one frame cell, in texture pixels
fps: 10,
clips: {
idle: [0, 1],
walk: [4, 5, 6, 7],
die: { frames: [8, 9, 10], fps: 6, loop: false },
},
})
hero.anim.play('walk')
hero.addEventListener('completed', clip => { if (clip === 'die') hero.destroy() })Configuring
sprite.aspect(SpriteAnimation, {
size?: [w, h] // frame cell size in texture pixels; default = full texture
fps?: number // default fps for clips; default 12
loop?: boolean // default loop for clips; default true
clips?: Record<string, Clip> // Clip = number[] | { frames: number[], fps?, loop? }
})Frame indices count row-major through the sheet's grid: the grid has
floor(texture.width / cellW) columns, index 0 is the top-left cell, and indices continue
left-to-right, then down. A clip is either a plain index array (inherits fps/loop) or an
object with per-clip overrides.
the sprite must already have its texture when the aspect attaches — the grid is
sliced from the texture's pixel size. Attaching without one throws.
passing size also sets the sprite's world size to the cell size, so one cell draws
at 1 texel = 1 world unit.
sprite.anim.define(): this // rebuild native clips after mutating anim.clipsPlayback
sprite.anim.play(name: string): this // throws on an unknown clip name
sprite.anim.stop(): this
sprite.anim.speed = 2 // playback rate multiplier (write-only)
sprite.anim.current // name of the playing clip, or null
sprite.anim.frame // current frame index (read-only)Re-playing the already-active clip is a no-op — safe to call play('walk') every frame from
input code without restarting the animation.
Events
Animation events are emitted on the node, with the clip name as the argument:
hero.addEventListener('loopReached', clip => {}) // a looping clip wrapped around (each loop)
hero.addEventListener('completed', clip => {}) // a non-looping clip finished