After uploading the asset, click on the SpineSkeletonData asset, hold it, and drag it into the viewport to quickly add Spine to the scene.
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:
The component configuration is as follows:
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.
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);
}
}
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
}
}
The most commonly used API is setAnimation:
state.setAnimation(0, 'animationName', true)
setAnimation
accepts three parameters:
TrackIndex
: Animation track indexanimationName
: Animation nameloop
: Whether to loopThe 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.
This track override mechanism is powerful. For example:
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);
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 settingssetMix
: Accepts three parameters (animation names and transition duration)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 trackanimationName
: Animation nameloop
: Whether to loopdelay
: Delay timeThe delay
parameter determines when the next animation starts:
delay > 0
(e.g., 1 second), the next animation begins 1 second after the previous one starts.delay = 0
, the next animation starts after the previous one finishes.delay < 0
, the next animation starts before the previous one finishes.Visual examples:
You can also use addEmptyAnimation to add empty animations that return to the initial state. This is useful for blending transitions.
setAnimation
and addAnimation
return a TrackEntry
object containing additional control parameters:
timeScale
: Controls playback speedanimationStart
: Sets the start timealpha
: Blending coefficient for the trackSee TrackEntry documentation for full details.
Using the AnimationState API triggers events like:
start
: When a new animation beginsend
: When an animation is removed or interruptedcomplete
: 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 */ },
})
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
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;
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');
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);
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