diff --git a/spine-c/src/spine/AnimationState.c b/spine-c/src/spine/AnimationState.c index b2293f865..327b8be47 100644 --- a/spine-c/src/spine/AnimationState.c +++ b/spine-c/src/spine/AnimationState.c @@ -94,7 +94,7 @@ void AnimationState_addAnimation (AnimationState* self, Animation* animation, in if (delay <= 0) { if (previousAnimation) - delay = self->animation->duration - AnimationStateData_getMix(self->data, self->animation, previousAnimation) + delay; + delay = previousAnimation->duration - AnimationStateData_getMix(self->data, previousAnimation, animation) + delay; else delay = 0; } @@ -141,6 +141,7 @@ void AnimationState_clearAnimation (AnimationState* self) { } void AnimationState_update (AnimationState* self, float delta) { + _Entry* next; _Internal* internal = SUB_CAST(_Internal, self); self->time += delta; @@ -149,7 +150,7 @@ void AnimationState_update (AnimationState* self, float delta) { if (internal->queue && self->time >= internal->queue->delay) { _AnimationState_setAnimation(self, internal->queue->animation, internal->queue->loop); - _Entry* next = internal->queue->next; + next = internal->queue->next; FREE(internal->queue); internal->queue = next; } diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index 8b6ef6b8e..874d7f209 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -33,9 +33,10 @@ namespace Spine { public float Time { get; set; } public bool Loop { get; set; } private Animation previous; - float previousTime; - bool previousLoop; - float mixTime, mixDuration; + private float previousTime; + private bool previousLoop; + private float mixTime, mixDuration; + private List queue = new List(); public AnimationState (AnimationStateData data) { if (data == null) throw new ArgumentNullException("data cannot be null."); @@ -46,6 +47,14 @@ namespace Spine { Time += delta; previousTime += delta; mixTime += delta; + + if (queue.Count > 0) { + QueueEntry entry = queue[0]; + if (Time >= entry.delay) { + SetAnimationInternal(entry.animation, entry.loop); + queue.RemoveAt(0); + } + } } public void Apply (Skeleton skeleton) { @@ -62,13 +71,40 @@ namespace Spine { Animation.Apply(skeleton, Time, Loop); } - public void SetAnimation (String animationName, bool loop) { - Animation animation = Data.SkeletonData.FindAnimation(animationName); - if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - SetAnimation(animation, loop); + public void AddAnimation (String animationName, bool loop) { + AddAnimation(animationName, loop, 0); } - public void SetAnimation (Animation animation, bool loop) { + public void AddAnimation (String animationName, bool loop, float delay) { + Animation animation = Data.SkeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + AddAnimation(animation, loop, delay); + } + + public void AddAnimation (Animation animation, bool loop) { + AddAnimation(animation, loop, 0); + } + + /** Adds an animation to be played delay seconds after the current or last queued animation. + * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ + public void AddAnimation (Animation animation, bool loop, float delay) { + QueueEntry entry = new QueueEntry(); + entry.animation = animation; + entry.loop = loop; + + if (delay <= 0) { + Animation previousAnimation = queue.Count == 0 ? Animation : queue[queue.Count - 1].animation; + if (previousAnimation != null) + delay = previousAnimation.Duration - Data.GetMix(previousAnimation, animation) + delay; + else + delay = 0; + } + entry.delay = delay; + + queue.Add(entry); + } + + private void SetAnimationInternal (Animation animation, bool loop) { previous = null; if (animation != null && Animation != null) { mixDuration = Data.GetMix(Animation, animation); @@ -84,13 +120,25 @@ namespace Spine { Time = 0; } + public void SetAnimation (String animationName, bool loop) { + Animation animation = Data.SkeletonData.FindAnimation(animationName); + if (animation == null) throw new ArgumentException("Animation not found: " + animationName); + SetAnimation(animation, loop); + } + + public void SetAnimation (Animation animation, bool loop) { + queue.Clear(); + SetAnimationInternal(animation, loop); + } + public void ClearAnimation () { previous = null; Animation = null; + queue.Clear(); } /** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */ - public bool isComplete () { + public bool IsComplete () { return Animation == null || Time >= Animation.Duration; } @@ -99,3 +147,9 @@ namespace Spine { } } } + +class QueueEntry { + public Spine.Animation animation; + public bool loop; + public float delay; +} diff --git a/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 30db44760..d363bc8f6 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -25,15 +25,17 @@ package com.esotericsoftware.spine; -import com.badlogic.gdx.math.MathUtils; +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Pools; /** Stores state for an animation and automatically mixes between animations. */ public class AnimationState { private final AnimationStateData data; - Animation current, previous; - float currentTime, previousTime; - boolean currentLoop, previousLoop; - float mixTime, mixDuration; + private Animation current, previous; + private float currentTime, previousTime; + private boolean currentLoop, previousLoop; + private float mixTime, mixDuration; + private Array queue = new Array(); public AnimationState (AnimationStateData data) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -44,6 +46,15 @@ public class AnimationState { currentTime += delta; previousTime += delta; mixTime += delta; + + if (queue.size > 0) { + QueueEntry entry = queue.first(); + if (currentTime >= entry.delay) { + setAnimationInternal(entry.animation, entry.loop); + Pools.free(entry); + queue.removeIndex(0); + } + } } public void apply (Skeleton skeleton) { @@ -63,18 +74,15 @@ public class AnimationState { public void clearAnimation () { previous = null; current = null; + clearQueue(); } - /** @see #setAnimation(Animation, boolean) */ - public void setAnimation (String animationName, boolean loop) { - Animation animation = data.getSkeletonData().findAnimation(animationName); - if (animation == null) throw new IllegalArgumentException("Animation not found: " + animationName); - setAnimation(animation, loop); + private void clearQueue () { + Pools.freeAll(queue); + queue.clear(); } - /** Set the current animation. The current animation time is set to 0. - * @param animation May be null. */ - public void setAnimation (Animation animation, boolean loop) { + private void setAnimationInternal (Animation animation, boolean loop) { previous = null; if (animation != null && current != null) { mixDuration = data.getMix(current, animation); @@ -90,6 +98,57 @@ public class AnimationState { currentTime = 0; } + /** @see #setAnimation(Animation, boolean) */ + public void setAnimation (String animationName, boolean loop) { + Animation animation = data.getSkeletonData().findAnimation(animationName); + if (animation == null) throw new IllegalArgumentException("Animation not found: " + animationName); + setAnimation(animation, loop); + } + + /** Set the current animation. Any queued animations are cleared and the current animation time is set to 0. + * @param animation May be null. */ + public void setAnimation (Animation animation, boolean loop) { + clearQueue(); + setAnimationInternal(animation, loop); + } + + /** @see #addAnimation(Animation, boolean) */ + public void addAnimation (String animationName, boolean loop) { + addAnimation(animationName, loop, 0); + } + + /** @see #addAnimation(Animation, boolean, float) */ + public void addAnimation (String animationName, boolean loop, float delay) { + Animation animation = data.getSkeletonData().findAnimation(animationName); + if (animation == null) throw new IllegalArgumentException("Animation not found: " + animationName); + addAnimation(animation, loop, delay); + } + + /** Adds an animation to be played delay seconds after the current or last queued animation, taking into account any mix + * duration. */ + public void addAnimation (Animation animation, boolean loop) { + addAnimation(animation, loop, 0); + } + + /** Adds an animation to be played delay seconds after the current or last queued animation. + * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ + public void addAnimation (Animation animation, boolean loop, float delay) { + QueueEntry entry = Pools.obtain(QueueEntry.class); + entry.animation = animation; + entry.loop = loop; + + if (delay <= 0) { + Animation previousAnimation = queue.size == 0 ? current : queue.peek().animation; + if (previousAnimation != null) + delay = previousAnimation.getDuration() - data.getMix(previousAnimation, animation) + delay; + else + delay = 0; + } + entry.delay = delay; + + queue.add(entry); + } + /** @return May be null. */ public Animation getAnimation () { return current; @@ -116,4 +175,10 @@ public class AnimationState { public String toString () { return (current != null && current.getName() != null) ? current.getName() : super.toString(); } + + static private class QueueEntry { + Animation animation; + boolean loop; + float delay; + } } diff --git a/spine-libgdx/test/com/esotericsoftware/spine/AnimationStateTest.java b/spine-libgdx/test/com/esotericsoftware/spine/AnimationStateTest.java index 0c5d5660e..2d4d54efe 100644 --- a/spine-libgdx/test/com/esotericsoftware/spine/AnimationStateTest.java +++ b/spine-libgdx/test/com/esotericsoftware/spine/AnimationStateTest.java @@ -54,7 +54,7 @@ public class AnimationStateTest extends ApplicationAdapter { // Define mixing between animations. AnimationStateData stateData = new AnimationStateData(skeletonData); - stateData.setMix("walk", "jump", 0.4f); + stateData.setMix("walk", "jump", 0.2f); stateData.setMix("jump", "walk", 0.4f); state = new AnimationState(stateData); diff --git a/spine-sfml/example/main.cpp b/spine-sfml/example/main.cpp index 81df65e09..950459871 100644 --- a/spine-sfml/example/main.cpp +++ b/spine-sfml/example/main.cpp @@ -41,11 +41,11 @@ void spineboy () { // Configure mixing. AnimationStateData* stateData = AnimationStateData_create(skeletonData); - AnimationStateData_setMixByName(stateData, "walk", "jump", 0.4f); + AnimationStateData_setMixByName(stateData, "walk", "jump", 0.2f); AnimationStateData_setMixByName(stateData, "jump", "walk", 0.4f); SkeletonDrawable* drawable = new SkeletonDrawable(skeletonData, stateData); - drawable->timeScale = 0.5f; + drawable->timeScale = 1; Skeleton* skeleton = drawable->skeleton; skeleton->flipX = false; @@ -57,6 +57,12 @@ void spineboy () { Skeleton_updateWorldTransform(skeleton); AnimationState_setAnimationByName(drawable->state, "walk", true); + AnimationState_addAnimationByName(drawable->state, "jump", false, 0); + AnimationState_addAnimationByName(drawable->state, "walk", true, 0); + AnimationState_addAnimationByName(drawable->state, "jump", false, 3); + AnimationState_addAnimationByName(drawable->state, "walk", true, 0); + AnimationState_addAnimationByName(drawable->state, 0, true, 0); + AnimationState_addAnimationByName(drawable->state, "walk", false, 1); sf::RenderWindow window(sf::VideoMode(640, 480), "Spine SFML"); window.setFramerateLimit(60); @@ -69,11 +75,11 @@ void spineboy () { float delta = deltaClock.getElapsedTime().asSeconds(); deltaClock.restart(); - if (drawable->state->loop) { - if (drawable->state->time > 2) AnimationState_setAnimationByName(drawable->state, "jump", false); - } else { - if (drawable->state->time > 1) AnimationState_setAnimationByName(drawable->state, "walk", true); - } + /*if (drawable->state->loop) { + if (drawable->state->time > 2) AnimationState_setAnimationByName(drawable->state, "jump", false); + } else { + if (drawable->state->time > 1) AnimationState_setAnimationByName(drawable->state, "walk", true); + }*/ drawable->update(delta); diff --git a/spine-xna/example/src/ExampleGame.cs b/spine-xna/example/src/ExampleGame.cs index 2b6f113b3..0442d76f1 100644 --- a/spine-xna/example/src/ExampleGame.cs +++ b/spine-xna/example/src/ExampleGame.cs @@ -40,8 +40,7 @@ namespace Spine { GraphicsDeviceManager graphics; SkeletonRenderer skeletonRenderer; Skeleton skeleton; - Animation animation; - float time; + AnimationState state; public Example () { graphics = new GraphicsDeviceManager(this); @@ -59,13 +58,25 @@ namespace Spine { protected override void LoadContent () { skeletonRenderer = new SkeletonRenderer(GraphicsDevice); - Atlas atlas = new Atlas("data/goblins.atlas", new XnaTextureLoader(GraphicsDevice)); + String name = "spineboy"; // "goblins"; + + Atlas atlas = new Atlas("data/" + name + ".atlas", new XnaTextureLoader(GraphicsDevice)); SkeletonJson json = new SkeletonJson(atlas); - skeleton = new Skeleton(json.ReadSkeletonData("data/goblins.json")); - skeleton.SetSkin("goblingirl"); + skeleton = new Skeleton(json.ReadSkeletonData("data/" + name + ".json")); + if (name == "goblins") skeleton.SetSkin("goblingirl"); skeleton.SetSlotsToBindPose(); // Without this the skin attachments won't be attached. See SetSkin. - skeleton.SetAttachment("left hand item", "spear"); - animation = skeleton.Data.FindAnimation("walk"); + + // Define mixing between animations. + AnimationStateData stateData = new AnimationStateData(skeleton.Data); + if (name == "spineboy") { + stateData.SetMix("walk", "jump", 0.2f); + stateData.SetMix("jump", "walk", 0.4f); + } + + state = new AnimationState(stateData); + state.SetAnimation("walk", false); + state.AddAnimation("jump", false); + state.AddAnimation("walk", true); skeleton.RootBone.X = 320; skeleton.RootBone.Y = 440; @@ -89,8 +100,8 @@ namespace Spine { protected override void Draw (GameTime gameTime) { GraphicsDevice.Clear(Color.Black); - time += gameTime.ElapsedGameTime.Milliseconds / 1000f; - animation.Apply(skeleton, time, true); + state.Update(gameTime.ElapsedGameTime.Milliseconds / 1000f); + state.Apply(skeleton); skeleton.UpdateWorldTransform(); skeletonRenderer.Begin(); skeletonRenderer.Draw(skeleton);