Skip to content

Spine Animation

The SpineAnimation component plays Spine skeletal animations. Add it to an entity in the scene editor along with Transform.

Properties

PropertyTypeDefaultDescription
skeletonPathstring''Path to skeleton JSON file
atlasPathstring''Path to atlas file
skinstring''Active skin name
animationstring''Current animation name
timeScalenumber1.0Playback speed multiplier
loopbooleantrueWhether to loop the animation
playingbooleantrueWhether currently playing
flipXbooleanfalseFlip horizontally
flipYbooleanfalseFlip vertically
colorColor{r:1, g:1, b:1, a:1}Tint color RGBA
layernumber0Render order
skeletonScalenumber1.0Skeleton scale factor
materialnumber0Material ID
enabledbooleantrueWhether this animation is rendered

Setup

  1. Place Spine export files (.json + .atlas + images) in your project’s assets/ folder
  2. In the scene editor: create an entity → add Transform and SpineAnimation
  3. Set skeletonPath and atlasPath to point to your Spine files
  4. Set animation to the name of the animation to play

Controlling Animations

Query the SpineAnimation component in a system to change animations at runtime:

import { defineSystem, addSystem, Res, Input, Query, Mut, SpineAnimation } from 'esengine';
import { Player } from './components';
addSystem(defineSystem(
[Res(Input), Query(Mut(SpineAnimation), Player)],
(input, query) => {
for (const [entity, spine] of query) {
if (input.isKeyPressed('Space')) {
spine.animation = 'jump';
spine.loop = false;
} else if (input.isKeyDown('KeyD') || input.isKeyDown('KeyA')) {
spine.animation = 'run';
spine.loop = true;
} else {
spine.animation = 'idle';
spine.loop = true;
}
}
}
));

Skins

Switch skins to change the character’s appearance:

spine.skin = 'warrior';

Playback Control

spine.playing = false; // Pause
spine.playing = true; // Resume
spine.timeScale = 2.0; // Double speed
spine.timeScale = 0.5; // Half speed

Flipping

spine.flipX = true; // Face left
spine.flipX = false; // Face right (default)

Color Tinting

spine.color = { r: 1, g: 0, b: 0, a: 1 }; // Red tint
spine.color = { r: 1, g: 1, b: 1, a: 0.5 }; // 50% transparent

Example: Character Controller

Define a CharacterState component, attach it with SpineAnimation on the entity in the editor:

import {
defineComponent, defineSystem, addSystem,
Res, Input, Time, Query, Mut, Transform, SpineAnimation
} from 'esengine';
const CharacterState = defineComponent('CharacterState', {
speed: 200,
currentAnim: 'idle'
});
addSystem(defineSystem(
[Res(Input), Res(Time), Query(Mut(Transform), Mut(SpineAnimation), Mut(CharacterState))],
(input, time, query) => {
for (const [entity, transform, spine, state] of query) {
let moving = false;
if (input.isKeyDown('KeyD')) {
transform.position.x += state.speed * time.delta;
spine.flipX = false;
moving = true;
}
if (input.isKeyDown('KeyA')) {
transform.position.x -= state.speed * time.delta;
spine.flipX = true;
moving = true;
}
const newAnim = moving ? 'run' : 'idle';
if (state.currentAnim !== newAnim) {
state.currentAnim = newAnim;
spine.animation = newAnim;
spine.loop = true;
}
}
}
));

Multi-Version Architecture

Estella supports Spine versions 3.8, 4.1, and 4.2 simultaneously. The engine automatically detects the Spine version from the skeleton data and selects the appropriate runtime backend.

import type { SpineVersion } from 'esengine';
// SpineVersion = '3.8' | '4.1' | '4.2'

How It Works

  • When a Spine asset is loaded, the engine detects the version from the skeleton file header
  • A matching backend is selected automatically: C++ native (preferred) or WASM fallback
  • No code changes are needed — the same SpineAnimation component works with all versions

SpineManager Runtime API

For advanced use cases, the SpineManager provides runtime introspection:

import { SpineManager } from 'esengine';
// Detect version from raw data (before loading)
const version = SpineManager.detectVersion(skelBinaryData);
const versionJson = SpineManager.detectVersionJson(skelJsonString);
// Query a loaded entity
const manager = app.getResource(SpineManager);
const ver = manager.getEntityVersion(entity); // '3.8' | '4.1' | '4.2'
const anims = manager.getAnimations(entity); // ['idle', 'run', 'jump']
const skins = manager.getSkins(entity); // ['default', 'warrior']
const bounds = manager.getBounds(entity); // { x, y, width, height }

Multi-Texture Batching

Spine skeletons that use multiple atlas pages are automatically batched by the renderer. No additional configuration is needed — the C++ backend handles multi-texture setups transparently, keeping draw calls low even with complex multi-atlas characters.