Outdated documentation
The article below is outdated and has been replaced by the Spine Runtimes Guide.
Using the Spine Runtimes
This article explains the basics common to all official runtimes.
Animation data can be exported from Spine as JSON Example or binary. This data contains the bone, attachment, and animation information needed to render the animations just as they appear in Spine. There are many Official runtimes for various game toolkits that can load this data and render the animations. All of the official runtimes use the same class names and basic structure explained here.
Source code
The source code for the official runtimes is available on GitHub and licensing Spine grants permission to use the runtimes in your applications. All of the source code is provided, which is essential for such a fundamental component of your games.
The code examples below use pseudo code that is easily translated to runtimes in any language. See the runtime specific pages (README.md files) on GitHub for more specific documentation.
Overview
This is a high level overview of the various pieces that make up a runtime. Click for full resolution.
Export
Spine can export numerical data for skeletal animation in your games. It can also export video and image sequences for traditional sprite animation. Please note that only the full version of Spine can export.
Skeletal animation
To export animation data from Spine, click the logo in the top left of the Spine window and choose Export. Choose JSON or Binary (note: binary is not currently supported by most runtimes) . Choose a directory and Spine will create a file for each skeleton in the project containing all the bone, attachment, and animation data for that skeleton. The name of each file will be the name of the skeleton.
For JSON export, the Pretty print setting makes the JSON output more human-readable. The Format setting controls the type of JSON that is output. JSON outputs valid JSON. JavaScript outputs valid JavaScript, where field names are only quoted as necessary. Minimal outputs a JSON-like format where both field names and values are only quoted as necessary. This can only be used with JSON parsers that are very lenient, such as the libgdx JSON parser.
Sprite animation
Alternatively, Spine can export an animation as a sequence of images which can be used as traditional sprite animation. This has a number of disadvantages:
- The images take up a lot of disk space.
- The decompressed images take up a large amount of memory at runtime.
- Sprite animation is not as smooth as interpolated Spine animation (sprite animation examples: Spineboy, Dragon ).
- Individual pieces of a skeleton cannot be tinted or attached on the fly at runtime.
- Procedural animation cannot be done.
- Animations cannot be mixed or crossfaded.
Still, sprite animation may be useful in some situations. A Spine runtime is not needed for sprite animation, since rendering is simply drawing images.
Texture atlas
In addition to the skeleton data exported from Spine, a runtime also needs a texture atlas (also known as a sprite sheet) which contains the texture regions to draw. In most game toolkits, changing which texture is being drawn is costly. Performance is increased by binding a large texture once, then drawing many portions of the large texture.
When creating skeletons in Spine, individual images are used as this is the easiest way to organize many images. To create a texture atlas for use at runtime, the individual images need to be packed into one or more larger images. Spine has a built-in texture packing.
Atlas
Some runtimes use a game toolkit specific texture atlas. Other runtimes have their own texture atlas implementation called Atlas. This loads texture atlas data in the "libgdx" format. Multiple backing pages are supported, though be warned that this can cause additional texture binds at runtime.
An atlas is loaded this way:
Atlas atlas = new Atlas("myAtlas.atlas", textureLoader);
Creating an atlas parses the atlas file, loading the data for where the regions are in the page images. The regions can be retrieved by name. The atlas supports whitespace stripping and rotation. Whitespace stripping removes the blank space at the edges of the packed regions and stores the original size. When drawn, the region must be offset by the blank pixels that were stripped. Rotation allows for better packing by allowing regions to be packed rotated by 90 degrees.
The atlas uses the TextureLoader to load the page images. The texture loader has two method which are used to create and dispose textures:
void unload (Object texture)
Often a runtime will provide a texture loader that knows how to create and dispose for a specific game toolkit. An implementation might look like this:
Texture texture = ...;
page.rendererObject = texture;
}
void unload (Object rendererObject) {
Texture texture = (Texture)rendererObject;
texture.dispose();
}
The AtlasPage has a rendererObject field which is an object (or void*). It holds any game toolkit specific object. This object will be used by the game toolkit specific code that renders the skeleton. The load method also must set the width and height on the page. The unload method receives the rendererObject and can cast it as necessary to dispose of the resources.
Loading
JSON or binary data is loaded using SkeletonJson or SkeletonBinary. Both have the same interface, which is a readSkeletonData method that parses the data and returns a SkeletonData instance:
SkeletonJson json = new SkeletonJson(attachmentLoader);
SkeletonData skeletonData = json.readSkeletonData("mySkeleton.json");
AttachmentLoader
The AttachmentLoader has a single method which is used to create attachment instances:
This returns a new attachment for the specified skin, attachment type, and name. Most attachments are a "region" attachment for rendering a texture region, but there can be other attachment types, such as a bounding box. The attachment loader allows attachments to be customized by application specific code. The built-in attachment types can be extended to customize rendering or other behavior.
The most common attachment loader has a texture atlas. In newAttachment it creates a RegionAttachment and populates it by looking up a texture region in the atlas using the attachment name. Code to do that might look like this:
Attachment newAttachment (Skin skin, AttachmentType type, String name) {
if (type != AttachmentType.region)
throw new Error("Unknown attachment type: " + type);
RegionAttachment attachment = new RegionAttachment(name);
TextureRegion region = textureAtlas.findRegion(name);
if (region == null) throw new Error("Region not found in atlas: " + name);
attachment.rendererObject = region;
return attachment;
}
Note the rendererObject field is runtime specific. Each runtime may have a different way of storing the texture region on the RegionAttachment. The runtime specific rendering needs to know how to access the texture region.
The attachment loader only creates a new attachment. It may do some configuration, such as setting a texture region, but additional configuration is done by the JSON or binary loader. For example, the loader may have code like this:
if (type == AttachmentType.region) {
RegionAttachment regionAttachment = (RegionAttachment)attachment;
regionAttachment.x = ...;
regionAttachment.y = ...;
regionAttachment.scaleX = ...;
regionAttachment.scaleY = ...;
regionAttachment.rotation = ...;
regionAttachment.width = ...;
regionAttachment.height = ...;
regionAttachment.updateOffset(regionAttachment);
}
AtlasAttachmentLoader
If using a runtime that has a Spine atlas, an AtlasAttachmentLoader is provided:
Atlas atlas = new Atlas("myAtlas.atlas", textureLoader);
AtlasAttachmentLoader attachmentLoader = new AtlasAttachmentLoader(atlas);
SkeletonJson json = new SkeletonJson(attachmentLoader);
SkeletonData skeletonData = json.readSkeletonData("mySkeleton.json");
Since this is very commonly used, the JSON and binary loaders for most runtimes will accept an atlas and create an atlas attachment loader internally:
Atlas atlas = new Atlas("myAtlas.atlas", textureLoader);
SkeletonJson json = new SkeletonJson(atlas);
SkeletonData skeletonData = json.readSkeletonData("mySkeleton.json");
Scaling
A scale can be specified on the JSON or binary loader which will scale the bone positions, image sizes, and animation translations:
SkeletonJson json = new SkeletonJson(attachmentLoader);
json.scale = 2;
SkeletonData skeletonData = json.readSkeletonData("mySkeleton.json");
This causes a skeleton to be drawn at a different size, twice as large in this example. This can be useful when using different sized images than were used when designing the skeleton in Spine. For example, if using images that are half the size than were used in Spine, a scale of 0.5 can be used. This is commonly used for games that can run with either low or high resolution texture atlases.
Using a loader scale changes the units used. For example, if a bone has a local position of 50,100 then with a scale of 2 it will be changed at load time to 100,200. This can be useful when not using a pixel unit scale at runtime, such as with Box2D.
A skeleton can also be scaled without affecting the units:
SkeletonJson json = new SkeletonJson(attachmentLoader);
SkeletonData skeletonData = json.readSkeletonData("mySkeleton.json");
BoneData root = skeletonData.findBone("root");
root.scaleX = 2;
root.scaleY = 2;
In this case, if a bone has a local position of 50,100 then it will remain at that position. When the bone's world SRT (scale, rotation, and translation) is computed (explained below), its position will be scaled.
SkeletonData
SkeletonData stores all the skins and animations as well as the setup pose's bone SRT, slot colors, and which slot attachments are visible. The setup pose is how the skeleton looks in Spine's Setup Mode.
SkeletonData and all other classes whose name ends with "Data" store information that is (typically) constant across multiple instances of the same skeleton. The data may be quite large, so this architecture avoids the need to load the same data multiple times.
Field | Description |
---|---|
name | The name of the skeleton as it appears in Spine. |
bones | A `BoneData` list, ordered so a parent always comes before all children. |
slots | A `SlotData` list, ordered using the slot draw order. |
animations | A list of all `Animation` for the skeleton. |
skins | A list of all `Skins` for the skeleton. |
defaultSkin | The skin containing all attachments that aren’t in a skin in Spine. |
Method | Parameters | Description |
---|---|---|
findBone | boneName | Finds a bone data by name. This does a string comparison for every bone. |
findBoneIndex | boneName | Finds a bone data index by name. This does a string comparison for every bone. |
findSlot | slotName | Finds a slot data by name. This does a string comparison for every slot. |
findSlotIndex | slotName | Finds a slot data index by name. This does a string comparison for every slot. |
findSkin | skinName | Finds a skin by name. This does a string comparison for every skin. |
findAnimation | animationName | Finds an animation by name. This does a string comparison for every animation. |
BoneData
BoneData stores the hierarchy of bones in the setup pose and their SRT.
Field | Description |
---|---|
name | The name of the bone as it appears in Spine. |
parent | The parent `BoneData`. |
length | The length of the bone. This is normally used solely to draw the bone. |
x,y | The local position of the bone relative to the parent bone. |
rotation | The local rotation of the bone. |
scaleX,scaleY | The local scale of the bone. |
inheritRotation | True if the local rotation is relative to the parent bone. |
inheritScale | True if the local scale is relative to the parent bone. |
SlotData
SlotData stores the slot color and which attachment is visible in the setup pose.
Field | Description |
---|---|
name | The name of the slot as it appears in Spine. |
boneData | The bone the slot belongs to. |
r,g,b,a | The color in the setup pose to tint the image for the slot. |
attachmentName | The name of the attachment visible for the slot in the setup pose, or null. |
additiveBlending | True if a region attachment in the slot should use additive blending when rendered. |
Skin
Skin is used to find an attachment for a slot by a name. It is simply a map where the key is a slot and a name and the value is an attachment. The name used in the key does not have to be the name of the attachment. This allows code and animations to set attachments by name without knowing what attachment is actually used.
For example, a skin might have a key [slot:head,name:head] and a value for that key [attachment:head-fish]. Another skin might have a key [slot:head,name:head] and a value [attachment:head-donkey]. The skin allows the name in the skin ("head") to be used without knowing which attachment is actually used ("head-fish" or "head-donkey").
All attachments that are not in a skin in Spine will appear at runtime in a skin named "default". When a skeleton needs to find an attachment by name, it first looks in its skin. If the attachment is not found, then it looks in the default skin.
See the skins video for configuring skins in Spine. See Using skins for how to apply skins at runtime.
Field | Description |
---|---|
name | The name of the skin as it appears in Spine. |
Method | Parameters | Description |
---|---|---|
getAttachment | slotIndex, name | Returns the attachment for the slot and name. `slotIndex` is the index in the skeleton data’s slot list. |
Animation
Animation has a list of timelines. Each timeline has a list of times and values which represent keyframes and knows how to apply the values to a skeleton for a given time.
Field | Description |
---|---|
name | The name of the animation as it appears in Spine. |
duration | The duration of the animation in seconds. This is computed by the JSON or binary loader to be the time of the last key, but can be adjusted manually. |
timelines | A list of timelines. |
Method | Parameters | Description |
---|---|---|
apply | skeleton, time, loop | Applies all keyframe values, interpolated for the specified time. Any current values are overwritten. |
mix | skeleton, time, loop, alpha | Applies all keyframe values, interpolated for the specified time and mixed with the current values. `alpha` is 0-1 and controls what percentage of the keyframe values are used. See below. |
Skeleton
Skeleton has a reference to a SkeletonData and stores the state for skeleton instance, which consists of the current pose's bone SRT, slot colors, and which slot attachments are visible. Multiple skeletons can use the same SkeletonData (which includes all animations, skins, and attachments).
Field | Description |
---|---|
data | The `SkeletonData` for the skeleton. |
bones | A `Bone` list, ordered the same as the skeleton data. |
slots | A `Slot` list, ordered the same as the skeleton data. |
drawOrder | A `Slot` list, initially ordered the same as the skeleton data. The order can be manipulated to change the order slots are drawn. |
skin | The currently active skin, or null. |
r,g,b,a | The color to tint the entire skeleton. |
time | Increases via `update(delta)` and allows slots to know how long an attachment has been visible. |
flipX,flipY | Flips the rendering of the skeleton horizontally and/or vertically. |
x,y | The drawing position of the skeleton in world coordinates. |
Method | Parameters | Description |
---|---|---|
updateWorldTransform | Computes the world SRT from the local SRT for each bone. See below. | |
setBonesToSetupPose | Sets the bones to the setup pose, using the values from the `BoneData` list in the `SkeletonData`. | |
setSlotsToSetupPose | Sets the slots to the setup pose, using the values from the `SlotData` list in the `SkeletonData`. | |
setToSetupPose | Sets the bones and slots to the setup pose. | |
findBone | boneName | Finds a bone by name. This does a string comparison for every bone. |
findBoneIndex | boneName | Finds a bone index by name. This does a string comparison for every bone. |
findSlot | slotName | Finds a slot by name. This does a string comparison for every slot. |
findSlotIndex | slotName | Finds a slot index by name. This does a string comparison for every slot. |
setSkin | skinName | Finds a skin by name and makes it the active skin. This does a string comparison for every skin. Note that setting the skin does not change which attachments are visisble. See Skin changes. |
getAttachment | slotName, attachmentName | Returns the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin, |
setAttachment | slotName, attachmentName | Sets the attachment for the slot and attachment name. The skeleton looks first in its skin, then in the skeleton data’s default skin, |
update | delta | Increments the skeleton’s `time` field. |
Bone
Bone has a reference to a BoneData and stores the hierarchy of bones and their SRT for the current pose.
Field | Description |
---|---|
data | The `BoneData` for the bone. |
parent | The parent `Bone`. |
x,y | The local position of the bone relative to the parent bone. |
rotation | The local rotation of the bone. |
scaleX,scaleY | The local scale of the bone. |
worldX,worldY | The world position of the bone. Readonly. |
worldRotation | The world rotation of the bone. Readonly. |
m00,m01,m10,m11 | The 2×2 world rotation matrix of the bone. Readonly. |
Method | Parameters | Description |
---|---|---|
updateWorldTransform | flipX,flipY | Computes the world SRT from the local SRT for this bone. Usually called by `Skeleton updateWorldTransform`. |
setToSetupPose | Sets the bone to the setup pose, using the values from the `BoneData`. Usually called by `Skeleton setBonesToSetupPose`. |
Slot
Slot has a reference to a SlotData and stores the slot color and which attachment is visible for the current pose.
Field | Description |
---|---|
data | The `SlotData` for the slot. |
bone | The bone the slot belongs to. |
r,g,b,a | The color to tint the image for the slot. |
attachment | The attachment visible for the slot, or null. |
attachmentTime | The time in seconds the attachment has been visible. |
Method | Description |
---|---|
setToSetupPose | Sets the slot to the setup pose, using the values from the `SlotData`. Usually called by `Skeleton setSlotsToSetupPose`. |
Attachments
An attachment has a name and a type, but otherwise can be anything: a texture region, bounding box, etc.
Field | Description |
---|---|
name | The name of the attachment as it appears in Spine. |
type | The type of the attachment. This enables the renderer to know if or how it should draw the attachment. |
Changing attachments
At any given time, a slot can have a single attachment or no attachment. The attachment for a slot can be changed by calling setAttachment on the Skeleton or a Slot. The attachment will stay until changed again.
// Finds the slot by name, finds the attachment by name in the skeleton's skin or default
// skin and sets it on the slot.
skeleton.setAttachment("slotName", "attachmentName");
// Attachments can be gotten from a skin or created manually (though this is advanced).
Attachment attachment = ...
// An attachment can be set directly on a slot.
skeleton.findSlot("slotName").setAttachment(attachment);
Attachments may be changed in other ways. Calling Skeleton setSlotsToSetupPose will change attachments. An animation may have keyframes that change attachments. Calling Skeleton setSkin may change attachments (see Skin changes).
RegionAttachment
The most common attachment type is the RegionAttachment, which has a texture region, size, and offset SRT relative to the bone it is attached to. Usually the attachment name is used to look up the texture region in a texture atlas, but this behavior is left up to the AttachmentLoader.
Field | Description |
---|---|
x,y | The offset position of the attachment relative to the bone. |
rotation | The offset rotation of the attachment relative to the bone. |
scaleX,scaleY | The offset scale of the attachment relative to the bone. |
width,height | The size the texture region will be drawn. |
offset | An array of 8 values that are the world positions of the 4 vertices as computed by `updateOffset`. |
uvs | An array of 8 values that are the texture coordinates of the 4 vertices as computed by `setUVs`. |
Method | Parameters | Description |
---|---|---|
updateOffset | Uses the size and offset SRT to compute the `offset`. | |
setUVs | u,v,u2,v2,rotate | Sets the `uvs`. If `rotate` is true, the UVs are rotated 90 degrees. |
computeVertices | x,y,bone,vertices | Uses the `offset` and bone world SRT to populate the vertices. The vertices are incremented by `x,y`, which is typically the skeleton position. |
Creating attachments
Attachments can be created programmatically. This can be useful when there are many attachments that would be tedious to create manually in Spine.
In Spine the offset SRT is defined by where an image is placed on a bone. When creating a RegionAttachment programmatically, some convention is needed to know where to place the attachment. For example, all attachments could be the same size and have the same offset SRT, then the art must positioned within this size so it appears in the correct place on the bone.
Rendering
Rendering is done by iterating the Skeleton drawOrder field, which is a Slot list. The renderer checks the type of each attachment and draws the attachments it knows about. Typically it knows how to draw a texture region from RegionAttachment.
Applying animations
Animation apply overwrites the current pose of a Skeleton with the pose from the animation at a specific time:
Animation walkAnimation = skeletonData.findAnimation("walk");
float animationTime = 0;
...
function render (float delta) {
animationTime += delta;
walkAnimation.apply(skeleton, animationTime, true); * true is for loop
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
}
In this example, the animationTime is incremented by the delta time since the last render. Next, apply is called which changes the bones and slots that have keyframes in the animation. If bones are changed, only the local SRT is changed. Next, updateWorldTransform is called to compute the world SRT for each bone. Lastly, the skeleton can now be rendered, which uses the world SRT for each bone.
The time passed to the apply method controls the speed of the animation. It can be decremented to play the animation backward. An animation is complete when the time is greater than the animation duration.
Because the animation time is not stored inside the animation, the animation is stateless and can be used for any number of skeleton instances.
Mixing animations
Animations can be mixed, which is often used for crossfading when animations change. Animation mix is similar to apply, accept instead of overwriting the current pose with the animation pose, it positions bones by interpolating between the current pose and the animation pose:
Animation walkAnimation = skeletonData.findAnimation("walk");
Animation jumpAnimation = skeletonData.findAnimation("jump");
float animationTime = 0;
...
function render (float delta) {
animationTime += delta;
walkAnimation.apply(skeleton, animationTime, true);
jumpAnimation.mix(skeleton, animationTime, true, 0.75); * 0.75 is the alpha parameter
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
}
In this example, first the walk animation is applied. This overwrites the skeleton's current pose. Next, mix is called which will compute the pose for the jump animation, then adjust the bones so they are 75% of the way between the current pose and the jump pose.
To crossfade animations using mix, the old one is applied, the new one is mixed, and the alpha parameter is adjusted from 0 to 1 over time. This causes the old animation to contribute less and less to the pose. Once the alpha parameter reaches 1, the old animation is completely overwritten by the new animation and the crossfade is complete.
AnimationState
Since applying animations with crossfading is very common, AnimationState makes it more convenient. First AnimationStateData is configured with the durations to crossfade each pair of animations. AnimationStateData is stateless and can be used with multiple AnimationState instances. Next, AnimationState takes an AnimationStateData and is told what animation to use. When the AnimationState animation changes, it does the mixing automatically.
AnimationStateData stateData = new AnimationStateData(skeletonData);
stateData.setMix("walk", "jump", 0.2f);
stateData.setMix("jump", "walk", 0.4f);
AnimationState state = new AnimationState(stateData);
state.setAnimation(0, "walk", true); * trackIndex, name, loop
...
function render (float delta) {
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
if (spacebar()) state.setAnimation(0, "jump", true);
}
In this example, the AnimationStateData is configured, an AnimationState is created, and the initial animation is set to "walk". In the game loop, update is called so the AnimationState can keep track of the time. Next, apply is called which will apply the animations to the skeleton. updateWorldTransform computes the world SRT of the bones, then the skeleton is rendered just as it was before. If spacebar is pressed, the animation is changed to "jump". The AnimationState will automatically crossfade the animation change.
AnimationState has the notion of "tracks" which are indexed starting at zero. The animation for each track is applied in sequence each frame, allowing animations to be applied on top of each other. Also, each track can have animations queued for later playback:
state.setAnimation(0, "walk", false);
state.addAnimation(0, "jump", false, 0); * trackIndex, name, loop, delay
state.addAnimation(0, "fly", true, 0);
In this example, first the "walk" animation is played. When the end of that animation is reached (loop is ignored), the "jump" animation is played. When the end of that animation is reached, the "fly" animation is played. The mix durations are taken into account when computing the end of an animation.
The delay parameter specifies how much time the previous animation plays before the animation is changed. A delay <= 0 is special and uses the duration of the previous animation plus the delay.
state.setAnimation(0, "walk", true);
state.addAnimation(0, "jump", false, 2.5);
state.addAnimation(0, "fly", true, -0.5);
In this example, first the "walk" animation is played. After 2.5 seconds, the "jump" animation is played. 0.5 seconds before the end of that animation is reached, the "fly" animation is played.
Field | Description |
---|---|
animation | The current animation for the animation state. |
time | The current time for the animation state. When a new animation is set, the time is reset to zero. |
Method | Parameters | Description |
---|---|---|
setAnimation | trackIndex,animation,loop | Sets the current animation. Any queued animations are cleared. |
addAnimation | trackIndex,animation,loop,delay | Queues an animation to be played after a delay. If delay is <= 0, the duration of previous animation is used plus the negative delay. |
clearTrack | trackIndex | Sets the current animation of a track to null and clears all queued animations. |
update | delta | Increases the animation state's `time` field. |
apply | skeleton | Poses the skeleton using the current animation and time. |
isComplete | Returns true of the time is greater than the current animation's duration. |
Animation changes
An animation only affects the bones and slots for which it has keyframes. This allows the state of the skeleton to be fully controlled by the application.
When animations are applied in sequence, a previous animation may have made changes to bones or slots that a subsequent animation does not have keyframes for. In some cases, the changes from the first animation may not be desired when the second animation is applied.
This can be solved be keying everything at the start of the second animation that the first animation affects. With many animations this quickly leads to everything being keyed at the start of every animation. This is suboptimal because each property that is keyed adds a small amount of overhead when the animation is applied each frame.
Another solution is to call setToSetupPose, setBonesToSetupPose, or setSlotsToSetupPose on the skeleton when the current animation is changed (or otherwise write code that changes the slots and bones as needed). This ensures a previous animation has not left the skeleton in an undesirable state without requiring a large number of keyframes. However, this will cause bones that aren't keyed in the second animation to be instantly set to the setup pose state. AnimationState won't be able to do crossfading for those bones.
Combining animations
Multiple animations can be applied in sequence to animate parts of the skeleton differently:
AnimationState state = new AnimationState(stateData);
state.setAnimation(0, "walk", true);
state.setAnimation(1, "shoot", false);
...
function render (float delta) {
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
}
In this example, first the "walk" animation is applied, then "shoot" is applied. If "shoot" only has keyframes for bones in the torso, the legs will not be affected and will remained posed by the "walk" animation.
Creating animations
Animations can be edited or created programmatically. For example, this might be useful to adjust an existing animation based on a character's distance from an object they are interacting with. Timelines are configured with keyframe values, added to the animation, then the animation is applied as normal.
Manipulating bones
Bones can be accessed programmatically. They can be manipulated to perform procedural animation:
Bone torso = skeleton.findBone("torso");
AnimationState state = new AnimationState(stateData);
state.setAnimation(0, "walk", true);
...
function render (float delta) {
state.update(delta);
state.apply(skeleton);
torso.rotation = ... // compute rotation for target
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
}
In this example, the "torso" bone's local rotation is adjusted. This is done after AnimationState apply poses the skeleton, but before Skeleton updateWorldTransform computes the world SRT for the bones. This can be used for dynamic behavior, such as having the skeleton look toward the mouse cursor.
All animations are relative to the setup pose. This means the BoneData can be adjusted to affect all animations:
Bone torso = skeleton.findBone("torso");
AnimationState state = new AnimationState(stateData);
state.setAnimation(0, "walk", true);
...
function render (float delta) {
torso.data.rotation = * compute rotation for target
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
}
This example is similar to the last one, except this time the BoneData rotation is modified. This is done before the animation is applied, because the animation uses the BoneData when computing the pose to apply.
The world SRT of a bone can be used to position game elements:
Bone rightHand = skeleton.findBone("right hand");
AnimationState state = new AnimationState(stateData);
state.setAnimation(0, "walk", true);
...
function render (float delta) {
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
renderSkeleton(skeleton);
renderParticles(rightHand.worldX, rightHand.worldY);
}
In this example, an animation is applied, the world SRT for the bones is computed, the skeleton is rendered, and then the world position of the "right hand" bone is used to draw particle effects. This can also be used for animation a UI by positioning UI elements. The world rotation and scale is also available.
Using skins
Skins allow all animations for a skeleton to be reused with different attachments.
For example, a skeleton has green attachments and purple attachments. An animation wants to change the "head" slot's attachment so the skeleton's eyes blink. If skins are not used, two animations are needed: one that attaches "blink-green" and one that attaches "blink-purple". This causes an explosion of the number of animations needed. If skins are used, only one animation is needed. It sets the "head" slot's attachment to "blink". If the "green" skin is active, "blink-green" is used. If the "purple" skin is active, "blink-purple" is used.
When not using animations that change attachments, skins are not needed. Skins can still be used to group attachments to make it easy to change how the skeleton looks, but this is not their primary goal. Application code can easily call setAttachment on the Skeleton or a Slot.
Skin changes
When setAttachment or getAttachment is called on a Skeleton, the skeleton finds the attachment by name by first looking in its skin (if any). If not found, it will look in the default skin of its SkeletonData. The skin for a Skeleton can be changed, which allows different attachments to be found by the same names.
When a new skin is set and the skeleton does not already have a skin, any attachments in the skin that are visible in the setup pose are attached.
When a new skin is set and the skeleton already has a skin, then attachments from the new skin are attached if the slot had the attachment from the old skin attached. Otherwise, no attachments are changed.
Creating skins
Skins can be created programmatically. This can be useful in the case where animations change attachments that are configurable by application code.
For example, a skeleton has a head that can be a dog head or a snake head. It also has wings that can be normal wings or burning wings. An animation changes both the head and wing attachments. To allow the skeleton to be configured with any combination of heads and wings, a skin can be created programmatically with the appropriate head and wing attachments.
This task may be simplified by creating skins in Spine for each head (dog, snake) and wing (normal, burning) solely to group the attachments. To configure the skeleton at runtime, get the attachments from the desired head and wing skins and put them in a new skin. Note: Spine can currently only show a single skin at once, so it won't be possible to preview the combined skin in the editor.