Skip to content

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).

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.

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.

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.

spine.setSkin(entity, 'armored');
spine.setAttachment(entity, 'weapon-slot', 'sword'); // swap a slot's attachment
spine.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).
const anims = spine.getAnimations(entity); // string[]
const skins = spine.getSkins(entity); // string[]
const bounds = spine.getBounds(entity); // { x, y, width, height } | null

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).
  • Set a default mix (setDefaultMix) so transitions crossfade instead of snapping; override hot paths with setMixDuration.
  • 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.
  • Animation — the Animator state machine can drive Spine.
  • Assets — loading skeleton + atlas assets.