diff --git a/spine-csharp/spine-csharp_xna.csproj b/spine-csharp/spine-csharp_xna.csproj
index 373273e0d..12513e295 100644
--- a/spine-csharp/spine-csharp_xna.csproj
+++ b/spine-csharp/spine-csharp_xna.csproj
@@ -105,6 +105,8 @@
+
+
diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs
index 01624fbe5..32433c108 100644
--- a/spine-csharp/src/Animation.cs
+++ b/spine-csharp/src/Animation.cs
@@ -48,27 +48,47 @@ namespace Spine {
Duration = duration;
}
- /** Poses the skeleton at the specified time for this animation. */
+ /** @deprecated */
public void Apply (Skeleton skeleton, float time, bool loop) {
+ Apply(skeleton, time, time, loop, null);
+ }
+
+ /** Poses the skeleton at the specified time for this animation.
+ * @param lastTime The last time the animation was applied. Can be equal to time if events shouldn't be fired.
+ * @param events Any triggered events are added. May be null if lastTime is known to not cause any events to trigger. */
+ public void Apply (Skeleton skeleton, float lastTime, float time, bool loop, List events) {
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
- if (loop && Duration != 0) time %= Duration;
+ if (loop && Duration != 0) {
+ time %= Duration;
+ lastTime %= Duration;
+ }
List timelines = Timelines;
for (int i = 0, n = timelines.Count; i < n; i++)
- timelines[i].Apply(skeleton, time, 1);
+ timelines[i].Apply(skeleton, lastTime, time, 1, events);
+ }
+
+ /** @deprecated */
+ public void Mix (Skeleton skeleton, float time, bool loop, float alpha) {
+ Mix(skeleton, time, time, loop, null, alpha);
}
/** Poses the skeleton at the specified time for this animation mixed with the current pose.
+ * @param lastTime The last time the animation was applied. Can be equal to time if events shouldn't be fired.
+ * @param events Any triggered events are added. May be null if lastTime is known to not cause any events to trigger.
* @param alpha The amount of this animation that affects the current pose. */
- public void Mix (Skeleton skeleton, float time, bool loop, float alpha) {
+ public void Mix (Skeleton skeleton, float lastTime, float time, bool loop, List events, float alpha) {
if (skeleton == null) throw new ArgumentNullException("skeleton cannot be null.");
- if (loop && Duration != 0) time %= Duration;
+ if (loop && Duration != 0) {
+ time %= Duration;
+ lastTime %= Duration;
+ }
List timelines = Timelines;
for (int i = 0, n = timelines.Count; i < n; i++)
- timelines[i].Apply(skeleton, time, alpha);
+ timelines[i].Apply(skeleton, lastTime, time, alpha, events);
}
/** @param target After the first and before the last entry. */
@@ -96,7 +116,7 @@ namespace Spine {
public interface Timeline {
/** Sets the value(s) for the specified time. */
- void Apply (Skeleton skeleton, float time, float alpha);
+ void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents);
}
/** Base class for frames that use an interpolation bezier curve. */
@@ -116,7 +136,7 @@ namespace Spine {
curves = new float[(frameCount - 1) * 6];
}
- abstract public void Apply (Skeleton skeleton, float time, float alpha);
+ abstract public void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents);
public void SetLinear (int frameIndex) {
curves[frameIndex * 6] = LINEAR;
@@ -202,7 +222,7 @@ namespace Spine {
Frames[frameIndex + 1] = angle;
}
- override public void Apply (Skeleton skeleton, float time, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
@@ -262,7 +282,7 @@ namespace Spine {
Frames[frameIndex + 2] = y;
}
- override public void Apply (Skeleton skeleton, float time, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
@@ -292,7 +312,7 @@ namespace Spine {
: base(frameCount) {
}
- override public void Apply (Skeleton skeleton, float time, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
@@ -341,7 +361,7 @@ namespace Spine {
Frames[frameIndex + 4] = a;
}
- override public void Apply (Skeleton skeleton, float time, float alpha) {
+ override public void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
@@ -405,7 +425,7 @@ namespace Spine {
AttachmentNames[frameIndex] = attachmentName;
}
- public void Apply (Skeleton skeleton, float time, float alpha) {
+ public void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
@@ -421,6 +441,57 @@ namespace Spine {
}
}
+ public class EventTimeline : Timeline {
+ public float[] Frames { get; private set; } // time, ...
+ public Event[] Events { get; private set; }
+ public int FrameCount {
+ get {
+ return Frames.Length;
+ }
+ }
+
+ public EventTimeline (int frameCount) {
+ Frames = new float[frameCount];
+ Events = new Event[frameCount];
+ }
+
+ /** Sets the time and value of the specified keyframe. */
+ public void setFrame (int frameIndex, float time, Event e) {
+ Frames[frameIndex] = time;
+ Events[frameIndex] = e;
+ }
+
+ public void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents) {
+ float[] frames = Frames;
+ if (time < frames[0]) return; // Time is before first frame.
+
+ int frameCount = frames.Length;
+ if (lastTime >= frames[frameCount - 1]) return; // Last time is after last frame.
+
+ if (lastTime > time) {
+ // Fire events after last time for looped animations.
+ Apply(skeleton, lastTime, int.MaxValue, alpha, firedEvents);
+ lastTime = 0;
+ }
+
+ int frameIndex;
+ if (frameCount == 1)
+ frameIndex = 0;
+ else {
+ frameIndex = Animation.binarySearch(frames, lastTime, 1);
+ float frame = frames[frameIndex];
+ while (frameIndex > 0) {
+ float lastFrame = frames[frameIndex - 1];
+ // Fire multiple events with the same frame and events that occurred at lastTime.
+ if (lastFrame != frame && lastFrame != lastTime) break;
+ frameIndex--;
+ }
+ }
+ for (; frameIndex < frameCount && time > frames[frameIndex]; frameIndex++)
+ firedEvents.Add(Events[frameIndex]);
+ }
+ }
+
public class DrawOrderTimeline : Timeline {
public float[] Frames { get; private set; } // time, ...
public int[][] DrawOrders { get; private set; }
@@ -441,7 +512,7 @@ namespace Spine {
DrawOrders[frameIndex] = drawOrder;
}
- public void Apply (Skeleton skeleton, float time, float alpha) {
+ public void Apply (Skeleton skeleton, float lastTime, float time, float alpha, List firedEvents) {
float[] frames = Frames;
if (time < frames[0]) return; // Time is before first frame.
diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs
index 1e52a77ac..b931b639c 100644
--- a/spine-csharp/src/AnimationState.cs
+++ b/spine-csharp/src/AnimationState.cs
@@ -38,28 +38,55 @@ namespace Spine {
public class AnimationState {
public AnimationStateData Data { get; private set; }
public Animation Animation { get; private set; }
- public float Time { get; set; }
+
+ private float time;
+ public float Time {
+ get { return time; }
+ set {
+ time = value;
+ currentLastTime = value - 0.00001f;
+ }
+ }
+
+ private float currentLastTime;
public bool Loop { get; set; }
private Animation previous;
private float previousTime;
private bool previousLoop;
+ private QueueEntry currentQueueEntry;
private float mixTime, mixDuration;
+ private List events = new List();
private List queue = new List();
+ public event EventHandler Start;
+ public event EventHandler End;
+ public event EventHandler Event;
+ public event EventHandler Complete;
+
public AnimationState (AnimationStateData data) {
if (data == null) throw new ArgumentNullException("data cannot be null.");
Data = data;
}
public void Update (float delta) {
- Time += delta;
+ time += delta;
previousTime += delta;
mixTime += delta;
+ if (Animation != null) {
+ float duration = Animation.Duration;
+ if (Loop ? (currentLastTime % duration > time % duration)
+ : (currentLastTime < duration && time >= duration)) {
+ int count = (int)(time / duration);
+ if (currentQueueEntry != null) currentQueueEntry.OnComplete(this, count);
+ if (Complete != null) Complete(this, new CompleteArgs(count));
+ }
+ }
+
if (queue.Count > 0) {
QueueEntry entry = queue[0];
- if (Time >= entry.delay) {
- SetAnimationInternal(entry.animation, entry.loop);
+ if (time >= entry.delay) {
+ SetAnimationInternal(entry.animation, entry.loop, entry);
queue.RemoveAt(0);
}
}
@@ -67,35 +94,94 @@ namespace Spine {
public void Apply (Skeleton skeleton) {
if (Animation == null) return;
+
+ List events = this.events;
+ events.Clear();
+
if (previous != null) {
- previous.Apply(skeleton, previousTime, previousLoop);
+ previous.Apply(skeleton, int.MaxValue, previousTime, previousLoop, null);
float alpha = mixTime / mixDuration;
if (alpha >= 1) {
alpha = 1;
previous = null;
}
- Animation.Mix(skeleton, Time, Loop, alpha);
+ Animation.Mix(skeleton, currentLastTime, time, Loop, events, alpha);
} else
- Animation.Apply(skeleton, Time, Loop);
+ Animation.Apply(skeleton, currentLastTime, time, Loop, events);
+
+ if (Event != null || currentQueueEntry != null) {
+ foreach (Event e in events) {
+ if (currentQueueEntry != null) currentQueueEntry.OnEvent(this, e);
+ if (Event != null) Event(this, new EventTriggeredArgs(e));
+ }
+ }
+
+ currentLastTime = time;
}
- public void AddAnimation (String animationName, bool loop) {
- AddAnimation(animationName, loop, 0);
+ public void ClearAnimation () {
+ previous = null;
+ Animation = null;
+ queue.Clear();
}
- public void AddAnimation (String animationName, bool loop, float delay) {
+ private void SetAnimationInternal (Animation animation, bool loop, QueueEntry entry) {
+ previous = null;
+ if (Animation != null) {
+ if (currentQueueEntry != null) currentQueueEntry.OnEnd(this);
+ if (End != null) End(this, EventArgs.Empty);
+
+ if (animation != null) {
+ mixDuration = Data.GetMix(Animation, animation);
+ if (mixDuration > 0) {
+ mixTime = 0;
+ previous = Animation;
+ previousTime = time;
+ previousLoop = Loop;
+ }
+ }
+ }
+ Animation = animation;
+ Loop = loop;
+ time = 0;
+ currentLastTime = 0;
+ currentQueueEntry = entry;
+
+ if (currentQueueEntry != null) currentQueueEntry.OnStart(this);
+ if (Start != null) Start(this, EventArgs.Empty);
+ }
+
+ public void SetAnimation (String animationName, bool loop) {
Animation animation = Data.SkeletonData.FindAnimation(animationName);
if (animation == null) throw new ArgumentException("Animation not found: " + animationName);
- AddAnimation(animation, loop, delay);
+ SetAnimation(animation, loop);
}
- public void AddAnimation (Animation animation, bool loop) {
- AddAnimation(animation, loop, 0);
+ /** Set the current animation. Any queued animations are cleared and the current animation time is set to 0.
+ * @param animation May be null.
+ * @param listener May be null. */
+ public void SetAnimation (Animation animation, bool loop) {
+ queue.Clear();
+ SetAnimationInternal(animation, loop, null);
+ }
+
+ public QueueEntry AddAnimation (String animationName, bool loop) {
+ return AddAnimation(animationName, loop, 0);
+ }
+
+ public QueueEntry AddAnimation (String animationName, bool loop, float delay) {
+ Animation animation = Data.SkeletonData.FindAnimation(animationName);
+ if (animation == null) throw new ArgumentException("Animation not found: " + animationName);
+ return AddAnimation(animation, loop, delay);
+ }
+
+ public QueueEntry AddAnimation (Animation animation, bool loop) {
+ return AddAnimation(animation, loop, 0);
}
/** Adds an animation to be played delay seconds after the current or last queued animation.
* @param delay May be <= 0 to use duration of previous animation minus any mix duration plus the negative delay. */
- public void AddAnimation (Animation animation, bool loop, float delay) {
+ public QueueEntry AddAnimation (Animation animation, bool loop, float delay) {
QueueEntry entry = new QueueEntry();
entry.animation = animation;
entry.loop = loop;
@@ -110,54 +196,59 @@ namespace Spine {
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);
- if (mixDuration > 0) {
- mixTime = 0;
- previous = Animation;
- previousTime = Time;
- previousLoop = Loop;
- }
- }
- Animation = animation;
- Loop = loop;
- 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();
+ return entry;
}
/** Returns true if no animation is set or if the current time is greater than the animation duration, regardless of looping. */
public bool IsComplete () {
- return Animation == null || Time >= Animation.Duration;
+ return Animation == null || time >= Animation.Duration;
}
override public String ToString () {
return (Animation != null && Animation.Name != null) ? Animation.Name : base.ToString();
}
}
-}
-class QueueEntry {
- public Spine.Animation animation;
- public bool loop;
- public float delay;
+ public class EventTriggeredArgs : EventArgs {
+ public Event Event { get; private set; }
+
+ public EventTriggeredArgs (Event e) {
+ Event = e;
+ }
+ }
+
+ public class CompleteArgs : EventArgs {
+ public int LoopCount { get; private set; }
+
+ public CompleteArgs (int loopCount) {
+ LoopCount = loopCount;
+ }
+ }
+
+ public class QueueEntry {
+ internal Spine.Animation animation;
+ internal bool loop;
+ internal float delay;
+
+ public event EventHandler Start;
+ public event EventHandler End;
+ public event EventHandler Event;
+ public event EventHandler Complete;
+
+ internal void OnStart (AnimationState state) {
+ if (Start != null) Start(state, EventArgs.Empty);
+ }
+
+ internal void OnEnd (AnimationState state) {
+ if (End != null) End(state, EventArgs.Empty);
+ }
+
+ internal void OnEvent (AnimationState state, Event e) {
+ if (Event != null) Event(state, new EventTriggeredArgs(e));
+ }
+
+ internal void OnComplete (AnimationState state, int loopCount) {
+ if (Complete != null) Complete(state, new CompleteArgs(loopCount));
+ }
+ }
}
diff --git a/spine-csharp/src/Event.cs b/spine-csharp/src/Event.cs
new file mode 100644
index 000000000..c789c3b9c
--- /dev/null
+++ b/spine-csharp/src/Event.cs
@@ -0,0 +1,51 @@
+/******************************************************************************
+ * Spine Runtime Software License - Version 1.0
+ *
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms in whole or in part, with
+ * or without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. A Spine Single User License or Spine Professional License must be
+ * purchased from Esoteric Software and the license must remain valid:
+ * http://esotericsoftware.com/
+ * 2. Redistributions of source code must retain this license, which is the
+ * above copyright notice, this declaration of conditions and the following
+ * disclaimer.
+ * 3. Redistributions in binary form must reproduce this license, which is the
+ * above copyright notice, this declaration of conditions and the following
+ * disclaimer, in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 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.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+ public class Event {
+ public EventData Data { get; private set; }
+ public int Int { get; set; }
+ public float Float { get; set; }
+ public String String { get; set; }
+
+ public Event (EventData data) {
+ Data = data;
+ }
+
+ override public String ToString () {
+ return Data.Name;
+ }
+ }
+}
diff --git a/spine-csharp/src/EventData.cs b/spine-csharp/src/EventData.cs
new file mode 100644
index 000000000..b835f47a7
--- /dev/null
+++ b/spine-csharp/src/EventData.cs
@@ -0,0 +1,52 @@
+/******************************************************************************
+ * Spine Runtime Software License - Version 1.0
+ *
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms in whole or in part, with
+ * or without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. A Spine Single User License or Spine Professional License must be
+ * purchased from Esoteric Software and the license must remain valid:
+ * http://esotericsoftware.com/
+ * 2. Redistributions of source code must retain this license, which is the
+ * above copyright notice, this declaration of conditions and the following
+ * disclaimer.
+ * 3. Redistributions in binary form must reproduce this license, which is the
+ * above copyright notice, this declaration of conditions and the following
+ * disclaimer, in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 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.
+ *****************************************************************************/
+
+using System;
+
+namespace Spine {
+ public class EventData {
+ public String Name { get; private set; }
+ public int Int { get; set; }
+ public float Float { get; set; }
+ public String String { get; set; }
+
+ public EventData (String name) {
+ if (name == null) throw new ArgumentNullException("name cannot be null.");
+ Name = name;
+ }
+
+ override public String ToString () {
+ return Name;
+ }
+ }
+}
diff --git a/spine-csharp/src/SkeletonBounds.cs b/spine-csharp/src/SkeletonBounds.cs
index 468a4b085..fbbf68e95 100644
--- a/spine-csharp/src/SkeletonBounds.cs
+++ b/spine-csharp/src/SkeletonBounds.cs
@@ -133,8 +133,10 @@ namespace Spine {
} else
polygon = new Polygon();
polygons.Add(polygon);
- polygon.Count = boundingBox.Vertices.Length;
- if (polygon.Vertices.Length < polygon.Count) polygon.Vertices = new float[polygon.Count];
+
+ int count = boundingBox.Vertices.Length;
+ polygon.Count = count;
+ if (polygon.Vertices.Length < count) polygon.Vertices = new float[count];
boundingBox.ComputeWorldVertices(x, y, slot.Bone, polygon.Vertices);
}
}
diff --git a/spine-csharp/src/SkeletonData.cs b/spine-csharp/src/SkeletonData.cs
index 6d2f308b1..4aa079e03 100644
--- a/spine-csharp/src/SkeletonData.cs
+++ b/spine-csharp/src/SkeletonData.cs
@@ -42,12 +42,14 @@ namespace Spine {
public List Skins { get; private set; }
/** May be null. */
public Skin DefaultSkin;
+ public List Events { get; private set; }
public List Animations { get; private set; }
public SkeletonData () {
Bones = new List();
Slots = new List();
Skins = new List();
+ Events = new List();
Animations = new List();
}
@@ -117,6 +119,21 @@ namespace Spine {
return null;
}
+ // --- Events.
+
+ public void AddEvent (EventData eventData) {
+ if (eventData == null) throw new ArgumentNullException("eventData cannot be null.");
+ Events.Add(eventData);
+ }
+
+ /** @return May be null. */
+ public EventData findEvent (String eventDataName) {
+ if (eventDataName == null) throw new ArgumentNullException("eventDataName cannot be null.");
+ foreach (EventData eventData in Events)
+ if (eventData.Name == eventDataName) return eventData;
+ return null;
+ }
+
// --- Animations.
public void AddAnimation (Animation animation) {
diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs
index a6524a427..a51da200a 100644
--- a/spine-csharp/src/SkeletonJson.cs
+++ b/spine-csharp/src/SkeletonJson.cs
@@ -118,8 +118,7 @@ namespace Spine {
// Slots.
if (root.ContainsKey("slots")) {
- var slots = (List