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 (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;
}

View File

@ -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<QueueEntry> queue = new List<QueueEntry>();
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;
}

View File

@ -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<QueueEntry> 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;
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);