Spine

Using in Code

After uploading the asset, click on the SpineSkeletonData asset, hold it, and drag it into the viewport to quickly add Spine to the scene.

Drag Spine skeleton data asset into viewport

Essentially, this operation creates a new entity, adds the SpineAnimationRenderer component, and sets the SpineSkeletonData asset for the component. You can also perform these steps manually:

Use add component to add spine animation renderer

SpineAnimationRenderer Component

The component configuration is as follows:

Spine animation renderer component config
  • Asset: Spine animation resource (SpineSkeletonData asset)
  • Loop: Whether the default animation should loop
  • Initial Animation: Name of the animation to play by default
  • Skin: Name of the default skin
  • Render Priority
  • Premultiplied Alpha: Whether to render the animation in premultiplied alpha mode

If you want to dynamically load Spine animations or perform advanced operations (e.g., control animation playback, modify bone states, switch skins), you'll need to use scripts. Below is how to achieve these functionalities in code.

Usage in Scripts

Dynamic Creation

Spine assets can be loaded and animations created at runtime via scripts. Recommended workflow:

import { Script } from '@galacean/engine';
import { SpineAnimationRenderer } from '@galacean/engine-spine';
 
class YourAmazingScript extends Script {
 
  onStart() {
 
    // Load and create Spine animation asset
    const spineResource = await engine.resourceManager.load(
      {
        // In the asset panel, right-click the SpineSkeletonData asset and "Copy Relative Path", paste it here
        url: './spine.json',
        type: 'Spine',
      },
    );
 
    // Instantiate Spine animation entity using the asset's instantiate method
    const spineEntity = spineResource.instantiate();
 
     // Add Spine animation entity to the current entity
    this.entity.addChild(spineEntity);
 
  }
}

Animation Control - AnimationState

The SpineAnimationRenderer component exposes the AnimationState API via its state property.
The AnimationState object enables more complex animation operations.

To access the AnimationState object in scripts:

import { Script } from '@galacean/engine';
import { SpineAnimationRenderer } from '@galacean/engine-spine';
 
class YourAmazingScript extends Script {
 
  onStart() {
    const spine = this.entity.getComponent(SpineAnimationRenderer);
    const { state } = spine; // AnimationState object
  }
  
}

Playing Animations

The most commonly used API is setAnimation:

state.setAnimation(0, 'animationName', true)

setAnimation accepts three parameters:

  • TrackIndex: Animation track index
  • animationName: Animation name
  • loop: Whether to loop

The last two parameters are straightforward. The first parameter introduces the Spine concept of Tracks:

When playing Spine animations, an animation track must be specified. Animation tracks allow layered animation application. Each track stores animations and playback parameters, with track indices starting at 0. After applying animations, Spine applies them from low to high tracks, with higher tracks overriding lower ones.

Animation Blending

This track override mechanism is powerful. For example:

  • Track 0 could contain walking, running, or swimming animations.
  • Track 1 could have a shooting animation with keyframes for arms and gun actions.
  • Adjusting the TrackEntry alpha of higher tracks enables blending with lower tracks. For instance, Track 0 could have a walking animation and Track 1 a limping animation. Increasing Track 1's alpha when the player is injured would emphasize the limp. Example:
// The character walks while shooting
state.setAnimation(0, 'walk', true);
state.setAnimation(1, 'shoot', true);

Animation Transitions

Calling setAnimation immediately switches the current track's animation. For transitions between animations, set a transition duration using AnimationStateData:

import { Script } from '@galacean/engine';
import { SpineAnimationRenderer } from '@galacean/engine-spine';
 
class YourAmazingScript extends Script {
 
  onStart() {
    const spine = this.entity.getComponent(SpineAnimationRenderer);
    const { state } = spine; // AnimationState object
    const { data } = state; // AnimationStateData object
    data.defaultMix = 0.2; // Default transition duration
    data.setMix('animationA', 'animationB', 0.3); // Transition duration between specific animations
  }
  
}
  • defaultMix: Default transition duration for animations without explicit settings
  • setMix: Accepts three parameters (animation names and transition duration)

Animation Queue

Spine provides addAnimation for queued animation playback:

state.setAnimation(0, 'animationA', false); // Play animation A on track 0
state.addAnimation(0, 'animationB', true, 0); // Queue animation B after A with looping

addAnimation accepts four parameters:

  • TrackIndex: Animation track
  • animationName: Animation name
  • loop: Whether to loop
  • delay: Delay time

The delay parameter determines when the next animation starts:

  • When delay > 0 (e.g., 1 second), the next animation begins 1 second after the previous one starts.
  • When delay = 0, the next animation starts after the previous one finishes.
  • When delay < 0, the next animation starts before the previous one finishes.

Visual examples:

  • Delay > 0
  • Delay = 0
  • Delay < 0

You can also use addEmptyAnimation to add empty animations that return to the initial state. This is useful for blending transitions.

Track Parameters

setAnimation and addAnimation return a TrackEntry object containing additional control parameters:

  • timeScale: Controls playback speed
  • animationStart: Sets the start time
  • alpha: Blending coefficient for the track
  • ...

See TrackEntry documentation for full details.

Animation Events

Animation event diagram

Using the AnimationState API triggers events like:

  • start: When a new animation begins
  • end: When an animation is removed or interrupted
  • complete: When an animation finishes (whether looping or not)

Refer to Spine Animation Events Documentation for full details.

Listen for events with AnimationState.addListener:

state.addListener({
  start: (entry: TrackEntry) => { /* callback */ },
  complete: (entry: TrackEntry) => { /* callback */ },
  end: (entry: TrackEntry) => { /* callback */ },
  interrupt: (entry: TrackEntry) => { /* callback */ },
  dispose: (entry: TrackEntry) => { /* callback */ },
  event: (entry: TrackEntry) => { /* callback */ },
})

Skeleton Operations - Skeleton

The SpineAnimationRenderer component exposes the Skeleton API via its skeleton property. Use this to perform advanced skeletal operations.

Access the Skeleton object in scripts:

const { skeleton } = spine; // Skeleton object

Modifying Bone Positions

Use the Skeleton API to adjust bone positions, e.g., for IK targeting effects:

const bone = skeleton.findBone('boneName');
bone.x = targetX;
bone.y = targetY;

Attachment Replacement

Replace slots with new attachments for partial outfit changes:

const slot = skeleton.findSlot('slotName');
const attachment = skeleton.getAttachment(slot.index, 'attachmentName');
slot.attachment = attachment;
// Or use skeleton.setAttachment('slotName', 'attachmentName');

Skinning & Mixing

Skin Replacement Use setSkin to change the entire skin:

skeleton.setSkinByName("skinName");
skeleton.setSlotsToSetupPose();

Skin Mixing Combine multiple skins at runtime:

const mixAndMatchSkin = new spine.Skin("custom-girl");
mixAndMatchSkin.addSkin(skeletonData.findSkin("skin-base"));
// Add other skin parts...
this.skeleton.setSkin(mixAndMatchSkin);

Dynamic Atlas Loading & Attachment Replacement

Load additional atlas files at runtime to support large-scale skin expansion without affecting initial load performance:

async onStart() {
  const extraAtlas = await this.engine.resourceManager.load('/extra.atlas') as TextureAtlas;
  const slot = skeleton.findSlot(slotName);
  const region = extraAtlas.findRegion(regionName);
  const clone = this.cloneAttachmentWithRegion(slot.attachment, region);
  slot.attachment = clone;
}
 
cloneAttachmentWithRegion(
  attachment: RegionAttachment | MeshAttachment | Attachment,
  atlasRegion: TextureAtlasRegion,
): Attachment {
  // Cloning logic for different attachment types
}

Next section: Spine Examples

Was this page helpful?