AnimationState queued animation for more runtimes, plus fixes.

This commit is contained in:
NathanSweet 2013-04-27 14:33:17 +02:00
parent 2c3453f4dd
commit ed0bae85c3
6 changed files with 178 additions and 41 deletions

View File

@ -94,7 +94,7 @@ void AnimationState_addAnimation (AnimationState* self, Animation* animation, in
if (delay <= 0) { if (delay <= 0) {
if (previousAnimation) 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 else
delay = 0; delay = 0;
} }
@ -141,6 +141,7 @@ void AnimationState_clearAnimation (AnimationState* self) {
} }
void AnimationState_update (AnimationState* self, float delta) { void AnimationState_update (AnimationState* self, float delta) {
_Entry* next;
_Internal* internal = SUB_CAST(_Internal, self); _Internal* internal = SUB_CAST(_Internal, self);
self->time += delta; self->time += delta;
@ -149,7 +150,7 @@ void AnimationState_update (AnimationState* self, float delta) {
if (internal->queue && self->time >= internal->queue->delay) { if (internal->queue && self->time >= internal->queue->delay) {
_AnimationState_setAnimation(self, internal->queue->animation, internal->queue->loop); _AnimationState_setAnimation(self, internal->queue->animation, internal->queue->loop);
_Entry* next = internal->queue->next; next = internal->queue->next;
FREE(internal->queue); FREE(internal->queue);
internal->queue = next; internal->queue = next;
} }

View File

@ -33,9 +33,10 @@ namespace Spine {
public float Time { get; set; } public float Time { get; set; }
public bool Loop { get; set; } public bool Loop { get; set; }
private Animation previous; private Animation previous;
float previousTime; private float previousTime;
bool previousLoop; private bool previousLoop;
float mixTime, mixDuration; private float mixTime, mixDuration;
private List<QueueEntry> queue = new List<QueueEntry>();
public AnimationState (AnimationStateData data) { public AnimationState (AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data cannot be null."); if (data == null) throw new ArgumentNullException("data cannot be null.");
@ -46,6 +47,14 @@ namespace Spine {
Time += delta; Time += delta;
previousTime += delta; previousTime += delta;
mixTime += 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) { public void Apply (Skeleton skeleton) {
@ -62,13 +71,40 @@ namespace Spine {
Animation.Apply(skeleton, Time, Loop); Animation.Apply(skeleton, Time, Loop);
} }
public void SetAnimation (String animationName, bool loop) { public void AddAnimation (String animationName, bool loop) {
Animation animation = Data.SkeletonData.FindAnimation(animationName); AddAnimation(animationName, loop, 0);
if (animation == null) throw new ArgumentException("Animation not found: " + animationName);
SetAnimation(animation, loop);
} }
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; previous = null;
if (animation != null && Animation != null) { if (animation != null && Animation != null) {
mixDuration = Data.GetMix(Animation, animation); mixDuration = Data.GetMix(Animation, animation);
@ -84,13 +120,25 @@ namespace Spine {
Time = 0; 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 () { public void ClearAnimation () {
previous = null; previous = null;
Animation = 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. */ /** 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; return Animation == null || Time >= Animation.Duration;
} }
@ -99,3 +147,9 @@ namespace Spine {
} }
} }
} }
class QueueEntry {
public Spine.Animation animation;
public bool loop;
public float delay;
}

View File

@ -25,15 +25,17 @@
package com.esotericsoftware.spine; 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. */ /** Stores state for an animation and automatically mixes between animations. */
public class AnimationState { public class AnimationState {
private final AnimationStateData data; private final AnimationStateData data;
Animation current, previous; private Animation current, previous;
float currentTime, previousTime; private float currentTime, previousTime;
boolean currentLoop, previousLoop; private boolean currentLoop, previousLoop;
float mixTime, mixDuration; private float mixTime, mixDuration;
private Array<QueueEntry> queue = new Array();
public AnimationState (AnimationStateData data) { public AnimationState (AnimationStateData data) {
if (data == null) throw new IllegalArgumentException("data cannot be null."); if (data == null) throw new IllegalArgumentException("data cannot be null.");
@ -44,6 +46,15 @@ public class AnimationState {
currentTime += delta; currentTime += delta;
previousTime += delta; previousTime += delta;
mixTime += 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) { public void apply (Skeleton skeleton) {
@ -63,18 +74,15 @@ public class AnimationState {
public void clearAnimation () { public void clearAnimation () {
previous = null; previous = null;
current = null; current = null;
clearQueue();
} }
/** @see #setAnimation(Animation, boolean) */ private void clearQueue () {
public void setAnimation (String animationName, boolean loop) { Pools.freeAll(queue);
Animation animation = data.getSkeletonData().findAnimation(animationName); queue.clear();
if (animation == null) throw new IllegalArgumentException("Animation not found: " + animationName);
setAnimation(animation, loop);
} }
/** Set the current animation. The current animation time is set to 0. private void setAnimationInternal (Animation animation, boolean loop) {
* @param animation May be null. */
public void setAnimation (Animation animation, boolean loop) {
previous = null; previous = null;
if (animation != null && current != null) { if (animation != null && current != null) {
mixDuration = data.getMix(current, animation); mixDuration = data.getMix(current, animation);
@ -90,6 +98,57 @@ public class AnimationState {
currentTime = 0; 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. */ /** @return May be null. */
public Animation getAnimation () { public Animation getAnimation () {
return current; return current;
@ -116,4 +175,10 @@ public class AnimationState {
public String toString () { public String toString () {
return (current != null && current.getName() != null) ? current.getName() : super.toString(); return (current != null && current.getName() != null) ? current.getName() : super.toString();
} }
static private class QueueEntry {
Animation animation;
boolean loop;
float delay;
}
} }

View File

@ -54,7 +54,7 @@ public class AnimationStateTest extends ApplicationAdapter {
// Define mixing between animations. // Define mixing between animations.
AnimationStateData stateData = new AnimationStateData(skeletonData); AnimationStateData stateData = new AnimationStateData(skeletonData);
stateData.setMix("walk", "jump", 0.4f); stateData.setMix("walk", "jump", 0.2f);
stateData.setMix("jump", "walk", 0.4f); stateData.setMix("jump", "walk", 0.4f);
state = new AnimationState(stateData); state = new AnimationState(stateData);

View File

@ -41,11 +41,11 @@ void spineboy () {
// Configure mixing. // Configure mixing.
AnimationStateData* stateData = AnimationStateData_create(skeletonData); 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); AnimationStateData_setMixByName(stateData, "jump", "walk", 0.4f);
SkeletonDrawable* drawable = new SkeletonDrawable(skeletonData, stateData); SkeletonDrawable* drawable = new SkeletonDrawable(skeletonData, stateData);
drawable->timeScale = 0.5f; drawable->timeScale = 1;
Skeleton* skeleton = drawable->skeleton; Skeleton* skeleton = drawable->skeleton;
skeleton->flipX = false; skeleton->flipX = false;
@ -57,6 +57,12 @@ void spineboy () {
Skeleton_updateWorldTransform(skeleton); Skeleton_updateWorldTransform(skeleton);
AnimationState_setAnimationByName(drawable->state, "walk", true); 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"); sf::RenderWindow window(sf::VideoMode(640, 480), "Spine SFML");
window.setFramerateLimit(60); window.setFramerateLimit(60);
@ -69,11 +75,11 @@ void spineboy () {
float delta = deltaClock.getElapsedTime().asSeconds(); float delta = deltaClock.getElapsedTime().asSeconds();
deltaClock.restart(); deltaClock.restart();
if (drawable->state->loop) { /*if (drawable->state->loop) {
if (drawable->state->time > 2) AnimationState_setAnimationByName(drawable->state, "jump", false); if (drawable->state->time > 2) AnimationState_setAnimationByName(drawable->state, "jump", false);
} else { } else {
if (drawable->state->time > 1) AnimationState_setAnimationByName(drawable->state, "walk", true); if (drawable->state->time > 1) AnimationState_setAnimationByName(drawable->state, "walk", true);
} }*/
drawable->update(delta); drawable->update(delta);

View File

@ -40,8 +40,7 @@ namespace Spine {
GraphicsDeviceManager graphics; GraphicsDeviceManager graphics;
SkeletonRenderer skeletonRenderer; SkeletonRenderer skeletonRenderer;
Skeleton skeleton; Skeleton skeleton;
Animation animation; AnimationState state;
float time;
public Example () { public Example () {
graphics = new GraphicsDeviceManager(this); graphics = new GraphicsDeviceManager(this);
@ -59,13 +58,25 @@ namespace Spine {
protected override void LoadContent () { protected override void LoadContent () {
skeletonRenderer = new SkeletonRenderer(GraphicsDevice); 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); SkeletonJson json = new SkeletonJson(atlas);
skeleton = new Skeleton(json.ReadSkeletonData("data/goblins.json")); skeleton = new Skeleton(json.ReadSkeletonData("data/" + name + ".json"));
skeleton.SetSkin("goblingirl"); if (name == "goblins") skeleton.SetSkin("goblingirl");
skeleton.SetSlotsToBindPose(); // Without this the skin attachments won't be attached. See SetSkin. 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.X = 320;
skeleton.RootBone.Y = 440; skeleton.RootBone.Y = 440;
@ -89,8 +100,8 @@ namespace Spine {
protected override void Draw (GameTime gameTime) { protected override void Draw (GameTime gameTime) {
GraphicsDevice.Clear(Color.Black); GraphicsDevice.Clear(Color.Black);
time += gameTime.ElapsedGameTime.Milliseconds / 1000f; state.Update(gameTime.ElapsedGameTime.Milliseconds / 1000f);
animation.Apply(skeleton, time, true); state.Apply(skeleton);
skeleton.UpdateWorldTransform(); skeleton.UpdateWorldTransform();
skeletonRenderer.Begin(); skeletonRenderer.Begin();
skeletonRenderer.Draw(skeleton); skeletonRenderer.Draw(skeleton);