Spine Animation
Estella has built-in Spine skeletal animation:
bones, meshes, IK, skins, and events. It’s driven by the SpineAnimation component
and controlled at runtime through the Spine resource (from the esengine/spine
subpath).
The SpineAnimation component
Section titled “The SpineAnimation component”Add it in the editor and point it at your Spine assets. The component lives in the
main esengine package.
| Property | Type | Default | Description |
|---|---|---|---|
skeletonPath |
asset | '' |
Skeleton file (.json / .skel). |
atlasPath |
asset | '' |
Atlas file. |
skin |
string | '' |
Active skin (empty = default). |
animation |
string | '' |
Initial animation on track 0. |
loop |
boolean | true |
Loop the initial animation. |
playing |
boolean | true |
Whether playback advances. |
timeScale |
number | 1 |
Per-entity playback speed. |
skeletonScale |
number | 1 |
Uniform skeleton scale. |
flipX / flipY |
boolean | false |
Mirror horizontally / vertically. |
color |
Color | {1,1,1,1} |
Tint (RGBA, 0..1). |
layer |
number | 0 |
Sorting layer for draw order. |
material |
asset | none | Optional custom material. |
enabled |
boolean | true |
Disable to freeze and hide. |
Skeleton and atlas are asset slots in the Details panel: pick from the popover,
drag a file in from the Content Browser, or use the slot’s locate / clear actions.
The scene serializes them as portable @uuid: references, so moving or renaming
the files never breaks the link. The editor previews the skeleton live in the
viewport — including across Play/Stop — and once it loads, the animation and
skin fields become dropdowns of that skeleton’s actual animations and skins.
Atlases
Section titled “Atlases”Spine .atlas files work as exported — including multi-page atlases (each
page texture loads alongside the atlas) and premultiplied alpha: a pma: true
atlas header is honored automatically, no import setting needed. When you export
with Compress textures enabled, atlas pages cook to KTX2 and transcode on
device like any other texture.
Play & blend animations
Section titled “Play & blend animations”The Spine resource controls tracks, mixing (crossfade), and per-track blending:
import { defineSystem, Query, Res, SpineAnimation } from 'esengine';import { Spine } from 'esengine/spine';
const control = defineSystem([Query(SpineAnimation), Res(Spine)], (q, spine) => { for (const [entity] of q) { spine.setAnimation(entity, 'run', true); // play on track 0, looped spine.setDefaultMix(entity, 0.15); // default crossfade spine.setMixDuration(entity, 'idle', 'run', 0.25); // specific transition spine.setTrackAlpha(entity, 0, 1.0); // blend a track in/out }});| Method | Description |
|---|---|
setAnimation(entity, name, loop) |
Play an animation on track 0. |
setDefaultMix(entity, seconds) |
Default crossfade between any two animations. |
setMixDuration(entity, from, to, seconds) |
Crossfade for a specific transition. |
setTrackAlpha(entity, track, alpha) |
Blend a track’s contribution (0..1). |
setEntityProps(entity, props) |
Set { skeletonScale?, flipX?, flipY?, layer? } at once. |
setIKTarget(entity, constraint, x, y, mix) |
Aim an IK constraint at a world point (mix 0..1). |
Transform / path constraint mixes are adjustable too — listConstraints(entity),
getTransformConstraintMix / setTransformConstraintMix, and the path equivalents.
Skins & attachments
Section titled “Skins & attachments”spine.setSkin(entity, 'armored');spine.setAttachment(entity, 'weapon-slot', 'sword'); // swap a slot's attachmentspine.setSlotColor(entity, 'body', 1, 0.5, 0.5, 1); // r, g, b, a (0..1)| Method | Description |
|---|---|
setSkin(entity, name) |
Switch the active skin. |
setAttachment(entity, slot, attachment) |
Swap a slot’s attachment (equip/variant). |
setSlotColor(entity, slot, r, g, b, a) |
Tint an individual slot (four 0..1 channels). |
Query a skeleton
Section titled “Query a skeleton”const anims = spine.getAnimations(entity); // string[]const skins = spine.getSkins(entity); // string[]const bounds = spine.getBounds(entity); // { x, y, width, height } | nullAnimation events
Section titled “Animation events”The SpineEvents resource publishes track events once per frame — read it the same
frame:
import { defineSystem, Res } from 'esengine';import { SpineEvents } from 'esengine/spine';
const onSpine = defineSystem([Res(SpineEvents)], (spineEvents) => { for (const e of spineEvents.events) { // e.type: 'start' | 'interrupt' | 'end' | 'complete' | 'event' // e.entity, e.track, e.animationName; for 'event': e.eventName + values }});Event type |
Fired when |
|---|---|
start |
An animation starts on a track. |
interrupt |
An animation is interrupted by another. |
end |
An animation is removed from a track. |
complete |
An animation loop/playthrough completes. |
event |
A user-authored event keyframe fires (eventName + values). |
Best practices
Section titled “Best practices”- Set a default mix (
setDefaultMix) so transitions crossfade instead of snapping; override hot paths withsetMixDuration. - Layer with tracks +
setTrackAlpha(e.g. an aim/overlay on track 1 blended over a locomotion track 0). - Share skeleton + atlas across entities — they ref-count a single loaded skeleton.
- Drive whole characters from the Animator state machine, which can target Spine as well as sprites.