Using Spine runtimes

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

The code examples below use psuedo code that is easily translated to runtimes in any language. See the runtime specific documentation for examples in the appropriate langauge Note: runtime specific documentation is under construction.

Source code

All of the source code for the official runtimes is available on github. All official Spine runtimes are open source software, licensed under the Simplified BSD License. With these open source runtimes you have full control over the runtime code, which is a fundamental part of your application.

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 chooseExport. 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. TheFormat setting controls the type of JSON that is output. JSON outputs valid JSON. JavaScriptoutputs 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: spineboydragon).
  • 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. In the near future this will be built into Spine, but for now an external tool must be used such as:

  • libgdx TexturePacker This is a free, open source tool written and maintained by the author of Spine. It can pack all images for an entire application in one go. It is configured via filename and directory conventions, with a pack.json file for further configuration. While this tool is intended to be used from the command line, a GUI for it is available (though this is maintained by others).
  • Texture Packer Pro This is a payware, closed source tool that has both GUI and command line interfaces, many features, and very nice documentation. Be sure to choose the “libgdx” output format if using a Spine runtime Atlas.

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:

TextureLoader textureLoader = ...
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 load (AtlasPage page, String path)
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:

void load (AtlasPage page, String path) {
   Texture texture = ...;
   page.rendererObject = texture;
   page.width = texture.width;
   page.height = texture.height;
}

void unload (Object texture) {
   ((Texture)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 SkeletonDatainstance:

AttachmentLoader attachmentLoader = ...
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:

Attachment newAttachment (Skin skin, AttachmentType type, String name)

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 aRegionAttachment and populates it by looking up a texture region in the atlas using the attachment name. Code to do that might look like this:

TextureAtlas textureAtlas = ...

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:

Attachment attachment = attachmentLoader.newAttachment(skin, type, name);
if (type == 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:

TextureLoader textureLoader = ...
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:

TextureLoader textureLoader = ...
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:

AttachmentLoader attachmentLoader = ...
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:

AttachmentLoader attachmentLoader = ...
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.

FieldDescription
name The name of the skeleton as it appears in Spine.
bones BoneData list, ordered so a parent always comes before all children.
slots 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.
MethodParametersDescription
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.

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

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

FieldDescription
name The name of the skin as it appears in Spine.
MethodParametersDescription
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.

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

FieldDescription
data The SkeletonData for the skeleton.
bones Bone list, ordered the same as the skeleton data.
slots Slot list, ordered the same as the skeleton data.
drawOrder 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.
MethodParametersDescription
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 Changing skins.
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.

FieldDescription
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.
MethodParametersDescription
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.

FieldDescription
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.
MethodParametersDescription
setToSetupPose   Sets the slot to the setup pose, using the values from theSlotData. 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.

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

Attachments may be changed in other ways. Calling Skeleton setSlotsToSetupPose will change attachments. An animation may have keyframes that change attachments. Calling Skeleton setSkinmay change attachments (see Changing skins).

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.

FieldDescription
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 byupdateOffset.
uvs An array of 8 values that are the texture coordinates of the 4 vertices as computed by setUVs.
MethodParametersDescription
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 aRegionAttachment 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:

Skeleton skeleton = new Skeleton(skeletonData);
Animation walkAnimation = skeleton.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:

Skeleton skeleton = new Skeleton(skeletonData);
Animation walkAnimation = skeleton.findAnimation("walk");
Animation jumpAnimation = skeleton.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 AnimationStateinstances. Next, AnimationState takes an AnimationStateData and is told what animation to use. When the AnimationState animation changes, it does the mixing automatically.

Skeleton skeleton = new Skeleton(skeletonData);
AnimationStateData stateData = new AnimationStateData(skeletonData);
stateData.setMix("walk", "jump", 0.2f);
stateData.setMix("jump", "walk", 0.4f);
AnimationState state = new AnimationState(stateData);
state.setAnimation("walk", true); // true is for loop
...
function render (float delta) {
   state.update(delta);
   state.apply(skeleton);
   skeleton.updateWorldTransform();
   renderSkeleton(skeleton);
   if (spacebar()) state.setAnimation("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 can also queue animations to be played in sequence:

AnimationState state = new AnimationState(stateData);
state.setAnimation("walk", false);
state.addAnimation("jump", false);
state.addAnimation("fly", true);

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. Alternatively, a delay can be specified for when to change animations:

AnimationState state = new AnimationState(stateData);
state.setAnimation("walk", true);
state.addAnimation("jump", false, 2.5);
state.addAnimation("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.

FieldDescription
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.
MethodParametersDescription
setAnimation animation,loop Sets the current animation. Any queued animations are cleared.
addAnimation 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.
clearAnimation   Sets the current animation 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.

A better solution is to call setToSetupPosesetBonesToSetupPose, or setSlotsToSetupPose on the skeleton when the current animation is changed. This ensures a previous animation has not left the skeleton in an undesirable state without requiring a large number of keyframes.

Combining animations

Multiple animations can be applied in sequence to animate parts of the skeleton differently:

Skeleton skeleton = new Skeleton(skeletonData);
AnimationState state1 = new AnimationState(stateData);
state1.setAnimation("walk", true);
AnimationState state2 = new AnimationState(stateData);
state2.setAnimation("shoot", false);
...
function render (float delta) {
   state1.update(delta);
   state2.update(delta);
   state1.apply(skeleton);
   state2.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:

Skeleton skeleton = new Skeleton(skeletonData);
Bone torso = skeleton.findBone("torso");
AnimationState state = new AnimationState(stateData);
state.setAnimation("walk", true);
...
function render (float delta) {
   state.update(delta);
   state.apply(skeleton);
   torso.rotation = computeRotationForTarget();
   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:

Skeleton skeleton = new Skeleton(skeletonData);
Bone torso = skeleton.findBone("torso");
AnimationState state = new AnimationState(stateData);
state.setAnimation("walk", true);
...
function render (float delta) {
   torso.data.rotation = computeRotationForTarget();
   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:

Skeleton skeleton = new Skeleton(skeletonData);
Bone rightHand = skeleton.findBone("right hand");
AnimationState state = new AnimationState(stateData);
state.setAnimation("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.

Changing skins

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 itsSkeletonData. 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, no attachments are changed because the runtime doesn’t know which attachments from the new skin should be attached. If the setup pose has attachments from the skin, calling setSlotsToSetupPose on the Skeleton after setting the skin may attach the desired attachments. Otherwise, use setAttachment on theSkeleton or appropriate Slot to attach the desired attachments.

When a new skin is set and the skeleton already has a skin, then attachments from the new skin are attached if an attachment with the same name in the old skin was 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.

posted @ 2013-08-08 17:43  samxu  阅读(744)  评论(0)    收藏  举报