mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 22:34:53 +08:00
parent
0a8896a3e2
commit
a39c970a6a
@ -122,7 +122,7 @@ public class AnimationStateTest {
|
||||
expect(0, "end", 1, 1.1f) //
|
||||
);
|
||||
state.setAnimation(0, "events1", false);
|
||||
run(0.1f, 1000);
|
||||
run(0.1f, 1000, null);
|
||||
|
||||
setup("1/60 time step", // 2
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -133,7 +133,7 @@ public class AnimationStateTest {
|
||||
expect(0, "end", 1, 1.017f) //
|
||||
);
|
||||
state.setAnimation(0, "events1", false);
|
||||
run(1 / 60f, 1000);
|
||||
run(1 / 60f, 1000, null);
|
||||
|
||||
setup("30 time step", // 3
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -144,7 +144,7 @@ public class AnimationStateTest {
|
||||
expect(0, "end", 30, 60) //
|
||||
);
|
||||
state.setAnimation(0, "events1", false);
|
||||
run(30, 1000);
|
||||
run(30, 1000, null);
|
||||
|
||||
setup("1 time step", // 4
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -155,7 +155,7 @@ public class AnimationStateTest {
|
||||
expect(0, "end", 1, 2) //
|
||||
);
|
||||
state.setAnimation(0, "events1", false);
|
||||
run(1, 1.01f);
|
||||
run(1, 1.01f, null);
|
||||
|
||||
setup("interrupt", // 5
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -188,7 +188,7 @@ public class AnimationStateTest {
|
||||
state.setAnimation(0, "events1", false);
|
||||
state.addAnimation(0, "events2", false, 0);
|
||||
state.addAnimation(0, "events1", false, 0);
|
||||
run(0.1f, 4f);
|
||||
run(0.1f, 4f, null);
|
||||
|
||||
setup("interrupt with delay", // 6
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -208,7 +208,7 @@ public class AnimationStateTest {
|
||||
);
|
||||
state.setAnimation(0, "events1", false);
|
||||
state.addAnimation(0, "events2", false, 0.5f);
|
||||
run(0.1f, 1000);
|
||||
run(0.1f, 1000, null);
|
||||
|
||||
setup("interrupt with delay and mix time", // 7
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -232,7 +232,7 @@ public class AnimationStateTest {
|
||||
stateData.setMix("events1", "events2", 0.7f);
|
||||
state.setAnimation(0, "events1", true);
|
||||
state.addAnimation(0, "events2", false, 0.9f);
|
||||
run(0.1f, 1000);
|
||||
run(0.1f, 1000, null);
|
||||
|
||||
setup("animation 0 events do not fire during mix", // 8
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -255,7 +255,7 @@ public class AnimationStateTest {
|
||||
stateData.setDefaultMix(0.7f);
|
||||
state.setAnimation(0, "events1", false);
|
||||
state.addAnimation(0, "events2", false, 0.4f);
|
||||
run(0.1f, 1000);
|
||||
run(0.1f, 1000, null);
|
||||
|
||||
setup("event threshold, some animation 0 events fire during mix", // 9
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -279,7 +279,7 @@ public class AnimationStateTest {
|
||||
stateData.setMix("events1", "events2", 0.7f);
|
||||
state.setAnimation(0, "events1", false).setEventThreshold(0.5f);
|
||||
state.addAnimation(0, "events2", false, 0.4f);
|
||||
run(0.1f, 1000);
|
||||
run(0.1f, 1000, null);
|
||||
|
||||
setup("event threshold, all animation 0 events fire during mix", // 10
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -306,7 +306,7 @@ public class AnimationStateTest {
|
||||
);
|
||||
state.setAnimation(0, "events1", true).setEventThreshold(1);
|
||||
state.addAnimation(0, "events2", false, 0.8f).setMixDuration(0.7f);
|
||||
run(0.1f, 1000);
|
||||
run(0.1f, 1000, null);
|
||||
|
||||
setup("looping", // 11
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -329,7 +329,7 @@ public class AnimationStateTest {
|
||||
expect(0, "event 0", 4, 4) //
|
||||
);
|
||||
state.setAnimation(0, "events1", true);
|
||||
run(0.1f, 4);
|
||||
run(0.1f, 4, null);
|
||||
|
||||
setup("not looping, update past animation 0 duration", // 12
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -351,7 +351,7 @@ public class AnimationStateTest {
|
||||
);
|
||||
state.setAnimation(0, "events1", false);
|
||||
state.addAnimation(0, "events2", false, 2);
|
||||
run(0.1f, 4f);
|
||||
run(0.1f, 4f, null);
|
||||
|
||||
setup("interrupt animation after first loop complete", // 13
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -392,7 +392,7 @@ public class AnimationStateTest {
|
||||
expect(0, "end", 1, 1.1f) //
|
||||
);
|
||||
state.addAnimation(0, "events1", false, 0);
|
||||
run(0.1f, 1.9f);
|
||||
run(0.1f, 1.9f, null);
|
||||
|
||||
setup("end time beyond non-looping animation duration", // 15
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -403,7 +403,7 @@ public class AnimationStateTest {
|
||||
expect(0, "end", 9f, 9.1f) //
|
||||
);
|
||||
state.setAnimation(0, "events1", false).setTrackEnd(9);
|
||||
run(0.1f, 10);
|
||||
run(0.1f, 10, null);
|
||||
|
||||
setup("looping with animation start", // 16
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -417,7 +417,7 @@ public class AnimationStateTest {
|
||||
entry = state.setAnimation(0, "events1", true);
|
||||
entry.setAnimationLast(0.6f);
|
||||
entry.setAnimationStart(0.6f);
|
||||
run(0.1f, 1.4f);
|
||||
run(0.1f, 1.4f, null);
|
||||
|
||||
setup("looping with animation start and end", // 17
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -431,7 +431,7 @@ public class AnimationStateTest {
|
||||
entry.setAnimationStart(0.2f);
|
||||
entry.setAnimationLast(0.2f);
|
||||
entry.setAnimationEnd(0.8f);
|
||||
run(0.1f, 1.8f);
|
||||
run(0.1f, 1.8f, null);
|
||||
|
||||
setup("non-looping with animation start and end", // 18
|
||||
expect(0, "start", 0, 0), //
|
||||
@ -443,7 +443,63 @@ public class AnimationStateTest {
|
||||
entry.setAnimationStart(0.2f);
|
||||
entry.setAnimationLast(0.2f);
|
||||
entry.setAnimationEnd(0.8f);
|
||||
run(0.1f, 1.8f);
|
||||
run(0.1f, 1.8f, null);
|
||||
|
||||
setup("mix out looping with animation start and end", // 19
|
||||
expect(0, "start", 0, 0), //
|
||||
expect(0, "event 14", 0.3f, 0.3f), //
|
||||
expect(0, "complete", 0.6f, 0.6f), //
|
||||
|
||||
expect(1, "start", 0.1f, 0.8f), //
|
||||
|
||||
expect(0, "interrupt", 0.8f, 0.8f), //
|
||||
|
||||
expect(1, "event 0", 0.1f, 0.8f), //
|
||||
|
||||
expect(0, "event 14", 0.9f, 0.9f), //
|
||||
expect(0, "complete", 1.2f, 1.2f), //
|
||||
|
||||
expect(1, "event 14", 0.5f, 1.2f), //
|
||||
|
||||
expect(0, "end", 1.4f, 1.4f), //
|
||||
|
||||
expect(1, "event 30", 1, 1.7f), //
|
||||
expect(1, "complete", 1, 1.7f), //
|
||||
expect(1, "end", 1, 1.8f) //
|
||||
);
|
||||
entry = state.setAnimation(0, "events1", true);
|
||||
entry.setAnimationStart(0.2f);
|
||||
entry.setAnimationLast(0.2f);
|
||||
entry.setAnimationEnd(0.8f);
|
||||
entry.setEventThreshold(1);
|
||||
state.addAnimation(0, "events2", false, 0.7f).setMixDuration(0.7f);
|
||||
run(0.1f, 20, null);
|
||||
|
||||
setup("setAnimation with track entry mix", // 20
|
||||
expect(0, "start", 0, 0), //
|
||||
expect(0, "event 0", 0, 0), //
|
||||
expect(0, "event 14", 0.5f, 0.5f), //
|
||||
|
||||
expect(1, "start", 0.1f, 1), //
|
||||
|
||||
expect(0, "interrupt", 1, 1), //
|
||||
expect(0, "complete", 1, 1), //
|
||||
|
||||
expect(1, "event 0", 0.1f, 1), //
|
||||
expect(1, "event 14", 0.5f, 1.4f), //
|
||||
|
||||
expect(0, "end", 1.6f, 1.6f), //
|
||||
|
||||
expect(1, "event 30", 1, 1.9f), //
|
||||
expect(1, "complete", 1, 1.9f), //
|
||||
expect(1, "end", 1, 2) //
|
||||
);
|
||||
state.setAnimation(0, "events1", true);
|
||||
run(0.1f, 1000, new TestListener() {
|
||||
public void frame (float time) {
|
||||
if (MathUtils.isEqual(time, 1f)) state.setAnimation(0, "events2", false).setMixDuration(0.7f);
|
||||
}
|
||||
});
|
||||
|
||||
System.out.println("AnimationState tests passed.");
|
||||
}
|
||||
@ -460,10 +516,6 @@ public class AnimationStateTest {
|
||||
log(String.format("%-3s%-12s%-7s%-7s%-7s", "#", "EVENT", "TRACK", "TOTAL", "RESULT"));
|
||||
}
|
||||
|
||||
void run (float incr, float endTime) {
|
||||
run(incr, endTime, null);
|
||||
}
|
||||
|
||||
void run (float incr, float endTime, TestListener listener) {
|
||||
Skeleton skeleton = new Skeleton(skeletonData);
|
||||
state.apply(skeleton);
|
||||
|
||||
@ -42,16 +42,16 @@ import com.esotericsoftware.spine.Animation.Timeline;
|
||||
/** Stores state for applying one or more animations over time and automatically mixes (crossfades) when animations change. */
|
||||
public class AnimationState {
|
||||
private AnimationStateData data;
|
||||
private Array<TrackEntry> tracks = new Array();
|
||||
private final Array<TrackEntry> tracks = new Array();
|
||||
private final Array<Event> events = new Array();
|
||||
final Array<AnimationStateListener> listeners = new Array();
|
||||
private float timeScale = 1;
|
||||
final Pool<TrackEntry> trackEntryPool = new Pool() {
|
||||
private final Array<AnimationStateListener> listeners = new Array();
|
||||
private final Pool<TrackEntry> trackEntryPool = new Pool() {
|
||||
protected Object newObject () {
|
||||
return new TrackEntry();
|
||||
}
|
||||
};
|
||||
private final EventQueue queue = new EventQueue(listeners, trackEntryPool);
|
||||
private float timeScale = 1;
|
||||
|
||||
/** Creates an uninitialized AnimationState. The animation state data must be set before use. */
|
||||
public AnimationState () {
|
||||
@ -62,7 +62,7 @@ public class AnimationState {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/** Updates the track entry times. */
|
||||
/** Increments the track entry times, setting queued animations as current if needed. */
|
||||
public void update (float delta) {
|
||||
delta *= timeScale;
|
||||
for (int i = 0; i < tracks.size; i++) {
|
||||
@ -82,8 +82,8 @@ public class AnimationState {
|
||||
if (next.mixingFrom != null) next.mixTime += currentDelta;
|
||||
continue;
|
||||
}
|
||||
} else if (!current.loop && current.trackLast >= current.trackEnd) {
|
||||
// Clear a non-looping animation when it reaches its end time and there is no next entry.
|
||||
} else if (current.trackLast >= current.trackEnd) {
|
||||
// Clear the track when the end time is reached and there is no next entry.
|
||||
clearTrack(i);
|
||||
continue;
|
||||
}
|
||||
@ -254,17 +254,21 @@ public class AnimationState {
|
||||
return setAnimation(trackIndex, animation, loop);
|
||||
}
|
||||
|
||||
/** Set the current animation. Any queued animations for the track are cleared.
|
||||
/** Sets the current animation for a track. If the track is empty, the new animation is made the current animation immediately.
|
||||
* Otherwise, any queued animations are discarded and the new animation is queued to become the current animation the next time
|
||||
* {@link #update(float)} is called.
|
||||
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
|
||||
* after {@link AnimationStateListener#end(TrackEntry)}. */
|
||||
public TrackEntry setAnimation (int trackIndex, Animation animation, boolean loop) {
|
||||
if (animation == null) throw new IllegalArgumentException("animation cannot be null.");
|
||||
|
||||
TrackEntry current = expandToIndex(trackIndex);
|
||||
if (current != null) freeAll(current.next);
|
||||
|
||||
TrackEntry entry = trackEntry(trackIndex, animation, loop, current);
|
||||
setCurrent(trackIndex, entry);
|
||||
if (current != null) {
|
||||
freeAll(current.next);
|
||||
current.next = entry;
|
||||
entry.delay = current.trackLast;
|
||||
} else
|
||||
setCurrent(trackIndex, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
@ -275,7 +279,7 @@ public class AnimationState {
|
||||
return addAnimation(trackIndex, animation, loop, delay);
|
||||
}
|
||||
|
||||
/** Adds an animation to be played after the current or last queued animation.
|
||||
/** Adds an animation to be played after the current or last queued animation for a track.
|
||||
* @param delay Seconds to begin this animation after the start of the previous animation. May be <= 0 to use duration of the
|
||||
* previous animation minus any mix duration plus the negative delay.
|
||||
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept
|
||||
@ -297,13 +301,12 @@ public class AnimationState {
|
||||
float duration = last.animationEnd - last.animationStart;
|
||||
if (duration != 0)
|
||||
delay += duration * (1 + (int)(last.trackTime / duration)) - data.getMix(last.animation, animation);
|
||||
else
|
||||
delay = 0;
|
||||
}
|
||||
} else {
|
||||
entry.delay = delay;
|
||||
} else
|
||||
setCurrent(trackIndex, entry);
|
||||
if (delay <= 0) delay = 0;
|
||||
}
|
||||
|
||||
entry.delay = delay;
|
||||
return entry;
|
||||
}
|
||||
|
||||
@ -319,12 +322,12 @@ public class AnimationState {
|
||||
entry.drawOrderThreshold = 0;
|
||||
|
||||
entry.delay = 0;
|
||||
entry.trackTime = 0;
|
||||
entry.trackEnd = animation.getDuration();
|
||||
entry.trackLast = -1;
|
||||
entry.animationStart = 0;
|
||||
entry.animationEnd = entry.trackEnd;
|
||||
entry.animationEnd = animation.getDuration();
|
||||
entry.animationLast = -1;
|
||||
entry.trackTime = 0;
|
||||
entry.trackEnd = loop ? Integer.MAX_VALUE : entry.animationEnd;
|
||||
entry.trackLast = -1;
|
||||
entry.timeScale = 1;
|
||||
|
||||
entry.alpha = 1;
|
||||
@ -334,13 +337,13 @@ public class AnimationState {
|
||||
return entry;
|
||||
}
|
||||
|
||||
/** @return May be null. */
|
||||
/** The track entry for the animation currently playing on the track, or null. */
|
||||
public TrackEntry getCurrent (int trackIndex) {
|
||||
if (trackIndex >= tracks.size) return null;
|
||||
return tracks.get(trackIndex);
|
||||
}
|
||||
|
||||
/** Adds a listener to receive events for all animations. */
|
||||
/** Adds a listener to receive events for all track entries. */
|
||||
public void addListener (AnimationStateListener listener) {
|
||||
if (listener == null) throw new IllegalArgumentException("listener cannot be null.");
|
||||
listeners.add(listener);
|
||||
@ -355,14 +358,14 @@ public class AnimationState {
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
/** Discards all listener notifications that have not yet been delivered. This can be useful to call from an
|
||||
* {@link AnimationStateListener} when it is known that further notifications that may have been already triggered are not
|
||||
* wanted, for example because new animations are being set. */
|
||||
/** Discards all {@link #addListener(AnimationStateListener) listener} notifications that have not yet been delivered. This can
|
||||
* be useful to call from an {@link AnimationStateListener} when it is known that further notifications that may have been
|
||||
* already queued for delivery are not wanted because new animations are being set. */
|
||||
public void clearListenerNotifications () {
|
||||
queue.clear();
|
||||
}
|
||||
|
||||
/** Multiplier for the delta time when the animation state is updated, causing time for all animations to move slower or
|
||||
/** Multiplier for the delta time when the animation state is updated, causing time for all animations to play slower or
|
||||
* faster. Defaults to 1. */
|
||||
public float getTimeScale () {
|
||||
return timeScale;
|
||||
@ -417,15 +420,6 @@ public class AnimationState {
|
||||
listener = null;
|
||||
}
|
||||
|
||||
public float getAnimationTime () {
|
||||
if (loop) {
|
||||
float duration = animationEnd - animationStart;
|
||||
if (duration == 0) return 0;
|
||||
return (trackTime % duration) + animationStart;
|
||||
}
|
||||
return Math.min(trackTime + animationStart, animationEnd);
|
||||
}
|
||||
|
||||
public int getTrackIndex () {
|
||||
return trackIndex;
|
||||
}
|
||||
@ -455,8 +449,8 @@ public class AnimationState {
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
/** Current time in seconds for this track entry. When changing the track time, it often makes sense to also change
|
||||
* {@link #getAnimationLast()} to control when timelines will trigger. Defaults to 0. */
|
||||
/** Current time in seconds this track entry has been the current track entry. The track time determines
|
||||
* {@link #getAnimationTime()} and can be set to start the animation at a time other than 0. */
|
||||
public float getTrackTime () {
|
||||
return trackTime;
|
||||
}
|
||||
@ -465,6 +459,9 @@ public class AnimationState {
|
||||
this.trackTime = trackTime;
|
||||
}
|
||||
|
||||
/** The track time in seconds when this animation will be removed from the track. If the track end time is reached and no
|
||||
* other animations are queued for playback, the track is cleared. Defaults to the animation duration for non-looping
|
||||
* animations and to {@link Integer#MAX_VALUE} for looping animations. */
|
||||
public float getTrackEnd () {
|
||||
return trackEnd;
|
||||
}
|
||||
@ -473,7 +470,10 @@ public class AnimationState {
|
||||
this.trackEnd = trackEnd;
|
||||
}
|
||||
|
||||
/** Seconds when this animation starts, both initially and after looping. Defaults to 0. */
|
||||
/** Seconds when this animation starts, both initially and after looping. Defaults to 0.
|
||||
* <p>
|
||||
* When changing the animation start time, it often makes sense to also change {@link #getAnimationLast()} to control when
|
||||
* timelines will trigger. */
|
||||
public float getAnimationStart () {
|
||||
return animationStart;
|
||||
}
|
||||
@ -482,8 +482,8 @@ public class AnimationState {
|
||||
this.animationStart = animationStart;
|
||||
}
|
||||
|
||||
/** Seconds when this animation ends, causing the next animation to start or this animation to loop. Defaults to the
|
||||
* animation duration. */
|
||||
/** Seconds for the last frame of this animation. Non-looping animations won't play past this time. Looping animation will
|
||||
* loop back to {@link #getAnimationStart()} at this time. Defaults to the animation duration. */
|
||||
public float getAnimationEnd () {
|
||||
return animationEnd;
|
||||
}
|
||||
@ -503,7 +503,19 @@ public class AnimationState {
|
||||
this.animationLast = animationLast;
|
||||
}
|
||||
|
||||
/** Multiplier for the delta time when the animation state is updated, causing time for this animation to move slower or
|
||||
/** Uses the {@link #getTrackTime() track time} to compute the animation time between the {@link #getAnimationStart()
|
||||
* animation start} and {@link #getAnimationEnd() animation end}. When the track time is 0, the animation time is equal to
|
||||
* the animation start time. */
|
||||
public float getAnimationTime () {
|
||||
if (loop) {
|
||||
float duration = animationEnd - animationStart;
|
||||
if (duration == 0) return animationStart;
|
||||
return (trackTime % duration) + animationStart;
|
||||
}
|
||||
return Math.min(trackTime + animationStart, animationEnd);
|
||||
}
|
||||
|
||||
/** Multiplier for the delta time when the animation state is updated, causing time for this animation to play slower or
|
||||
* faster. Defaults to 1. */
|
||||
public float getTimeScale () {
|
||||
return timeScale;
|
||||
@ -513,7 +525,7 @@ public class AnimationState {
|
||||
this.timeScale = timeScale;
|
||||
}
|
||||
|
||||
/** The listener for events generated solely from this track entry, or null. */
|
||||
/** The listener for events generated by this track entry, or null. */
|
||||
public AnimationStateListener getListener () {
|
||||
return listener;
|
||||
}
|
||||
@ -524,8 +536,10 @@ public class AnimationState {
|
||||
}
|
||||
|
||||
/** Values < 1 mix this animation with the skeleton pose. Defaults to 1, which overwrites the skeleton pose with this
|
||||
* animation. Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. Generally
|
||||
* it doesn't make sense to use alpha on track 0, since the skeleton pose is probably from the last frame render. */
|
||||
* animation.
|
||||
* <p>
|
||||
* Typically track 0 is used to completely pose the skeleton, then alpha can be used on higher tracks. Generally it doesn't
|
||||
* make sense to use alpha on track 0, since the skeleton pose is probably from the last frame render. */
|
||||
public float getAlpha () {
|
||||
return alpha;
|
||||
}
|
||||
@ -576,12 +590,12 @@ public class AnimationState {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
/** Returns true if at least one loop has been completed (ie time >= end time). */
|
||||
/** Returns true if at least one loop has been completed. */
|
||||
public boolean isComplete () {
|
||||
return trackTime >= trackEnd;
|
||||
return trackTime >= animationEnd - animationStart;
|
||||
}
|
||||
|
||||
/** Seconds from zero to the mix duration when mixing from the previous animation to this animation. */
|
||||
/** Seconds from 0 to the mix duration when mixing from the previous animation to this animation. */
|
||||
public float getMixTime () {
|
||||
return mixTime;
|
||||
}
|
||||
@ -591,7 +605,9 @@ public class AnimationState {
|
||||
}
|
||||
|
||||
/** Seconds for mixing from the previous animation to this animation. Defaults to the value provided by
|
||||
* {@link AnimationStateData} based on the animation before this animation (if any). */
|
||||
* {@link AnimationStateData} based on the animation before this animation (if any).
|
||||
* <p>
|
||||
* The mix duration must be set before this track entry becomes the current track entry. */
|
||||
public float getMixDuration () {
|
||||
return mixDuration;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user