diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs index 66683a9e9..5ed439f72 100644 --- a/spine-csharp/src/AnimationState.cs +++ b/spine-csharp/src/AnimationState.cs @@ -37,24 +37,24 @@ using System.Collections.Generic; namespace Spine { public class AnimationState { private AnimationStateData data; - private Animation current, previous; - private float currentTime, currentLastTime, previousTime; - private bool currentLoop, previousLoop; - private QueueEntry currentQueueEntry; + private QueuedAnimation current, previous; private float mixTime, mixDuration; + private List queue = new List(); private List events = new List(); - private List queue = new List(); public AnimationStateData Data { get { return data; } } - public Animation Animation { get { return current; } } - public bool Loop { get { return currentLoop; } set { currentLoop = value; } } - + public List Queue { get { return queue; } } + public QueuedAnimation Current { get { return current; } } + public Animation Animation { + get { return current != null ? current.animation : null; } + } public float Time { - get { return currentTime; } - set { - currentTime = value; - currentLastTime = value - 0.00001f; - } + get { return current != null ? current.time : 0; } + set { if (current != null) current.Time = value; } + } + public bool Loop { + get { return current != null ? current.loop : false; } + set { if (current != null) current.Loop = value; } } public event EventHandler Start; @@ -68,54 +68,63 @@ namespace Spine { } public void Update (float delta) { - currentTime += delta; - previousTime += delta; - mixTime += delta; + QueuedAnimation current = this.current; + if (current == null) return; - if (current != null) { - float duration = current.duration; - if (currentLoop ? (currentLastTime % duration > currentTime % duration) - : (currentLastTime < duration && currentTime >= duration)) { - int count = (int)(currentTime / duration); - if (currentQueueEntry != null) currentQueueEntry.OnComplete(this, count); - if (Complete != null) Complete(this, new CompleteArgs(count)); - } + float time = current.time; + float duration = current.endTime; + + current.time = time + delta; + if (previous != null) { + previous.time += delta; + mixTime += delta; + } + + // Check if completed the animation or a loop iteration. + if (current.loop ? (current.lastTime % duration > time % duration) : (current.lastTime < duration && time >= duration)) { + int count = (int)(time / duration); + current.OnComplete(this, count); + if (Complete != null) Complete(this, new CompleteArgs(count)); } if (queue.Count > 0) { - QueueEntry entry = queue[0]; - if (currentTime >= entry.delay) { - SetAnimationInternal(entry.animation, entry.loop, entry); - queue.RemoveAt(0); + QueuedAnimation entry = queue[0]; + if (time >= entry.delay) { + if (entry.animation == null) + ClearAnimation(); + else { + SetAnimationEntry(entry); + queue.RemoveAt(0); + } } } } public void Apply (Skeleton skeleton) { + QueuedAnimation current = this.current; if (current == null) return; List events = this.events; events.Clear(); + QueuedAnimation previous = this.previous; if (previous != null) { - previous.Apply(skeleton, int.MaxValue, previousTime, previousLoop, null); + previous.animation.Apply(skeleton, int.MaxValue, previous.time, previous.loop, null); float alpha = mixTime / mixDuration; if (alpha >= 1) { alpha = 1; - previous = null; + this.previous = null; } - current.Mix(skeleton, currentLastTime, currentTime, currentLoop, events, alpha); + current.animation.Mix(skeleton, current.lastTime, current.time, current.loop, events, alpha); } else - current.Apply(skeleton, currentLastTime, currentTime, currentLoop, events); + current.animation.Apply(skeleton, current.lastTime, current.time, current.loop, events); - if (Event != null || currentQueueEntry != null) { - foreach (Event e in events) { - if (currentQueueEntry != null) currentQueueEntry.OnEvent(this, e); - if (Event != null) Event(this, new EventTriggeredArgs(e)); - } + foreach (Event e in events) { + current.OnEvent(this, e); + if (Event != null) Event(this, new EventTriggeredArgs(e)); } - currentLastTime = currentTime; + current.lastTime = current.time; } public void ClearAnimation () { @@ -124,72 +133,73 @@ namespace Spine { queue.Clear(); } - private void SetAnimationInternal (Animation animation, bool loop, QueueEntry entry) { + private void SetAnimationEntry (QueuedAnimation entry) { previous = null; + + QueuedAnimation current = this.current; if (current != null) { - if (currentQueueEntry != null) currentQueueEntry.OnEnd(this); + current.OnEnd(this); if (End != null) End(this, EventArgs.Empty); - if (animation != null) { - mixDuration = data.GetMix(current, animation); - if (mixDuration > 0) { - mixTime = 0; - previous = current; - previousTime = currentTime; - previousLoop = currentLoop; - } + mixDuration = data.GetMix(current.animation, entry.animation); + if (mixDuration > 0) { + mixTime = 0; + previous = current; } } - current = animation; - currentLoop = loop; - currentTime = 0; - currentLastTime = 0; - currentQueueEntry = entry; + this.current = entry; - if (currentQueueEntry != null) currentQueueEntry.OnStart(this); + entry.OnStart(this); if (Start != null) Start(this, EventArgs.Empty); } - public void SetAnimation (String animationName, bool loop) { + public QueuedAnimation SetAnimation (String animationName, bool loop) { Animation animation = data.skeletonData.FindAnimation(animationName); if (animation == null) throw new ArgumentException("Animation not found: " + animationName); - SetAnimation(animation, loop); + return 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. - * @param listener May be null. */ - public void SetAnimation (Animation animation, bool loop) { + /** Set the current animation. Any queued animations are cleared. */ + public QueuedAnimation SetAnimation (Animation animation, bool loop) { queue.Clear(); - SetAnimationInternal(animation, loop, null); + QueuedAnimation entry = new QueuedAnimation(); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.Duration; + SetAnimationEntry(entry); + return entry; } - public QueueEntry AddAnimation (String animationName, bool loop) { + public QueuedAnimation AddAnimation (String animationName, bool loop) { return AddAnimation(animationName, loop, 0); } - public QueueEntry AddAnimation (String animationName, bool loop, float delay) { + public QueuedAnimation AddAnimation (String animationName, bool loop, float delay) { Animation animation = data.skeletonData.FindAnimation(animationName); if (animation == null) throw new ArgumentException("Animation not found: " + animationName); return AddAnimation(animation, loop, delay); } - public QueueEntry AddAnimation (Animation animation, bool loop) { + public QueuedAnimation AddAnimation (Animation animation, bool loop) { return 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 QueueEntry AddAnimation (Animation animation, bool loop, float delay) { - QueueEntry entry = new QueueEntry(); + public QueuedAnimation AddAnimation (Animation animation, bool loop, float delay) { + QueuedAnimation entry = new QueuedAnimation(); entry.animation = animation; entry.loop = loop; + entry.time = 0; + entry.endTime = animation != null ? animation.Duration : 0; if (delay <= 0) { - Animation previousAnimation = queue.Count == 0 ? current : queue[queue.Count - 1].animation; - if (previousAnimation != null) - delay = previousAnimation.duration - data.GetMix(previousAnimation, animation) + delay; - else + QueuedAnimation previousEntry = queue.Count > 0 ? queue[queue.Count - 1] : current; + if (previousEntry != null) { + delay += previousEntry.endTime; + if (animation != null) delay += -data.GetMix(previousEntry.animation, animation); + } else delay = 0; } entry.delay = delay; @@ -200,11 +210,12 @@ namespace Spine { /** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */ public bool IsComplete () { - return current == null || currentTime >= current.duration; + return current == null || current.time >= current.endTime; } override public String ToString () { - return (current != null && current.name != null) ? current.name : base.ToString(); + if (current == null || current.animation == null) return ""; + return current.animation.Name; } } @@ -224,10 +235,23 @@ namespace Spine { } } - public class QueueEntry { - internal Spine.Animation animation; + public class QueuedAnimation { + internal Animation animation; internal bool loop; - internal float delay; + internal float delay, time, lastTime, endTime; + + public Animation Animation { get { return animation; } } + public bool Loop { get { return loop; } set { loop = value; } } + public float Delay { get { return delay; } set { delay = value; } } + public float EndTime { get { return EndTime; } set { EndTime = value; } } + + public float Time { + get { return time; } + set { + time = value; + if (lastTime < value) lastTime = value; + } + } public event EventHandler Start; public event EventHandler End; diff --git a/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java b/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java index 6f8ef3a08..17955dd06 100644 --- a/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java +++ b/spine-libgdx/src/com/esotericsoftware/spine/AnimationState.java @@ -34,19 +34,17 @@ package com.esotericsoftware.spine; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.Pool.Poolable; import com.badlogic.gdx.utils.Pools; /** Stores state for an animation and automatically mixes between animations. */ public class AnimationState { private final AnimationStateData data; - private Animation current, previous; - private float currentTime, currentLastTime, previousTime; - private boolean currentLoop, previousLoop; - private AnimationStateListener currentListener; + private QueuedAnimation current, previous; private float mixTime, mixDuration; - private final Array queue = new Array(); - private final Array events = new Array(); + private final Array queue = new Array(); private final Array listeners = new Array(); + private final Array events = new Array(); public AnimationState (AnimationStateData data) { if (data == null) throw new IllegalArgumentException("data cannot be null."); @@ -54,62 +52,79 @@ public class AnimationState { } public void update (float delta) { - currentTime += delta; - previousTime += delta; - mixTime += delta; + QueuedAnimation current = this.current; + if (current == null) return; - if (current != null) { - float duration = current.getDuration(); - if (currentLoop ? (currentLastTime % duration > currentTime % duration) - : (currentLastTime < duration && currentTime >= duration)) { - int count = (int)(currentTime / duration); - if (currentListener != null) currentListener.complete(count); - for (int i = 0, n = listeners.size; i < n; i++) - listeners.get(i).complete(count); - } + float time = current.time; + float duration = current.endTime; + + current.time = time + delta; + if (previous != null) { + previous.time += delta; + mixTime += delta; + } + + // Check if completed the animation or a loop iteration. + if (current.loop ? (current.lastTime % duration > time % duration) : (current.lastTime < duration && time >= duration)) { + int count = (int)(time / duration); + if (current.listener != null) current.listener.complete(count); + for (int i = 0, n = listeners.size; i < n; i++) + listeners.get(i).complete(count); } if (queue.size > 0) { - QueueEntry entry = queue.first(); - if (currentTime >= entry.delay) { - setAnimationInternal(entry.animation, entry.loop, entry.listener); - Pools.free(entry); - queue.removeIndex(0); + QueuedAnimation entry = queue.first(); + if (time >= entry.delay) { + if (entry.animation == null) + clearAnimation(); + else { + setAnimationEntry(entry); + queue.removeIndex(0); + } } } } public void apply (Skeleton skeleton) { + QueuedAnimation current = this.current; if (current == null) return; Array events = this.events; - events.clear(); + events.size = 0; + QueuedAnimation previous = this.previous; if (previous != null) { - previous.apply(skeleton, Integer.MAX_VALUE, previousTime, previousLoop, null); + previous.animation.apply(skeleton, Integer.MAX_VALUE, previous.time, previous.loop, null); float alpha = mixTime / mixDuration; if (alpha >= 1) { alpha = 1; - previous = null; + Pools.free(previous); + this.previous = null; } - current.mix(skeleton, currentLastTime, currentTime, currentLoop, events, alpha); + current.animation.mix(skeleton, current.lastTime, current.time, current.loop, events, alpha); } else - current.apply(skeleton, currentLastTime, currentTime, currentLoop, events); + current.animation.apply(skeleton, current.lastTime, current.time, current.loop, events); int listenerCount = listeners.size; for (int i = 0, n = events.size; i < n; i++) { Event event = events.get(i); - if (currentListener != null) currentListener.event(event); + if (current.listener != null) current.listener.event(event); for (int ii = 0; ii < listenerCount; ii++) listeners.get(ii).event(event); } - currentLastTime = currentTime; + current.lastTime = current.time; } public void clearAnimation () { - previous = null; - current = null; + if (previous != null) { + Pools.free(previous); + previous = null; + } + if (current != null) { + Pools.free(current); + current = null; + } clearQueue(); } @@ -118,117 +133,121 @@ public class AnimationState { queue.clear(); } - private void setAnimationInternal (Animation animation, boolean loop, AnimationStateListener listener) { - previous = null; + private void setAnimationEntry (QueuedAnimation entry) { + if (previous != null) { + Pools.free(previous); + previous = null; + } + + QueuedAnimation current = this.current; if (current != null) { - if (currentListener != null) currentListener.end(); + if (current.listener != null) current.listener.end(); for (int i = 0, n = listeners.size; i < n; i++) listeners.get(i).end(); - if (animation != null) { - mixDuration = data.getMix(current, animation); - if (mixDuration > 0) { - mixTime = 0; - previous = current; - previousTime = currentTime; - previousLoop = currentLoop; - } - } + mixDuration = data.getMix(current.animation, entry.animation); + if (mixDuration > 0) { + mixTime = 0; + previous = current; + } else + Pools.free(current); } - current = animation; - currentLoop = loop; - currentTime = 0; - currentLastTime = 0; - currentListener = listener; + this.current = entry; - if (currentListener != null) currentListener.start(); + if (entry != null && entry.listener != null) entry.listener.start(); for (int i = 0, n = listeners.size; i < n; i++) listeners.get(i).start(); } - /** @see #setAnimation(Animation, boolean, AnimationStateListener) */ - public void setAnimation (String animationName, boolean loop) { - setAnimation(animationName, loop, null); - } - - /** @see #setAnimation(Animation, boolean, AnimationStateListener) */ - public void setAnimation (String animationName, boolean loop, AnimationStateListener listener) { + /** @see #setAnimation(Animation, boolean) */ + public QueuedAnimation setAnimation (String animationName, boolean loop) { Animation animation = data.getSkeletonData().findAnimation(animationName); if (animation == null) throw new IllegalArgumentException("Animation not found: " + animationName); - setAnimation(animation, loop, listener); + return setAnimation(animation, loop); } - /** @see #setAnimation(Animation, boolean, AnimationStateListener) */ - public void setAnimation (Animation animation, boolean loop) { - setAnimation(animation, loop, null); - } - - /** Set the current animation. Any queued animations are cleared and the current animation time is set to 0. The specified - * listener receives events only for this animation. - * @param animation May be null. - * @param listener May be null. */ - public void setAnimation (Animation animation, boolean loop, AnimationStateListener listener) { + /** Set the current animation. Any queued animations are cleared. */ + public QueuedAnimation setAnimation (Animation animation, boolean loop) { clearQueue(); - setAnimationInternal(animation, loop, listener); + + QueuedAnimation entry = Pools.obtain(QueuedAnimation.class); + entry.animation = animation; + entry.loop = loop; + entry.time = 0; + entry.endTime = animation.getDuration(); + setAnimationEntry(entry); + return entry; } /** @see #addAnimation(Animation, boolean) */ - public void addAnimation (String animationName, boolean loop) { - addAnimation(animationName, loop, 0, null); + public QueuedAnimation addAnimation (String animationName, boolean loop) { + return addAnimation(animationName, loop, 0); } - /** @see #addAnimation(Animation, boolean, float, AnimationStateListener) */ - public void addAnimation (String animationName, boolean loop, float delay, AnimationStateListener listener) { + /** @see #addAnimation(Animation, boolean, float) */ + public QueuedAnimation 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, listener); + return 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, null); + * duration. + * @param animation May be null to queue clearing the AnimationState. */ + public QueuedAnimation addAnimation (Animation animation, boolean loop) { + return 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. - * @param listener May be null. */ - public void addAnimation (Animation animation, boolean loop, float delay, AnimationStateListener listener) { - QueueEntry entry = Pools.obtain(QueueEntry.class); + * @param animation May be null to queue clearing the AnimationState. + * @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */ + public QueuedAnimation addAnimation (Animation animation, boolean loop, float delay) { + QueuedAnimation entry = Pools.obtain(QueuedAnimation.class); entry.animation = animation; entry.loop = loop; - entry.listener = listener; + entry.time = 0; + entry.endTime = animation != null ? animation.getDuration() : 0; if (delay <= 0) { - Animation previousAnimation = queue.size == 0 ? current : queue.peek().animation; - if (previousAnimation != null) - delay = previousAnimation.getDuration() - data.getMix(previousAnimation, animation) + delay; - else + QueuedAnimation previousEntry = queue.size > 0 ? queue.peek() : current; + if (previousEntry != null) { + delay += previousEntry.endTime; + if (animation != null) delay += -data.getMix(previousEntry.animation, animation); + } else delay = 0; } entry.delay = delay; queue.add(entry); + return entry; + } + + public Array getQueue () { + return queue; + } + + /** @return May be null. */ + public QueuedAnimation getCurrent () { + return current; } /** @return May be null. */ public Animation getAnimation () { - return current; + return current != null ? current.animation : null; } /** Returns the time within the current animation. */ public float getTime () { - return currentTime; + return current != null ? current.time : 0; } public void setTime (float time) { - currentTime = time; - currentLastTime = time - 0.00001f; + if (current != null) current.setTime(time); } /** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */ public boolean isComplete () { - return current == null || currentTime >= current.getDuration(); + return current == null || current.time >= current.endTime; } /** Adds a listener to receive events for all animations. */ @@ -237,15 +256,9 @@ public class AnimationState { listeners.add(listener); } - /** Removes the listener, which may be for all animations, the current animation, or a queued animation. */ + /** Removes the listener added with {@link #addListener(AnimationStateListener)}. */ public void removeListener (AnimationStateListener listener) { listeners.removeValue(listener, true); - - if (listener == currentListener) currentListener = null; - - Array queue = this.queue; - for (int i = queue.size - 1; i >= 0; i--) - if (queue.get(i).listener == listener) queue.get(i).listener = null; } public AnimationStateData getData () { @@ -253,14 +266,66 @@ public class AnimationState { } public String toString () { - return (current != null && current.getName() != null) ? current.getName() : super.toString(); + if (current == null || current.animation == null) return ""; + return current.animation.getName(); } - static private class QueueEntry { + /** A queued animation. */ + static public class QueuedAnimation implements Poolable { Animation animation; boolean loop; - float delay; + float delay, time, lastTime, endTime; AnimationStateListener listener; + + public void reset () { + animation = null; + listener = null; + } + + public Animation getAnimation () { + return animation; + } + + public boolean getLoop () { + return loop; + } + + public void setLoop (boolean loop) { + this.loop = loop; + } + + public float getTime () { + return time; + } + + public void setTime (float time) { + this.time = time; + if (lastTime < time) lastTime = time; + } + + public float getEndTime () { + return endTime; + } + + public void setEndTime (float endTime) { + this.endTime = endTime; + } + + public AnimationStateListener getListener () { + return listener; + } + + public void setListener (AnimationStateListener listener) { + this.listener = listener; + } + + public float getDelay () { + return delay; + } + + public void setDelay (float delay) { + this.delay = delay; + } } static public interface AnimationStateListener { diff --git a/spine-libgdx/test/com/esotericsoftware/spine/AnimationStateTest.java b/spine-libgdx/test/com/esotericsoftware/spine/AnimationStateTest.java index 4d08913fe..f98099a85 100644 --- a/spine-libgdx/test/com/esotericsoftware/spine/AnimationStateTest.java +++ b/spine-libgdx/test/com/esotericsoftware/spine/AnimationStateTest.java @@ -112,12 +112,6 @@ public class AnimationStateTest extends ApplicationAdapter { Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT); state.apply(skeleton); - if (state.getAnimation().getName().equals("walk")) { - // After one second, change the current animation. Mixing is done by AnimationState for you. -// if (state.getTime() > 2) state.setAnimation("jump", false); -// } else { -// if (state.getTime() > 1) state.setAnimation("walk", true); - } skeleton.updateWorldTransform(); batch.begin(); diff --git a/spine-libgdx/test/spineboy.json b/spine-libgdx/test/spineboy.json index 113e72c33..c2bfc6ccc 100644 --- a/spine-libgdx/test/spineboy.json +++ b/spine-libgdx/test/spineboy.json @@ -995,6 +995,11 @@ { "time": 0.9333, "angle": 2.28 }, { "time": 1.0666, "angle": 3.6 } ] + }, + "root": { + "rotate": [ + { "time": 0, "angle": 0 } + ] } } } diff --git a/spine-xna/example/src/ExampleGame.cs b/spine-xna/example/src/ExampleGame.cs index fd981d329..ef84c1b3f 100644 --- a/spine-xna/example/src/ExampleGame.cs +++ b/spine-xna/example/src/ExampleGame.cs @@ -88,7 +88,7 @@ namespace Spine { state = new AnimationState(stateData); - if (true) { + if (false) { // Event handling for all animations. state.Start += new EventHandler(Start); state.End += new EventHandler(End); @@ -98,7 +98,7 @@ namespace Spine { state.SetAnimation("drawOrder", true); } else { state.SetAnimation("walk", false); - QueueEntry entry = state.AddAnimation("jump", false); + QueuedAnimation entry = state.AddAnimation("jump", false); entry.End += new EventHandler(End); // Event handling for queued animations. state.AddAnimation("walk", true); }