Spine is currently the most promising solution for 2d skeleton animation, featuring a lot of powerful features such as individual keyframing, skinning and more. The major advantages over traditional sprite sheets are that using skeletal animation uses far less memory and provides smoother animations and the ability to seamlessly shift from one animation to the next - something that makes your animations more vivid. Spine is currently offered for $60 and is definitely worth every cent of it. If you want to give it a try there's a export-disabled
trial version for download. There are
a couple of alternatives to Spine, but nothing comes close to its functionality. It's currently the most powerful 2d skeletal animation tool.
Currently Spine does not have a texture packer built-in, so we will be using
TexturePacker developed by Andreas Löw. It's a great tool that recently got even better with a content protection feature. If you haven't already you should definitely
check it out!
Lets start by preparing the data. Once you've created your animations you need to get them out of Spine and into your project. This is done using the Export function.
Lets write some code!
Lets start by adding
Starling into our
Main class:
package {
import flash.display.Sprite;
import flash.events.Event;
import starling.core.Starling;
public class Main extends Sprite {
private var _starling:Starling;
public function Main():void {
if (stage)
init();
else
addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
_starling = new Starling(MyStarlingApp, stage);
_starling.start();
}
}
}
Next step is to add the
MyStarlingApp class that we used as a constructor argument for
Starling. Create a new class named
MyStarlingApp and make it inherit from
starling.display.Sprite.
Embed the data files using
[Embed(source="..",mimeType="application/octet-stream")] inside the class.
Here's the complete code, I'll return to some of the more specific parts:
package {
import flash.utils.setTimeout;
import spine.animation.AnimationStateData;
import spine.starling.SkeletonAnimation;
import spine.starling.StarlingAtlasAttachmentLoader;
import spine.SkeletonData;
import spine.SkeletonJson;
import starling.core.Starling;
import starling.display.Sprite;
import starling.textures.Texture;
import starling.textures.TextureAtlas;
public class MyStarlingApp extends Sprite {
[Embed(source="spineboy.xml",mimeType="application/octet-stream")]
static public const SpineBoyAtlasXml:Class;
[Embed(source="spineboy.png")]
static public const SpineBoyAtlasTexture:Class;
[Embed(source="spineboy.json",mimeType="application/octet-stream")]
static public const SpineBoyJson:Class;
private var _skeleton:SkeletonAnimation;
public function MyStarlingApp() {
var texture:Texture = Texture.fromBitmap(new SpineBoyAtlasTexture());
var xml:XML = XML(new SpineBoyAtlasXml());
var atlas:TextureAtlas = new TextureAtlas(texture, xml);
var attachmentLoader:StarlingAtlasAttachmentLoader = new StarlingAtlasAttachmentLoader(atlas);
var json:SkeletonJson = new SkeletonJson(attachmentLoader);
var skeletonData:SkeletonData = json.readSkeletonData(new SpineBoyJson());
var stateData:AnimationStateData = new AnimationStateData(skeletonData);
// Setup how to morph between different animation sets
stateData.setMixByName("walk", "jump", 0.3);
stateData.setMixByName("jump", "walk", 0.4);
stateData.setMixByName("jump", "jump", 0.2);
_skeleton = new SkeletonAnimation(skeletonData, false, stateData);
_skeleton.x = 400;
_skeleton.y = 460;
_skeleton.state.setAnimationByName(0, "walk", true);
setTimeout(makeJump, 3000);
addChild(_skeleton);
Starling.juggler.add(_skeleton);
}
private function makeJump():void {
_skeleton.state.setAnimationByName(0, "jump", false);
_skeleton.state.addAnimationByName(0, "walk", true, 2); // delay next animation by 2 seconds
}
}
}
All animations are referred to by the name given in
Spine. For the Spineboy demo project there's two animations: "walk" and "jump".
One of the powerful features of
Spine is the smooth transitions between animations. This is made by defining "mixes" on the
AnimationStateData object using the
setMixByName function. The first parameter is the name of the animation that the transition will be made from, the second the animation that will be switched to and the third parameter is the transition time. In the code above the transition between "walk" and "jump" is set to 0.3 seconds. If no mixes have been defined the change in animation will occur instantly. To make the animations smoother you should set up mixes for all animation changes that can occur.
To start an animation call the
setAnimationByName function of the
SkeletonAnimation object's
state property. The first parameter is track, the second is the name of the animation and the third is if the animation should be looped.
If you wish to play one animation and immediately after another, use
addAnimationByName after
setAnimationByName in order to stack animations that will be played after each other.
Download source codeThe FlashDevelop project except the Spineboy data, which you can get from the Spine runtime download.