diff --git a/spine-as3/spine-as3/lib/spine-as3.swc b/spine-as3/spine-as3/lib/spine-as3.swc
index 0b35f87ee..dc431ba12 100644
Binary files a/spine-as3/spine-as3/lib/spine-as3.swc and b/spine-as3/spine-as3/lib/spine-as3.swc differ
diff --git a/spine-as3/spine-as3/src/spine/SkeletonJson.as b/spine-as3/spine-as3/src/spine/SkeletonJson.as
index 3c0aad8be..384524e17 100644
--- a/spine-as3/spine-as3/src/spine/SkeletonJson.as
+++ b/spine-as3/spine-as3/src/spine/SkeletonJson.as
@@ -621,7 +621,7 @@ package spine {
}
// Path constraint timelines.
- var paths : Object = map["paths"];
+ var paths : Object = map["path"];
for (var pathName : String in paths) {
var index : int = skeletonData.findPathConstraintIndex(pathName);
if (index == -1) throw new Error("Path constraint not found: " + pathName);
diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs
index a7efa5904..e6d172aad 100644
--- a/spine-csharp/src/Animation.cs
+++ b/spine-csharp/src/Animation.cs
@@ -143,7 +143,7 @@ namespace Spine {
/// apply animations on top of each other (layered).
/// Controls how mixing is applied when alpha < 1.
/// Indicates whether the timeline is mixing in or out. Used by timelines which perform instant transitions,
- /// such as or .
+ /// such as or , and other such as {@link ScaleTimeline}.
void Apply (Skeleton skeleton, float lastTime, float time, ExposedList events, float alpha, MixBlend blend, MixDirection direction);
/// Uniquely encodes both the type of this timeline and the skeleton property that it affects.
int PropertyId { get; }
@@ -973,18 +973,14 @@ namespace Spine {
string attachmentName;
Slot slot = skeleton.slots.Items[slotIndex];
if (!slot.bone.active) return;
- if (direction == MixDirection.Out && blend == MixBlend.Setup) {
- attachmentName = slot.data.attachmentName;
- slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
+ if (direction == MixDirection.Out) {
+ if (blend == MixBlend.Setup) SetAttachment(skeleton, slot, slot.data.attachmentName);
return;
}
float[] frames = this.frames;
if (time < frames[0]) { // Time is before first frame.
- if (blend == MixBlend.Setup || blend == MixBlend.First) {
- attachmentName = slot.data.attachmentName;
- slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
- }
+ if (blend == MixBlend.Setup || blend == MixBlend.First) SetAttachment(skeleton, slot, slot.data.attachmentName);
return;
}
@@ -994,7 +990,10 @@ namespace Spine {
else
frameIndex = Animation.BinarySearch(frames, time) - 1;
- attachmentName = attachmentNames[frameIndex];
+ SetAttachment(skeleton, slot, attachmentNames[frameIndex]);
+ }
+
+ private void SetAttachment (Skeleton skeleton, Slot slot, string attachmentName) {
slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slotIndex, attachmentName);
}
}
@@ -1332,8 +1331,8 @@ namespace Spine {
MixDirection direction) {
ExposedList drawOrder = skeleton.drawOrder;
ExposedList slots = skeleton.slots;
- if (direction == MixDirection.Out && blend == MixBlend.Setup) {
- Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
+ if (direction == MixDirection.Out) {
+ if (blend == MixBlend.Setup) Array.Copy(slots.Items, 0, drawOrder.Items, 0, slots.Count);
return;
}
diff --git a/spine-csharp/src/AnimationState.cs b/spine-csharp/src/AnimationState.cs
index 0ddfecbb8..7c7149a32 100644
--- a/spine-csharp/src/AnimationState.cs
+++ b/spine-csharp/src/AnimationState.cs
@@ -66,12 +66,12 @@ namespace Spine {
/// (which affects B and C). Without using D to mix out, A would be applied fully until mixing completes, then snap into
/// place.
internal const int HoldMix = 3;
- /// 1) An attachment timeline in a subsequent track entry sets the attachment for the same slot as this attachment
- /// timeline.
- /// Result: This attachment timeline will not use MixDirection.out, which would otherwise show the setup mode attachment (or
- /// none if not visible in setup mode). This allows deform timelines to be applied for the subsequent entry to mix from, rather
- /// than mixing from the setup pose.
- internal const int NotLast = 4;
+ /// 1) This is the last attachment timeline to set the attachment for a slot.
+ /// Result: Don't apply this timeline when mixing out. Attachment timelines that are not last are applied when mixing out so
+ /// any deform timelines are applied and subsequent entries can mix from that deform.
+ internal const int Last = 4;
+
+ internal const int Setup = 1, Current = 2;
protected AnimationStateData data;
private readonly ExposedList tracks = new ExposedList();
@@ -94,6 +94,7 @@ namespace Spine {
private readonly HashSet propertyIDs = new HashSet();
private bool animationsChanged;
private float timeScale = 1;
+ private int unkeyedState;
private readonly Pool trackEntryPool = new Pool();
@@ -196,8 +197,8 @@ namespace Spine {
}
///
- /// Poses the skeleton using the track entry animations. There are no side effects other than invoking listeners, so the
- /// animation state can be applied to multiple skeletons to pose them identically.
+ /// Poses the skeleton using the track entry animations. The animation state is not changed, so can be applied to multiple
+ /// skeletons to pose them identically.
/// True if any animations were applied.
public bool Apply (Skeleton skeleton) {
if (skeleton == null) throw new ArgumentNullException("skeleton", "skeleton cannot be null.");
@@ -227,8 +228,13 @@ namespace Spine {
var timelines = current.animation.timelines;
var timelinesItems = timelines.Items;
if ((i == 0 && mix == 1) || blend == MixBlend.Add) {
- for (int ii = 0; ii < timelineCount; ii++)
- timelinesItems[ii].Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
+ for (int ii = 0; ii < timelineCount; ii++) {
+ var timeline = timelinesItems[ii];
+ if (timeline is AttachmentTimeline)
+ ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true);
+ else
+ timeline.Apply(skeleton, animationLast, animationTime, events, mix, blend, MixDirection.In);
+ }
} else {
var timelineMode = current.timelineMode.Items;
@@ -238,11 +244,13 @@ namespace Spine {
for (int ii = 0; ii < timelineCount; ii++) {
Timeline timeline = timelinesItems[ii];
- MixBlend timelineBlend = (timelineMode[ii] & AnimationState.NotLast - 1) == AnimationState.Subsequent ? blend : MixBlend.Setup;
+ MixBlend timelineBlend = (timelineMode[ii] & AnimationState.Last - 1) == AnimationState.Subsequent ? blend : MixBlend.Setup;
var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null)
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, mix, timelineBlend, timelinesRotation,
ii << 1, firstFrame);
+ else if (timeline is AttachmentTimeline)
+ ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, blend, true);
else
timeline.Apply(skeleton, animationLast, animationTime, events, mix, timelineBlend, MixDirection.In);
}
@@ -253,6 +261,20 @@ namespace Spine {
current.nextTrackLast = current.trackTime;
}
+ // Set slots attachments to the setup pose, if needed. This occurs if an animation that is mixing out sets attachments so
+ // subsequent timelines see any deform, but the subsequent timelines don't set an attachment (eg they are also mixing out or
+ // the time is before the first key).
+ int setupState = unkeyedState + Setup;
+ var slots = skeleton.slots.Items;
+ for (int i = 0, n = skeleton.slots.Count; i < n; i++) {
+ Slot slot = (Slot)slots[i];
+ if (slot.attachmentState == setupState) {
+ string attachmentName = slot.data.attachmentName;
+ slot.Attachment = (attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName));
+ }
+ }
+ unkeyedState += 2; // Increasing after each use avoids the need to reset attachmentState for every slot.
+
queue.Drain();
return applied;
}
@@ -296,14 +318,10 @@ namespace Spine {
MixDirection direction = MixDirection.Out;
MixBlend timelineBlend;
float alpha;
- switch (timelineMode[i] & AnimationState.NotLast - 1) {
+ switch (timelineMode[i] & AnimationState.Last - 1) {
case AnimationState.Subsequent:
- timelineBlend = blend;
- if (!attachments && timeline is AttachmentTimeline) {
- if ((timelineMode[i] & AnimationState.NotLast) == AnimationState.NotLast) continue;
- timelineBlend = MixBlend.Setup;
- }
if (!drawOrder && timeline is DrawOrderTimeline) continue;
+ timelineBlend = blend;
alpha = alphaMix;
break;
case AnimationState.First:
@@ -321,22 +339,18 @@ namespace Spine {
break;
}
from.totalAlpha += alpha;
-
var rotateTimeline = timeline as RotateTimeline;
if (rotateTimeline != null) {
ApplyRotateTimeline(rotateTimeline, skeleton, animationTime, alpha, timelineBlend, timelinesRotation,
i << 1, firstFrame);
+ } else if (timeline is AttachmentTimeline) {
+ // If not showing attachments: do nothing if this is the last timeline, else apply the timeline so
+ // subsequent timelines see any deform, but don't set attachmentState to Current.
+ if (!attachments && (timelineMode[i] & Last) != 0) continue;
+ ApplyAttachmentTimeline((AttachmentTimeline)timeline, skeleton, animationTime, timelineBlend, attachments);
} else {
- if (timelineBlend == MixBlend.Setup) {
- if (timeline is AttachmentTimeline) {
- if (attachments || (timelineMode[i] & AnimationState.NotLast) == AnimationState.NotLast) direction = MixDirection.In;
- } else if (timeline is DrawOrderTimeline) {
- if (drawOrder) {
- direction = MixDirection.In;
- }
- }
- }
-
+ if (drawOrder && timeline is DrawOrderTimeline && timelineBlend == MixBlend.Setup)
+ direction = MixDirection.In;
timeline.Apply(skeleton, animationLast, animationTime, eventBuffer, alpha, timelineBlend, direction);
}
}
@@ -350,6 +364,42 @@ namespace Spine {
return mix;
}
+ /// Applies the attachment timeline and sets .
+ /// False when: 1) the attachment timeline is mixing out, 2) mix < attachmentThreshold, and 3) the timeline
+ /// is not the last timeline to set the slot's attachment. In that case the timeline is applied only so subsequent
+ /// timelines see any deform.
+ private void ApplyAttachmentTimeline (AttachmentTimeline timeline, Skeleton skeleton, float time, MixBlend blend,
+ bool attachments) {
+
+ Slot slot = skeleton.slots.Items[timeline.slotIndex];
+ if (!slot.bone.active) return;
+
+ float[] frames = timeline.frames;
+ if (time < frames[0]) { // Time is before first frame.
+ if (blend == MixBlend.Setup || blend == MixBlend.First)
+ SetAttachment(skeleton, slot, slot.data.attachmentName, attachments);
+ }
+ else {
+ int frameIndex;
+ if (time >= frames[frames.Length - 1]) // Time is after last frame.
+ frameIndex = frames.Length - 1;
+ else
+ frameIndex = Animation.BinarySearch(frames, time) - 1;
+ SetAttachment(skeleton, slot, timeline.attachmentNames[frameIndex], attachments);
+ }
+
+ // If an attachment wasn't set (ie before the first frame or attachments is false), set the setup attachment later.
+ if (slot.attachmentState <= unkeyedState) slot.attachmentState = unkeyedState + Setup;
+ }
+
+ private void SetAttachment (Skeleton skeleton, Slot slot, String attachmentName, bool attachments) {
+ slot.Attachment = attachmentName == null ? null : skeleton.GetAttachment(slot.data.index, attachmentName);
+ if (attachments) slot.attachmentState = unkeyedState + Current;
+ }
+
+ ///
+ /// Applies the rotate timeline, mixing with the current pose while keeping the same rotation direction chosen as the shortest
+ /// the first time the mixing was applied.
static private void ApplyRotateTimeline (RotateTimeline timeline, Skeleton skeleton, float time, float alpha, MixBlend blend,
float[] timelinesRotation, int i, bool firstFrame) {
@@ -803,7 +853,7 @@ namespace Spine {
for (int i = 0; i < timelinesCount; i++) {
if (timelines[i] is AttachmentTimeline) {
AttachmentTimeline timeline = (AttachmentTimeline)timelines[i];
- if (!propertyIDs.Add(timeline.slotIndex)) timelineMode[i] |= AnimationState.NotLast;
+ if (propertyIDs.Add(timeline.slotIndex)) timelineMode[i] |= AnimationState.Last;
}
}
}
diff --git a/spine-csharp/src/Slot.cs b/spine-csharp/src/Slot.cs
index 309f1e08d..1c1c1fb34 100644
--- a/spine-csharp/src/Slot.cs
+++ b/spine-csharp/src/Slot.cs
@@ -45,6 +45,7 @@ namespace Spine {
internal Attachment attachment;
internal float attachmentTime;
internal ExposedList deform = new ExposedList();
+ internal int attachmentState;
public Slot (SlotData data, Bone bone) {
if (data == null) throw new ArgumentNullException("data", "data cannot be null.");
diff --git a/spine-starling/spine-starling/lib/spine-starling.swc b/spine-starling/spine-starling/lib/spine-starling.swc
index 8f3701a6b..edaa380d6 100644
Binary files a/spine-starling/spine-starling/lib/spine-starling.swc and b/spine-starling/spine-starling/lib/spine-starling.swc differ
diff --git a/spine-ts/webgl/tests/test-simple.html b/spine-ts/webgl/tests/test-simple.html
new file mode 100644
index 000000000..5b7457f74
--- /dev/null
+++ b/spine-ts/webgl/tests/test-simple.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs
index f63010b09..260a90150 100644
--- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs
+++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Components/BoundingBoxFollowerInspector.cs
@@ -87,7 +87,7 @@ namespace Spine.Unity.Editor {
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
EditorGUILayout.HelpBox("It's ideal to add BoundingBoxFollower to a separate child GameObject of the Spine GameObject.", MessageType.Warning);
- if (GUILayout.Button(new GUIContent("Move BoundingBoxFollower to new GameObject", Icons.boundingBox), GUILayout.Height(50f))) {
+ if (GUILayout.Button(new GUIContent("Move BoundingBoxFollower to new GameObject", Icons.boundingBox), GUILayout.Height(30f))) {
AddBoundingBoxFollowerChild(skeletonRendererValue, follower);
DestroyImmediate(follower);
return;
diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineInspectorUtility.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineInspectorUtility.cs
index 24b6af729..7842686c1 100644
--- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineInspectorUtility.cs
+++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineInspectorUtility.cs
@@ -220,13 +220,12 @@ namespace Spine.Unity.Editor {
#region Button
const float CenterButtonMaxWidth = 270f;
- const float CenterButtonHeight = 35f;
+ const float CenterButtonHeight = 30f;
static GUIStyle spineButtonStyle;
static GUIStyle SpineButtonStyle {
get {
if (spineButtonStyle == null) {
spineButtonStyle = new GUIStyle(GUI.skin.button);
- spineButtonStyle.name = "Spine Button";
spineButtonStyle.padding = new RectOffset(10, 10, 10, 10);
}
return spineButtonStyle;
diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs
index 1662b7ae0..9c5fd1bf0 100644
--- a/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs
+++ b/spine-unity/Assets/Spine/Editor/spine-unity/Modules/SkeletonRenderSeparator/Editor/SkeletonRenderSeparatorInspector.cs
@@ -182,7 +182,7 @@ namespace Spine.Unity.Examples {
if (component.enabled && component.SkeletonRenderer != null && extraRenderersNeeded > 0) {
EditorGUILayout.HelpBox(string.Format("Insufficient parts renderers. Some parts will not be rendered."), MessageType.Warning);
string addMissingLabel = string.Format("Add the missing renderer{1} ({0}) ", extraRenderersNeeded, SpineInspectorUtility.PluralThenS(extraRenderersNeeded));
- if (GUILayout.Button(addMissingLabel, GUILayout.Height(40f))) {
+ if (GUILayout.Button(addMissingLabel, GUILayout.Height(30f))) {
AddPartsRenderer(extraRenderersNeeded);
DetectOrphanedPartsRenderers(component);
partsRendererInitRequired = true;