Merge branch 'master' into 3.6-beta

This commit is contained in:
NathanSweet 2016-12-06 16:57:05 +01:00
commit 6fb9a4e991
37 changed files with 348 additions and 130 deletions

View File

@ -366,7 +366,7 @@ public class AnimationState {
from.timelinesRotation.length = 0; from.timelinesRotation.length = 0;
// If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
if (from.mixingFrom != null) current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); if (from.mixingFrom != null && from.mixDuration > 0) current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
queue.start(current); queue.start(current);
@ -485,7 +485,7 @@ public class AnimationState {
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? int.MAX_VALUE : entry.animationEnd; entry.trackEnd = int.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;

View File

@ -126,7 +126,7 @@ public class Atlas {
region.splits = new Vector.<int>(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])); region.splits = new Vector.<int>(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3]));
if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits if (reader.readTuple(tuple) == 4) { // pad is optional, but only present with splits
region.pads = Vector.<int>(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3])); region.pads = new Vector.<int>(parseInt(tuple[0]), parseInt(tuple[1]), parseInt(tuple[2]), parseInt(tuple[3]));
reader.readTuple(tuple); reader.readTuple(tuple);
} }

View File

@ -582,7 +582,7 @@ void _spAnimationState_setCurrent (spAnimationState* self, int index, spTrackEnt
from->timelinesRotationCount = 0; from->timelinesRotationCount = 0;
/* If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. */ /* If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. */
if (from->mixingFrom) current->mixAlpha *= MIN(from->mixTime / from->mixDuration, 1); if (from->mixingFrom && from->mixDuration > 0) current->mixAlpha *= MIN(from->mixTime / from->mixDuration, 1);
} }
_spEventQueue_start(internal->queue, current); _spEventQueue_start(internal->queue, current);
@ -715,7 +715,7 @@ spTrackEntry* _spAnimationState_trackEntry (spAnimationState* self, int trackInd
entry->trackTime = 0; entry->trackTime = 0;
entry->trackLast = -1; entry->trackLast = -1;
entry->nextTrackLast = -1; entry->nextTrackLast = -1;
entry->trackEnd = loop ? (float)INT_MAX : entry->animationEnd; entry->trackEnd = (float)INT_MAX;
entry->timeScale = 1; entry->timeScale = 1;
entry->alpha = 1; entry->alpha = 1;

View File

@ -66,11 +66,11 @@ typedef struct {
} Str; } Str;
static void trim (Str* str) { static void trim (Str* str) {
while (isspace(*str->begin) && str->begin < str->end) while (isspace((unsigned char)*str->begin) && str->begin < str->end)
(str->begin)++; (str->begin)++;
if (str->begin == str->end) return; if (str->begin == str->end) return;
str->end--; str->end--;
while (isspace(*str->end) && str->end >= str->begin) while (isspace((unsigned char)*str->end) && str->end >= str->begin)
str->end--; str->end--;
str->end++; str->end++;
} }

View File

@ -36,7 +36,7 @@
#include "kvec.h" #include "kvec.h"
typedef struct { typedef struct {
const unsigned char* cursor; const unsigned char* cursor;
const unsigned char* end; const unsigned char* end;
} _dataInput; } _dataInput;
@ -373,7 +373,7 @@ static spAnimation* _spSkeletonBinary_readAnimation (spSkeletonBinary* self, con
for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) { for (frameIndex = 0; frameIndex < frameCount; ++frameIndex) {
float time = readFloat(input); float time = readFloat(input);
float mix = readFloat(input); float mix = readFloat(input);
char bendDirection = readSByte(input); signed char bendDirection = readSByte(input);
spIkConstraintTimeline_setFrame(timeline, frameIndex, time, mix, bendDirection); spIkConstraintTimeline_setFrame(timeline, frameIndex, time, mix, bendDirection);
if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex); if (frameIndex < frameCount - 1) readCurve(input, SUPER(timeline), frameIndex);
} }

View File

@ -30,11 +30,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
namespace Spine { namespace Spine {
public class AnimationState { public class AnimationState {
private static Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0); static readonly Animation EmptyAnimation = new Animation("<empty>", new ExposedList<Timeline>(), 0);
private AnimationStateData data; private AnimationStateData data;
private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>(); private readonly ExposedList<TrackEntry> tracks = new ExposedList<TrackEntry>();
@ -44,6 +43,8 @@ namespace Spine {
private bool animationsChanged; private bool animationsChanged;
private float timeScale = 1; private float timeScale = 1;
Pool<TrackEntry> trackEntryPool = new Pool<TrackEntry>();
public AnimationStateData Data { get { return data; } } public AnimationStateData Data { get { return data; } }
/// <summary>A list of tracks that have animations, which may contain nulls.</summary> /// <summary>A list of tracks that have animations, which may contain nulls.</summary>
public ExposedList<TrackEntry> Tracks { get { return tracks; } } public ExposedList<TrackEntry> Tracks { get { return tracks; } }
@ -58,7 +59,7 @@ namespace Spine {
public AnimationState (AnimationStateData data) { public AnimationState (AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null."); if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
this.data = data; this.data = data;
this.queue = new EventQueue(this, HandleAnimationsChanged); this.queue = new EventQueue(this, HandleAnimationsChanged, trackEntryPool);
} }
void HandleAnimationsChanged () { void HandleAnimationsChanged () {
@ -215,7 +216,7 @@ namespace Spine {
float alpha = from.alpha * entry.mixAlpha * (1 - mix); float alpha = from.alpha * entry.mixAlpha * (1 - mix);
bool firstFrame = entry.timelinesRotation.Count == 0; bool firstFrame = entry.timelinesRotation.Count == 0;
if (firstFrame) entry.timelinesRotation.Capacity = timelineCount << 1; if (firstFrame) entry.timelinesRotation.EnsureCapacity(timelines.Count << 1);
var timelinesRotation = entry.timelinesRotation.Items; var timelinesRotation = entry.timelinesRotation.Items;
for (int i = 0; i < timelineCount; i++) { for (int i = 0; i < timelineCount; i++) {
@ -389,7 +390,7 @@ namespace Spine {
from.timelinesRotation.Clear(); from.timelinesRotation.Clear();
// If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
if (from.mixingFrom != null) from.mixAlpha *= Math.Min(from.mixTime / from.mixDuration, 1); if (from.mixingFrom != null && from.mixDuration > 0) current.mixAlpha *= Math.Min(from.mixTime / from.mixDuration, 1);
} }
queue.Start(current); queue.Start(current);
@ -526,32 +527,32 @@ namespace Spine {
/// <param name="last">May be null.</param> /// <param name="last">May be null.</param>
private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) { private TrackEntry NewTrackEntry (int trackIndex, Animation animation, bool loop, TrackEntry last) {
return new TrackEntry { TrackEntry entry = trackEntryPool.Obtain(); // Pooling
trackIndex = trackIndex, entry.trackIndex = trackIndex;
animation = animation, entry.animation = animation;
loop = loop, entry.loop = loop;
eventThreshold = 0, entry.eventThreshold = 0;
attachmentThreshold = 0, entry.attachmentThreshold = 0;
drawOrderThreshold = 0, entry.drawOrderThreshold = 0;
animationStart = 0, entry.animationStart = 0;
animationEnd = animation.duration, entry.animationEnd = animation.Duration;
animationLast = -1, entry.animationLast = -1;
nextAnimationLast = -1, entry.nextAnimationLast = -1;
delay = 0, entry.delay = 0;
trackTime = 0, entry.trackTime = 0;
trackLast = -1, entry.trackLast = -1;
nextTrackLast = -1, entry.nextTrackLast = -1;
trackEnd = loop ? int.MaxValue : animation.duration, entry.trackEnd = float.MaxValue; // loop ? float.MaxValue : animation.Duration;
timeScale = 1, entry.timeScale = 1;
alpha = 1, entry.alpha = 1;
mixAlpha = 1, entry.mixAlpha = 1;
mixTime = 0, entry.mixTime = 0;
mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation), entry.mixDuration = (last == null) ? 0 : data.GetMix(last.animation, animation);
}; return entry;
} }
private void DisposeNext (TrackEntry entry) { private void DisposeNext (TrackEntry entry) {
@ -627,7 +628,7 @@ namespace Spine {
} }
override public String ToString () { override public String ToString () {
var buffer = new StringBuilder(); var buffer = new System.Text.StringBuilder();
for (int i = 0, n = tracks.Count; i < n; i++) { for (int i = 0, n = tracks.Count; i < n; i++) {
TrackEntry entry = tracks.Items[i]; TrackEntry entry = tracks.Items[i];
if (entry == null) continue; if (entry == null) continue;
@ -646,7 +647,7 @@ namespace Spine {
} }
/// <summary>State for the playback of an animation.</summary> /// <summary>State for the playback of an animation.</summary>
public class TrackEntry { public class TrackEntry : Pool<TrackEntry>.IPoolable {
internal Animation animation; internal Animation animation;
internal TrackEntry next, mixingFrom; internal TrackEntry next, mixingFrom;
@ -657,9 +658,25 @@ namespace Spine {
internal float animationStart, animationEnd, animationLast, nextAnimationLast; internal float animationStart, animationEnd, animationLast, nextAnimationLast;
internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f; internal float delay, trackTime, trackLast, nextTrackLast, trackEnd, timeScale = 1f;
internal float alpha, mixTime, mixDuration, mixAlpha; internal float alpha, mixTime, mixDuration, mixAlpha;
internal readonly ExposedList<bool> timelinesFirst = new ExposedList<bool>(), timelinesLast = new ExposedList<bool>(); internal readonly ExposedList<bool> timelinesFirst = new ExposedList<bool>();
internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>(); internal readonly ExposedList<float> timelinesRotation = new ExposedList<float>();
// IPoolable.Reset()
public void Reset () {
next = null;
mixingFrom = null;
animation = null;
timelinesFirst.Clear();
timelinesRotation.Clear();
Start = null;
Interrupt = null;
End = null;
Dispose = null;
Complete = null;
Event = null;
}
/// <summary>The index of the track where this entry is either current or queued.</summary> /// <summary>The index of the track where this entry is either current or queued.</summary>
public int TrackIndex { get { return trackIndex; } } public int TrackIndex { get { return trackIndex; } }
@ -824,11 +841,13 @@ namespace Spine {
public bool drainDisabled; public bool drainDisabled;
private readonly AnimationState state; private readonly AnimationState state;
private readonly Pool<TrackEntry> trackEntryPool;
public event Action AnimationsChanged; public event Action AnimationsChanged;
public EventQueue (AnimationState state, Action HandleAnimationsChanged) { public EventQueue (AnimationState state, Action HandleAnimationsChanged, Pool<TrackEntry> trackEntryPool) {
this.state = state; this.state = state;
this.AnimationsChanged += HandleAnimationsChanged; this.AnimationsChanged += HandleAnimationsChanged;
this.trackEntryPool = trackEntryPool;
} }
struct EventQueueEntry { struct EventQueueEntry {
@ -901,6 +920,7 @@ namespace Spine {
case EventType.Dispose: case EventType.Dispose:
trackEntry.OnDispose(); trackEntry.OnDispose();
state.OnDispose(trackEntry); state.OnDispose(trackEntry);
trackEntryPool.Free(trackEntry); // Pooling
break; break;
case EventType.Complete: case EventType.Complete:
trackEntry.OnComplete(); trackEntry.OnComplete();
@ -921,4 +941,57 @@ namespace Spine {
eventQueueEntries.Clear(); eventQueueEntries.Clear();
} }
} }
public class Pool<T> where T : class, new() {
public readonly int max;
readonly Stack<T> freeObjects;
public int Count { get { return freeObjects.Count; } }
public int Peak { get; private set; }
public Pool (int initialCapacity = 16, int max = int.MaxValue) {
freeObjects = new Stack<T>(initialCapacity);
this.max = max;
}
public T Obtain () {
return freeObjects.Count == 0 ? new T() : freeObjects.Pop();
}
public void Free (T obj) {
if (obj == null) throw new ArgumentNullException("obj", "obj cannot be null");
if (freeObjects.Count < max) {
freeObjects.Push(obj);
Peak = Math.Max(Peak, freeObjects.Count);
}
Reset(obj);
}
// protected void FreeAll (List<T> objects) {
// if (objects == null) throw new ArgumentNullException("objects", "objects cannot be null.");
// var freeObjects = this.freeObjects;
// int max = this.max;
// for (int i = 0; i < objects.Count; i++) {
// T obj = objects[i];
// if (obj == null) continue;
// if (freeObjects.Count < max) freeObjects.Push(obj);
// Reset(obj);
// }
// Peak = Math.Max(Peak, freeObjects.Count);
// }
public void Clear () {
freeObjects.Clear();
}
protected void Reset (T obj) {
var poolable = obj as IPoolable;
if (poolable != null) poolable.Reset();
}
public interface IPoolable {
void Reset ();
}
}
} }

View File

@ -134,7 +134,7 @@ public class AnimationStateTests {
expect(0, "end", 1, 1.1f), // expect(0, "end", 1, 1.1f), //
expect(0, "dispose", 1, 1.1f) // expect(0, "dispose", 1, 1.1f) //
); );
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false).setTrackEnd(1);
run(0.1f, 1000, null); run(0.1f, 1000, null);
setup("1/60 time step, dispose queued", // 2 setup("1/60 time step, dispose queued", // 2
@ -160,7 +160,7 @@ public class AnimationStateTests {
state.addAnimation(0, "events1", false, 0); state.addAnimation(0, "events1", false, 0);
state.addAnimation(0, "events0", false, 0); state.addAnimation(0, "events0", false, 0);
state.addAnimation(0, "events1", false, 0); state.addAnimation(0, "events1", false, 0);
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false).setTrackEnd(1);
run(1 / 60f, 1000, null); run(1 / 60f, 1000, null);
setup("30 time step", // 3 setup("30 time step", // 3
@ -172,7 +172,7 @@ public class AnimationStateTests {
expect(0, "end", 30, 60), // expect(0, "end", 30, 60), //
expect(0, "dispose", 30, 60) // expect(0, "dispose", 30, 60) //
); );
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false).setTrackEnd(1);
run(30, 1000, null); run(30, 1000, null);
setup("1 time step", // 4 setup("1 time step", // 4
@ -184,7 +184,7 @@ public class AnimationStateTests {
expect(0, "end", 1, 2), // expect(0, "end", 1, 2), //
expect(0, "dispose", 1, 2) // expect(0, "dispose", 1, 2) //
); );
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false).setTrackEnd(1);
run(1, 1.01f, null); run(1, 1.01f, null);
setup("interrupt", // 5 setup("interrupt", // 5
@ -220,7 +220,7 @@ public class AnimationStateTests {
); );
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 0); state.addAnimation(0, "events1", false, 0);
state.addAnimation(0, "events0", false, 0); state.addAnimation(0, "events0", false, 0).setTrackEnd(1);
run(0.1f, 4f, null); run(0.1f, 4f, null);
setup("interrupt with delay", // 6 setup("interrupt with delay", // 6
@ -242,7 +242,7 @@ public class AnimationStateTests {
expect(1, "dispose", 1, 1.6f) // expect(1, "dispose", 1, 1.6f) //
); );
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 0.5f); state.addAnimation(0, "events1", false, 0.5f).setTrackEnd(1);
run(0.1f, 1000, null); run(0.1f, 1000, null);
setup("interrupt with delay and mix time", // 7 setup("interrupt with delay and mix time", // 7
@ -268,7 +268,7 @@ public class AnimationStateTests {
); );
stateData.setMix("events0", "events1", 0.7f); stateData.setMix("events0", "events1", 0.7f);
state.setAnimation(0, "events0", true); state.setAnimation(0, "events0", true);
state.addAnimation(0, "events1", false, 0.9f); state.addAnimation(0, "events1", false, 0.9f).setTrackEnd(1);
run(0.1f, 1000, null); run(0.1f, 1000, null);
setup("animation 0 events do not fire during mix", // 8 setup("animation 0 events do not fire during mix", // 8
@ -291,7 +291,7 @@ public class AnimationStateTests {
); );
stateData.setDefaultMix(0.7f); stateData.setDefaultMix(0.7f);
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 0.4f); state.addAnimation(0, "events1", false, 0.4f).setTrackEnd(1);
run(0.1f, 1000, null); run(0.1f, 1000, null);
setup("event threshold, some animation 0 events fire during mix", // 9 setup("event threshold, some animation 0 events fire during mix", // 9
@ -317,7 +317,7 @@ public class AnimationStateTests {
); );
stateData.setMix("events0", "events1", 0.7f); stateData.setMix("events0", "events1", 0.7f);
state.setAnimation(0, "events0", false).setEventThreshold(0.5f); state.setAnimation(0, "events0", false).setEventThreshold(0.5f);
state.addAnimation(0, "events1", false, 0.4f); state.addAnimation(0, "events1", false, 0.4f).setTrackEnd(1);
run(0.1f, 1000, null); run(0.1f, 1000, null);
setup("event threshold, all animation 0 events fire during mix", // 10 setup("event threshold, all animation 0 events fire during mix", // 10
@ -344,7 +344,9 @@ public class AnimationStateTests {
expect(1, "dispose", 1, 1.9f) // expect(1, "dispose", 1, 1.9f) //
); );
state.setAnimation(0, "events0", true).setEventThreshold(1); state.setAnimation(0, "events0", true).setEventThreshold(1);
state.addAnimation(0, "events1", false, 0.8f).setMixDuration(0.7f); entry = state.addAnimation(0, "events1", false, 0.8f);
entry.setMixDuration(0.7f);
entry.setTrackEnd(1);
run(0.1f, 1000, null); run(0.1f, 1000, null);
setup("looping", // 11 setup("looping", // 11
@ -393,7 +395,7 @@ public class AnimationStateTests {
expect(1, "dispose", 1, 3.1f) // expect(1, "dispose", 1, 3.1f) //
); );
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false);
state.addAnimation(0, "events1", false, 2); state.addAnimation(0, "events1", false, 2).setTrackEnd(1);
run(0.1f, 4f, null); run(0.1f, 4f, null);
setup("interrupt animation after first loop complete", // 13 setup("interrupt animation after first loop complete", // 13
@ -424,7 +426,7 @@ public class AnimationStateTests {
state.setAnimation(0, "events0", true); state.setAnimation(0, "events0", true);
run(0.1f, 6, new TestListener() { run(0.1f, 6, new TestListener() {
public void frame (float time) { public void frame (float time) {
if (MathUtils.isEqual(time, 1.4f)) state.addAnimation(0, "events1", false, 0); if (MathUtils.isEqual(time, 1.4f)) state.addAnimation(0, "events1", false, 0).setTrackEnd(1);
} }
}); });
@ -437,7 +439,7 @@ public class AnimationStateTests {
expect(0, "end", 1, 1.1f), // expect(0, "end", 1, 1.1f), //
expect(0, "dispose", 1, 1.1f) // expect(0, "dispose", 1, 1.1f) //
); );
state.addAnimation(0, "events0", false, 0); state.addAnimation(0, "events0", false, 0).setTrackEnd(1);
run(0.1f, 1.9f, null); run(0.1f, 1.9f, null);
setup("end time beyond non-looping animation duration", // 15 setup("end time beyond non-looping animation duration", // 15
@ -495,6 +497,7 @@ public class AnimationStateTests {
entry.setAnimationStart(0.2f); entry.setAnimationStart(0.2f);
entry.setAnimationLast(0.2f); entry.setAnimationLast(0.2f);
entry.setAnimationEnd(0.8f); entry.setAnimationEnd(0.8f);
entry.setTrackEnd(1);
run(0.1f, 1.8f, null); run(0.1f, 1.8f, null);
setup("mix out looping with animation start and end", // 19 setup("mix out looping with animation start and end", // 19
@ -524,7 +527,9 @@ public class AnimationStateTests {
entry.setAnimationLast(0.2f); entry.setAnimationLast(0.2f);
entry.setAnimationEnd(0.8f); entry.setAnimationEnd(0.8f);
entry.setEventThreshold(1); entry.setEventThreshold(1);
state.addAnimation(0, "events1", false, 0.7f).setMixDuration(0.7f); entry = state.addAnimation(0, "events1", false, 0.7f);
entry.setMixDuration(0.7f);
entry.setTrackEnd(1);
run(0.1f, 20, null); run(0.1f, 20, null);
setup("setAnimation with track entry mix", // 20 setup("setAnimation with track entry mix", // 20
@ -552,7 +557,11 @@ public class AnimationStateTests {
state.setAnimation(0, "events0", true); state.setAnimation(0, "events0", true);
run(0.1f, 1000, new TestListener() { run(0.1f, 1000, new TestListener() {
public void frame (float time) { public void frame (float time) {
if (MathUtils.isEqual(time, 1f)) state.setAnimation(0, "events1", false).setMixDuration(0.7f); if (MathUtils.isEqual(time, 1f)) {
TrackEntry entry = state.setAnimation(0, "events1", false);
entry.setMixDuration(0.7f);
entry.setTrackEnd(1);
}
} }
}); });
@ -593,7 +602,7 @@ public class AnimationStateTests {
public void frame (float time) { public void frame (float time) {
if (MathUtils.isEqual(time, 0.8f)) { if (MathUtils.isEqual(time, 0.8f)) {
state.setAnimation(0, "events0", false); // First should be ignored. state.setAnimation(0, "events0", false); // First should be ignored.
state.setAnimation(0, "events2", false); state.setAnimation(0, "events2", false).setTrackEnd(1);
} }
} }
}); });
@ -655,7 +664,7 @@ public class AnimationStateTests {
} }
if (MathUtils.isEqual(time, 0.4f)) { if (MathUtils.isEqual(time, 0.4f)) {
state.setAnimation(0, "events1", false); // First should be ignored. state.setAnimation(0, "events1", false); // First should be ignored.
state.setAnimation(0, "events0", false); state.setAnimation(0, "events0", false).setTrackEnd(1);
} }
} }
}); });
@ -669,7 +678,7 @@ public class AnimationStateTests {
expect(0, "end", 1, 6.1f), // expect(0, "end", 1, 6.1f), //
expect(0, "dispose", 1, 6.1f) // expect(0, "dispose", 1, 6.1f) //
); );
state.addAnimation(0, "events0", false, 5); state.addAnimation(0, "events0", false, 5).setTrackEnd(1);
run(0.1f, 10, null); run(0.1f, 10, null);
setup("setAnimation during AnimationStateListener"); // 24 setup("setAnimation during AnimationStateListener"); // 24
@ -700,7 +709,7 @@ public class AnimationStateTests {
}); });
state.addAnimation(0, "events0", false, 0); state.addAnimation(0, "events0", false, 0);
state.addAnimation(0, "events1", false, 0); state.addAnimation(0, "events1", false, 0);
state.setAnimation(1, "events1", false); state.setAnimation(1, "events1", false).setTrackEnd(1);
run(0.1f, 10, null); run(0.1f, 10, null);
setup("clearTrack", // 25 setup("clearTrack", // 25
@ -710,7 +719,7 @@ public class AnimationStateTests {
expect(0, "end", 0.7f, 0.7f), // expect(0, "end", 0.7f, 0.7f), //
expect(0, "dispose", 0.7f, 0.7f) // expect(0, "dispose", 0.7f, 0.7f) //
); );
state.addAnimation(0, "events0", false, 0); state.addAnimation(0, "events0", false, 0).setTrackEnd(1);
run(0.1f, 10, new TestListener() { run(0.1f, 10, new TestListener() {
public void frame (float time) { public void frame (float time) {
if (MathUtils.isEqual(time, 0.7f)) state.clearTrack(0); if (MathUtils.isEqual(time, 0.7f)) state.clearTrack(0);
@ -732,7 +741,7 @@ public class AnimationStateTests {
expect(-1, "end", 0.2f, 1), // expect(-1, "end", 0.2f, 1), //
expect(-1, "dispose", 0.2f, 1) // expect(-1, "dispose", 0.2f, 1) //
); );
state.addAnimation(0, "events0", false, 0); state.addAnimation(0, "events0", false, 0).setTrackEnd(1);
run(0.1f, 10, new TestListener() { run(0.1f, 10, new TestListener() {
public void frame (float time) { public void frame (float time) {
if (MathUtils.isEqual(time, 0.7f)) state.setEmptyAnimation(0, 0); if (MathUtils.isEqual(time, 0.7f)) state.setEmptyAnimation(0, 0);
@ -768,7 +777,7 @@ public class AnimationStateTests {
}); });
state.addAnimation(0, "events0", false, 0); state.addAnimation(0, "events0", false, 0);
state.addAnimation(0, "events1", false, 0); state.addAnimation(0, "events1", false, 0);
state.setAnimation(1, "events1", false); state.setAnimation(1, "events1", false).setTrackEnd(1);
run(0.1f, 10, null); run(0.1f, 10, null);
if (counter.get() != 15082016) { if (counter.get() != 15082016) {
log("TEST 26 FAILED! " + counter); log("TEST 26 FAILED! " + counter);

View File

@ -185,6 +185,7 @@ public class AnimationState {
} }
} }
queueEvents(current, animationTime); queueEvents(current, animationTime);
events.clear();
current.nextAnimationLast = animationTime; current.nextAnimationLast = animationTime;
current.nextTrackLast = current.trackTime; current.nextTrackLast = current.trackTime;
} }
@ -230,7 +231,8 @@ public class AnimationState {
} }
} }
queueEvents(from, animationTime); if (entry.mixDuration > 0) queueEvents(from, animationTime);
this.events.clear();
from.nextAnimationLast = animationTime; from.nextAnimationLast = animationTime;
from.nextTrackLast = from.trackTime; from.nextTrackLast = from.trackTime;
@ -330,7 +332,6 @@ public class AnimationState {
if (event.time < animationStart) continue; // Discard events outside animation start/end. if (event.time < animationStart) continue; // Discard events outside animation start/end.
queue.event(entry, events.get(i)); queue.event(entry, events.get(i));
} }
events.clear();
} }
/** Removes all animations from all tracks, leaving skeletons in their previous pose. /** Removes all animations from all tracks, leaving skeletons in their previous pose.
@ -385,7 +386,7 @@ public class AnimationState {
from.timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in. from.timelinesRotation.clear(); // Reset rotation for mixing out, in case entry was mixed in.
// If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
if (from.mixingFrom != null) current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); if (from.mixingFrom != null && from.mixDuration > 0) current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
queue.start(current); queue.start(current);
@ -539,7 +540,7 @@ public class AnimationState {
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Integer.MAX_VALUE : entry.animationEnd; entry.trackEnd = Float.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;
@ -752,10 +753,10 @@ public class AnimationState {
this.trackTime = trackTime; this.trackTime = trackTime;
} }
/** The track time in seconds when this animation will be removed from the track. Defaults to the animation /** The track time in seconds when this animation will be removed from the track. Defaults to the highest possible float
* {@link Animation#duration} for non-looping animations and the highest float possible for looping animations. If the track * value, meaning the animation will be applied until a new animation is set or the track is cleared. If the track end time
* end time is reached, no other animations are queued for playback, and mixing from any previous animations is complete, * is reached, no other animations are queued for playback, and mixing from any previous animations is complete, then the
* then the properties keyed by the animation are set to the setup pose and the track is cleared. * properties keyed by the animation are set to the setup pose and the track is cleared.
* <p> * <p>
* It may be desired to use {@link AnimationState#addEmptyAnimation(int, float, float)} to mix the properties back to the * It may be desired to use {@link AnimationState#addEmptyAnimation(int, float, float)} to mix the properties back to the
* setup pose over time, rather than have it happen instantly. */ * setup pose over time, rather than have it happen instantly. */

View File

@ -541,7 +541,7 @@ function AnimationState:setCurrent (index, current, interrupt)
from.timelinesRotation = {}; from.timelinesRotation = {};
-- If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. -- If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
if from.mixingFrom then current.mixAlpha = current.mixAlpha * math_min(from.mixTime / from.mixDuration, 1) end if from.mixingFrom and from.mixDuration > 0 then current.mixAlpha = current.mixAlpha * math_min(from.mixTime / from.mixDuration, 1) end
end end
queue:start(current) queue:start(current)
@ -665,11 +665,7 @@ function AnimationState:trackEntry (trackIndex, animation, loop, last)
entry.trackTime = 0 entry.trackTime = 0
entry.trackLast = -1 entry.trackLast = -1
entry.nextTrackLast = -1 entry.nextTrackLast = -1
if loop then entry.trackEnd = 999999999
entry.trackEnd = 999999999
else
entry.trackEnd = entry.animationEnd
end
entry.timeScale = 1 entry.timeScale = 1
entry.alpha = 1 entry.alpha = 1

View File

@ -75,8 +75,8 @@ setup a development environment, follow these steps.
* **WebGL**: `tsc -w -p tsconfig.webgl.json`, builds `core/src` and `webgl/src`, outputs `build/spine-webgl.js|d.ts|js.map` * **WebGL**: `tsc -w -p tsconfig.webgl.json`, builds `core/src` and `webgl/src`, outputs `build/spine-webgl.js|d.ts|js.map`
* **Canvas**: `tsc -w -p tsconfig.canvas.json`, builds `core/src` and `canvas/src`, outputs `build/spine-canvas.js|d.ts|js.map` * **Canvas**: `tsc -w -p tsconfig.canvas.json`, builds `core/src` and `canvas/src`, outputs `build/spine-canvas.js|d.ts|js.map`
* **THREE.JS**: `tsc -w -p tsconfig.threejs.json`, builds `core/src` and `threejs/src`, outputs `build/spine-threejs.js|d.ts|js.map` * **THREE.JS**: `tsc -w -p tsconfig.threejs.json`, builds `core/src` and `threejs/src`, outputs `build/spine-threejs.js|d.ts|js.map`
* **Widget**: `tsc -w -p tsconfig.widget.json`, builds `core/src` and `widget/src`, outputs `build/spine-widget.js|d.ts|js.map` * **Widget**: `tsc -w -p tsconfig.widget.json`, builds `core/src` and `widget/src`, outputs `build/spine-widget.js|d.ts|js.map`
6. Open the `spine-ts` folder in Visual Studio Code. VS Code will use the `tsconfig.json` file all source files from core and all 6. Open the `spine-ts` folder in Visual Studio Code. VS Code will use the `tsconfig.json` file all source files from core and all
backends for your development pleasure. The actual JavaScript output is still created by the command line TypeScript compiler process from the previous step. backends for your development pleasure. The actual JavaScript output is still created by the command line TypeScript compiler process from the previous step.
Each backend contains an `example/` folder with an `index.html` file that demonstrates the respective backend. For development, we Each backend contains an `example/` folder with an `index.html` file that demonstrates the respective backend. For development, we
@ -90,7 +90,7 @@ python -m SimpleHTTPServer
Then navigate to `http://localhost:8000/webgl/example`, `http://localhost:8000/canvas/example`, `http://localhost:8000/threejs/example` or `http://localhost:8000/widget/example` Then navigate to `http://localhost:8000/webgl/example`, `http://localhost:8000/canvas/example`, `http://localhost:8000/threejs/example` or `http://localhost:8000/widget/example`
### Using the Widget ### Using the Widget
To easily display Spine animations on your website, you can use the spine-ts Widget backend. To easily display Spine animations on your website, you can use the spine-ts Widget backend.
1. Export your Spine animation with a texture atlas and put the resulting `.json`, `.atlas` and `.png` files on your server. 1. Export your Spine animation with a texture atlas and put the resulting `.json`, `.atlas` and `.png` files on your server.
2. Copy the `build/spine-widget.js` file to your server and include it on your website `<script src="spine-widget.js"></script>`, adjusting the src to match the location of the file on your server. 2. Copy the `build/spine-widget.js` file to your server and include it on your website `<script src="spine-widget.js"></script>`, adjusting the src to match the location of the file on your server.
@ -114,7 +114,7 @@ To specify the configuration of a Spine Widget via HTML, you can use these HTML
* `data-fit-to-canvas`: optional, whether to fit the animation to the canvas size or not. Defaults to `true` if omitted, in which case `data-scale`, `data-x` and `data-y` are irrelevant. This setting calculates the setup pose bounding box using the specified skin to center and scale the animation on the canvas. * `data-fit-to-canvas`: optional, whether to fit the animation to the canvas size or not. Defaults to `true` if omitted, in which case `data-scale`, `data-x` and `data-y` are irrelevant. This setting calculates the setup pose bounding box using the specified skin to center and scale the animation on the canvas.
* `data-background-color`: optional, the background color to use. Defaults to `#000000` if omitted. * `data-background-color`: optional, the background color to use. Defaults to `#000000` if omitted.
* `data-premultiplied-alpha`: optional, whether the atlas pages use premultiplied alpha or not. Defaults to `false` if omitted. * `data-premultiplied-alpha`: optional, whether the atlas pages use premultiplied alpha or not. Defaults to `false` if omitted.
* `data-debug`: optional, whether to show debug information such as bones, attachments, etc. Defaults to `false` if omitted. * `data-debug`: optional, whether to show debug information such as bones, attachments, etc. Defaults to `false` if omitted.
You can specify these as attribuets on the HTML element like this: You can specify these as attribuets on the HTML element like this:
@ -139,12 +139,12 @@ Then create a new `spine.SpineWidget`, providing a [`SpineWidgetConfiguration`](
```JavaScript ```JavaScript
new spine.SpineWidget("my-widget", { new spine.SpineWidget("my-widget", {
json: "assets/spineboy.json", json: "assets/spineboy.json",
atlas: "assets/spineboy.atlas", atlas: "assets/spineboy.atlas",
animation: "run", animation: "run",
backgroundColor: "#000000", backgroundColor: "#000000",
success: function (widget) { success: function (widget) {
var animIndex = 0; var animIndex = 0;
widget.canvas.onclick = function () { widget.canvas.onclick = function () {
animIndex++; animIndex++;
let animations = widget.skeleton.data.animations; let animations = widget.skeleton.data.animations;
if (animIndex >= animations.length) animIndex = 0; if (animIndex >= animations.length) animIndex = 0;
@ -160,6 +160,7 @@ The configuration object has the following fields:
* `atlas`: required, path to the `.atlas` file, absolute or relative, e.g. "assets/animation.atlas" * `atlas`: required, path to the `.atlas` file, absolute or relative, e.g. "assets/animation.atlas"
* `animation`: required, the name of the animation to play back * `animation`: required, the name of the animation to play back
* `imagesPath`: optional, the location of images on the server to load atlas pages from. If omitted, atlas `.png` page files are loaded relative to the `.atlas` file. * `imagesPath`: optional, the location of images on the server to load atlas pages from. If omitted, atlas `.png` page files are loaded relative to the `.atlas` file.
* `atlasPages`: optional, the list of atlas page images, e.g. `atlasPages: ["assets/page1.png", "assets/page2.png"]` when using code, or `data-atlas-pages="assets/page1.png,assets/page2.png"` on case of HTML instantiation. Use this if you have a multi-page atlas. If ommited, only one atlas page image is loaded based on the atlas file name, replacing `.atlas` with `.png`.
* `skin`: optional, the name of the skin to use. Defaults to `default` if omitted. * `skin`: optional, the name of the skin to use. Defaults to `default` if omitted.
* `loop`: optional, whether to loop the animation or not. Defaults to `true` if omitted. * `loop`: optional, whether to loop the animation or not. Defaults to `true` if omitted.
* `scale`: optional, the scaling factor to apply when loading the `.json` file. Defaults to `1` if omitted. Irrelevant if `data-fit-to-canavs` is `true`. * `scale`: optional, the scaling factor to apply when loading the `.json` file. Defaults to `1` if omitted. Irrelevant if `data-fit-to-canavs` is `true`.

View File

@ -1543,6 +1543,7 @@ declare module spine {
atlas: string; atlas: string;
animation: string; animation: string;
imagesPath: string; imagesPath: string;
atlasPages: string[];
skin: string; skin: string;
loop: boolean; loop: boolean;
scale: number; scale: number;

View File

@ -1568,7 +1568,7 @@ var spine;
current.mixingFrom = from; current.mixingFrom = from;
current.mixTime = 0; current.mixTime = 0;
from.timelinesRotation.length = 0; from.timelinesRotation.length = 0;
if (from.mixingFrom != null) if (from.mixingFrom != null && from.mixDuration > 0)
current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
this.queue.start(current); this.queue.start(current);
@ -1680,7 +1680,7 @@ var spine;
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Number.MAX_VALUE : entry.animationEnd; entry.trackEnd = Number.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;
entry.mixAlpha = 1; entry.mixAlpha = 1;
@ -7899,7 +7899,14 @@ var spine;
var assets = this.assetManager = new spine.webgl.AssetManager(gl); var assets = this.assetManager = new spine.webgl.AssetManager(gl);
assets.loadText(config.atlas); assets.loadText(config.atlas);
assets.loadText(config.json); assets.loadText(config.json);
assets.loadTexture(config.atlas.replace(".atlas", ".png")); if (config.atlasPages == null) {
assets.loadTexture(config.atlas.replace(".atlas", ".png"));
}
else {
for (var i = 0; i < config.atlasPages.length; i++) {
assets.loadTexture(config.atlasPages[i]);
}
}
requestAnimationFrame(function () { _this.load(); }); requestAnimationFrame(function () { _this.load(); });
} }
SpineWidget.prototype.validateConfig = function (config) { SpineWidget.prototype.validateConfig = function (config) {
@ -8082,6 +8089,8 @@ var spine;
config.animation = widget.getAttribute("data-animation"); config.animation = widget.getAttribute("data-animation");
if (widget.getAttribute("data-images-path")) if (widget.getAttribute("data-images-path"))
config.imagesPath = widget.getAttribute("data-images-path"); config.imagesPath = widget.getAttribute("data-images-path");
if (widget.getAttribute("data-atlas-pages"))
config.atlasPages = widget.getAttribute("data-atlas-pages").split(",");
if (widget.getAttribute("data-skin")) if (widget.getAttribute("data-skin"))
config.skin = widget.getAttribute("data-skin"); config.skin = widget.getAttribute("data-skin");
if (widget.getAttribute("data-loop")) if (widget.getAttribute("data-loop"))

File diff suppressed because one or more lines are too long

View File

@ -1568,7 +1568,7 @@ var spine;
current.mixingFrom = from; current.mixingFrom = from;
current.mixTime = 0; current.mixTime = 0;
from.timelinesRotation.length = 0; from.timelinesRotation.length = 0;
if (from.mixingFrom != null) if (from.mixingFrom != null && from.mixDuration > 0)
current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
this.queue.start(current); this.queue.start(current);
@ -1680,7 +1680,7 @@ var spine;
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Number.MAX_VALUE : entry.animationEnd; entry.trackEnd = Number.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;
entry.mixAlpha = 1; entry.mixAlpha = 1;

File diff suppressed because one or more lines are too long

View File

@ -1221,7 +1221,7 @@ var spine;
current.mixingFrom = from; current.mixingFrom = from;
current.mixTime = 0; current.mixTime = 0;
from.timelinesRotation.length = 0; from.timelinesRotation.length = 0;
if (from.mixingFrom != null) if (from.mixingFrom != null && from.mixDuration > 0)
current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
this.queue.start(current); this.queue.start(current);
@ -1333,7 +1333,7 @@ var spine;
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Number.MAX_VALUE : entry.animationEnd; entry.trackEnd = Number.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;
entry.mixAlpha = 1; entry.mixAlpha = 1;

File diff suppressed because one or more lines are too long

View File

@ -1221,7 +1221,7 @@ var spine;
current.mixingFrom = from; current.mixingFrom = from;
current.mixTime = 0; current.mixTime = 0;
from.timelinesRotation.length = 0; from.timelinesRotation.length = 0;
if (from.mixingFrom != null) if (from.mixingFrom != null && from.mixDuration > 0)
current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
this.queue.start(current); this.queue.start(current);
@ -1333,7 +1333,7 @@ var spine;
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Number.MAX_VALUE : entry.animationEnd; entry.trackEnd = Number.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;
entry.mixAlpha = 1; entry.mixAlpha = 1;

File diff suppressed because one or more lines are too long

View File

@ -1221,7 +1221,7 @@ var spine;
current.mixingFrom = from; current.mixingFrom = from;
current.mixTime = 0; current.mixTime = 0;
from.timelinesRotation.length = 0; from.timelinesRotation.length = 0;
if (from.mixingFrom != null) if (from.mixingFrom != null && from.mixDuration > 0)
current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
this.queue.start(current); this.queue.start(current);
@ -1333,7 +1333,7 @@ var spine;
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Number.MAX_VALUE : entry.animationEnd; entry.trackEnd = Number.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;
entry.mixAlpha = 1; entry.mixAlpha = 1;

File diff suppressed because one or more lines are too long

View File

@ -1473,6 +1473,7 @@ declare module spine {
atlas: string; atlas: string;
animation: string; animation: string;
imagesPath: string; imagesPath: string;
atlasPages: string[];
skin: string; skin: string;
loop: boolean; loop: boolean;
scale: number; scale: number;

View File

@ -1221,7 +1221,7 @@ var spine;
current.mixingFrom = from; current.mixingFrom = from;
current.mixTime = 0; current.mixTime = 0;
from.timelinesRotation.length = 0; from.timelinesRotation.length = 0;
if (from.mixingFrom != null) if (from.mixingFrom != null && from.mixDuration > 0)
current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
this.queue.start(current); this.queue.start(current);
@ -1333,7 +1333,7 @@ var spine;
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Number.MAX_VALUE : entry.animationEnd; entry.trackEnd = Number.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;
entry.mixAlpha = 1; entry.mixAlpha = 1;
@ -7478,7 +7478,14 @@ var spine;
var assets = this.assetManager = new spine.webgl.AssetManager(gl); var assets = this.assetManager = new spine.webgl.AssetManager(gl);
assets.loadText(config.atlas); assets.loadText(config.atlas);
assets.loadText(config.json); assets.loadText(config.json);
assets.loadTexture(config.atlas.replace(".atlas", ".png")); if (config.atlasPages == null) {
assets.loadTexture(config.atlas.replace(".atlas", ".png"));
}
else {
for (var i = 0; i < config.atlasPages.length; i++) {
assets.loadTexture(config.atlasPages[i]);
}
}
requestAnimationFrame(function () { _this.load(); }); requestAnimationFrame(function () { _this.load(); });
} }
SpineWidget.prototype.validateConfig = function (config) { SpineWidget.prototype.validateConfig = function (config) {
@ -7661,6 +7668,8 @@ var spine;
config.animation = widget.getAttribute("data-animation"); config.animation = widget.getAttribute("data-animation");
if (widget.getAttribute("data-images-path")) if (widget.getAttribute("data-images-path"))
config.imagesPath = widget.getAttribute("data-images-path"); config.imagesPath = widget.getAttribute("data-images-path");
if (widget.getAttribute("data-atlas-pages"))
config.atlasPages = widget.getAttribute("data-atlas-pages").split(",");
if (widget.getAttribute("data-skin")) if (widget.getAttribute("data-skin"))
config.skin = widget.getAttribute("data-skin"); config.skin = widget.getAttribute("data-skin");
if (widget.getAttribute("data-loop")) if (widget.getAttribute("data-loop"))

File diff suppressed because one or more lines are too long

View File

@ -349,7 +349,7 @@ module spine {
from.timelinesRotation.length = 0; from.timelinesRotation.length = 0;
// If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero. // If not completely mixed in, set mixAlpha so mixing out happens from current mix to zero.
if (from.mixingFrom != null) current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1); if (from.mixingFrom != null && from.mixDuration > 0) current.mixAlpha *= Math.min(from.mixTime / from.mixDuration, 1);
} }
this.queue.start(current); this.queue.start(current);
@ -469,7 +469,7 @@ module spine {
entry.trackTime = 0; entry.trackTime = 0;
entry.trackLast = -1; entry.trackLast = -1;
entry.nextTrackLast = -1; entry.nextTrackLast = -1;
entry.trackEnd = loop ? Number.MAX_VALUE : entry.animationEnd; entry.trackEnd = Number.MAX_VALUE;
entry.timeScale = 1; entry.timeScale = 1;
entry.alpha = 1; entry.alpha = 1;

View File

@ -80,7 +80,13 @@ module spine {
let assets = this.assetManager = new spine.webgl.AssetManager(gl); let assets = this.assetManager = new spine.webgl.AssetManager(gl);
assets.loadText(config.atlas); assets.loadText(config.atlas);
assets.loadText(config.json); assets.loadText(config.json);
assets.loadTexture(config.atlas.replace(".atlas", ".png")); if (config.atlasPages == null) {
assets.loadTexture(config.atlas.replace(".atlas", ".png"));
} else {
for (let i = 0; i < config.atlasPages.length; i++) {
assets.loadTexture(config.atlasPages[i]);
}
}
requestAnimationFrame(() => { this.load(); }); requestAnimationFrame(() => { this.load(); });
} }
@ -264,6 +270,7 @@ module spine {
config.json = widget.getAttribute("data-json"); config.json = widget.getAttribute("data-json");
config.animation = widget.getAttribute("data-animation"); config.animation = widget.getAttribute("data-animation");
if (widget.getAttribute("data-images-path")) config.imagesPath = widget.getAttribute("data-images-path"); if (widget.getAttribute("data-images-path")) config.imagesPath = widget.getAttribute("data-images-path");
if (widget.getAttribute("data-atlas-pages")) config.atlasPages = widget.getAttribute("data-atlas-pages").split(",");
if (widget.getAttribute("data-skin")) config.skin = widget.getAttribute("data-skin"); if (widget.getAttribute("data-skin")) config.skin = widget.getAttribute("data-skin");
if (widget.getAttribute("data-loop")) config.loop = widget.getAttribute("data-loop") === "true"; if (widget.getAttribute("data-loop")) config.loop = widget.getAttribute("data-loop") === "true";
if (widget.getAttribute("data-scale")) config.scale = parseFloat(widget.getAttribute("data-scale")); if (widget.getAttribute("data-scale")) config.scale = parseFloat(widget.getAttribute("data-scale"));
@ -303,6 +310,7 @@ module spine {
atlas: string; atlas: string;
animation: string; animation: string;
imagesPath: string; imagesPath: string;
atlasPages: string[];
skin = "default"; skin = "default";
loop = true; loop = true;
scale = 1.0; scale = 1.0;

View File

@ -69,9 +69,8 @@ namespace Spine.Unity.Examples {
SetXPosition(endX); SetXPosition(endX);
separator.enabled = true; // Enable Separator when hit separator.enabled = true; // Enable Separator when hit
var poleTrack = state.SetAnimation(0, pole, false); var poleTrack = state.SetAnimation(0, pole, false);
float duration = poleTrack.TrackEnd; yield return new WaitForSpineAnimationComplete(poleTrack);
poleTrack.TrackEnd = float.PositiveInfinity; yield return new WaitForSeconds(1f);
yield return new WaitForSeconds(duration + 1f);
} }
} }

View File

@ -1079,7 +1079,7 @@ namespace Spine.Unity.Editor {
AssetDatabase.CreateAsset(skeletonDataAsset, filePath); AssetDatabase.CreateAsset(skeletonDataAsset, filePath);
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
} else { } else {
skeletonDataAsset.Reset(); skeletonDataAsset.Clear();
skeletonDataAsset.GetSkeletonData(true); skeletonDataAsset.GetSkeletonData(true);
} }
@ -1312,16 +1312,16 @@ namespace Spine.Unity.Editor {
anim.skeletonDataAsset = skeletonDataAsset; anim.skeletonDataAsset = skeletonDataAsset;
// Detect "Lit" shader and automatically enable calculateNormals. // Detect "Lit" shader and automatically enable calculateNormals.
bool requiresNormals = false; // bool requiresNormals = false;
foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) { // foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
foreach (Material m in atlasAsset.materials) { // foreach (Material m in atlasAsset.materials) {
if (m.shader.name.Contains("Lit")) { // if (m.shader.name.Contains("Lit")) {
requiresNormals = true; // requiresNormals = true;
break; // break;
} // }
} // }
} // }
anim.calculateNormals = requiresNormals; // anim.calculateNormals = requiresNormals;
SkeletonData data = skeletonDataAsset.GetSkeletonData(true); SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
if (data == null) { if (data == null) {
@ -1352,8 +1352,9 @@ namespace Spine.Unity.Editor {
static void EnableTK2D () { static void EnableTK2D () {
bool added = false; bool added = false;
foreach (BuildTargetGroup group in System.Enum.GetValues(typeof(BuildTargetGroup))) { foreach (BuildTargetGroup group in System.Enum.GetValues(typeof(BuildTargetGroup))) {
if (group == BuildTargetGroup.Unknown) int gi = (int)group;
if (gi == 15 || gi == 16 || group == BuildTargetGroup.Unknown)
continue; continue;
string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group); string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
@ -1379,6 +1380,10 @@ namespace Spine.Unity.Editor {
static void DisableTK2D () { static void DisableTK2D () {
bool removed = false; bool removed = false;
foreach (BuildTargetGroup group in System.Enum.GetValues(typeof(BuildTargetGroup))) { foreach (BuildTargetGroup group in System.Enum.GetValues(typeof(BuildTargetGroup))) {
int gi = (int)group;
if (gi == 15 || gi == 16 || group == BuildTargetGroup.Unknown)
continue;
string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group); string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
if (defines.Contains(SPINE_TK2D_DEFINE)) { if (defines.Contains(SPINE_TK2D_DEFINE)) {
removed = true; removed = true;

View File

@ -62,14 +62,13 @@ namespace Spine.Unity {
Debug.LogWarning("TrackEntry was null. Coroutine will continue immediately."); Debug.LogWarning("TrackEntry was null. Coroutine will continue immediately.");
m_WasFired = true; m_WasFired = true;
} else { } else {
// Function normally.
trackEntry.Complete += HandleComplete; trackEntry.Complete += HandleComplete;
} }
} }
#region Reuse #region Reuse
/// <summary> /// <summary>
/// One optimization high-frequency YieldInstruction returns is to cache instances to minimize pressure. /// One optimization high-frequency YieldInstruction returns is to cache instances to minimize GC pressure.
/// Use NowWaitFor to reuse the same instance of WaitForSpineAnimationComplete.</summary> /// Use NowWaitFor to reuse the same instance of WaitForSpineAnimationComplete.</summary>
public WaitForSpineAnimationComplete NowWaitFor (Spine.TrackEntry trackEntry) { public WaitForSpineAnimationComplete NowWaitFor (Spine.TrackEntry trackEntry) {
SafeSubscribe(trackEntry); SafeSubscribe(trackEntry);

View File

@ -0,0 +1,94 @@
/******************************************************************************
* Spine Runtimes Software License v2.5
*
* Copyright (c) 2013-2016, Esoteric Software
* All rights reserved.
*
* You are granted a perpetual, non-exclusive, non-sublicensable, and
* non-transferable license to use, install, execute, and perform the Spine
* Runtimes software and derivative works solely for personal or internal
* use. Without the written permission of Esoteric Software (see Section 2 of
* the Spine Software License Agreement), you may not (a) modify, translate,
* adapt, or develop new applications using the Spine Runtimes or otherwise
* create derivative works or improvements of the Spine Runtimes or (b) remove,
* delete, alter, or obscure any trademarks or any copyright, trademark, patent,
* or other intellectual property or proprietary rights notices on or in the
* Software, including any copy thereof. Redistributions in binary or source
* form must include this license and terms.
*
* THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL ESOTERIC SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS INTERRUPTION, OR LOSS OF
* USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#if (UNITY_5_0 || UNITY_5_1 || UNITY_5_2 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_4_7)
#define PREUNITY_5_3
#endif
using UnityEngine;
using System.Collections;
using Spine;
namespace Spine.Unity {
/// <summary>
/// Use this as a condition-blocking yield instruction for Unity Coroutines.
/// The routine will pause until the AnimationState.TrackEntry fires its End event.</summary>
public class WaitForSpineTrackEntryEnd : IEnumerator {
bool m_WasFired = false;
public WaitForSpineTrackEntryEnd (Spine.TrackEntry trackEntry) {
#if PREUNITY_5_3
Debug.LogWarning("Unity 5.3 or later is required for Spine Unity custom yield instructions to function correctly.");
#endif
SafeSubscribe(trackEntry);
}
void HandleEnd (TrackEntry trackEntry) {
m_WasFired = true;
}
void SafeSubscribe (Spine.TrackEntry trackEntry) {
if (trackEntry == null) {
// Break immediately if trackEntry is null.
Debug.LogWarning("TrackEntry was null. Coroutine will continue immediately.");
m_WasFired = true;
} else {
trackEntry.End += HandleEnd;
}
}
#region Reuse
/// <summary>
/// One optimization high-frequency YieldInstruction returns is to cache instances to minimize GC pressure.
/// Use NowWaitFor to reuse the same instance of WaitForSpineAnimationEnd.</summary>
public WaitForSpineTrackEntryEnd NowWaitFor (Spine.TrackEntry trackEntry) {
SafeSubscribe(trackEntry);
return this;
}
#endregion
#region IEnumerator
bool IEnumerator.MoveNext () {
if (m_WasFired) {
((IEnumerator)this).Reset(); // auto-reset for YieldInstruction reuse
return false;
}
return true;
}
void IEnumerator.Reset () { m_WasFired = false; }
object IEnumerator.Current { get { return null; } }
#endregion
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8036c6c2897d2764db92f632d2aef568
timeCreated: 1480672707
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -132,8 +132,8 @@ namespace Spine.Unity.Editor {
skeleton.FlipY = EditorGUILayout.ToggleLeft("skeleton.FlipY", skeleton.FlipY); skeleton.FlipY = EditorGUILayout.ToggleLeft("skeleton.FlipY", skeleton.FlipY);
requireRepaint |= EditorGUI.EndChangeCheck(); requireRepaint |= EditorGUI.EndChangeCheck();
foreach (var t in skeleton.IkConstraints) // foreach (var t in skeleton.IkConstraints)
EditorGUILayout.LabelField(t.Data.Name + " " + t.Mix + " " + t.Target.Data.Name); // EditorGUILayout.LabelField(t.Data.Name + " " + t.Mix + " " + t.Target.Data.Name);
showSlots.target = EditorGUILayout.Foldout(showSlots.target, SlotsRootLabel); showSlots.target = EditorGUILayout.Foldout(showSlots.target, SlotsRootLabel);
if (showSlots.faded > 0) { if (showSlots.faded > 0) {

View File

@ -110,12 +110,13 @@ namespace Spine.Unity {
public Transform boneRoot; public Transform boneRoot;
void Update () { void Update () {
if (boneRoot != null && skeletonRenderer.skeleton != null) { var skeleton = skeletonRenderer.skeleton;
if (boneRoot != null && skeleton != null) {
Vector3 flipScale = Vector3.one; Vector3 flipScale = Vector3.one;
if (skeletonRenderer.skeleton.FlipX) if (skeleton.FlipX)
flipScale.x = -1; flipScale.x = -1;
if (skeletonRenderer.skeleton.FlipY) if (skeleton.FlipY)
flipScale.y = -1; flipScale.y = -1;
boneRoot.localScale = flipScale; boneRoot.localScale = flipScale;

View File

@ -121,7 +121,7 @@ namespace Spine.Unity {
cachedTransform.localPosition = new Vector3(bone.x, bone.y, 0); cachedTransform.localPosition = new Vector3(bone.x, bone.y, 0);
if (rotation) { if (rotation) {
if (!bone.data.transformMode.InheritsRotation()) { if (bone.data.transformMode.InheritsRotation()) {
cachedTransform.localRotation = Quaternion.Euler(0, 0, bone.AppliedRotation); cachedTransform.localRotation = Quaternion.Euler(0, 0, bone.AppliedRotation);
} else { } else {
Vector3 euler = skeletonTransform.rotation.eulerAngles; Vector3 euler = skeletonTransform.rotation.eulerAngles;