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;