فهرست منبع

Merge branch 'skin-bones' of https://github.com/EsotericSoftware/spine-runtimes into skin-bones

badlogic 6 سال پیش
والد
کامیت
a0c9c9bb0e
43فایلهای تغییر یافته به همراه626 افزوده شده و 364 حذف شده
  1. 1 1
      spine-csharp/src/Animation.cs
  2. 3 0
      spine-csharp/src/Attachments/Attachment.cs
  3. 6 0
      spine-csharp/src/Attachments/BoundingBoxAttachment.cs
  4. 8 1
      spine-csharp/src/Attachments/ClippingAttachment.cs
  5. 37 0
      spine-csharp/src/Attachments/MeshAttachment.cs
  6. 11 1
      spine-csharp/src/Attachments/PathAttachment.cs
  7. 8 0
      spine-csharp/src/Attachments/PointAttachment.cs
  8. 21 0
      spine-csharp/src/Attachments/RegionAttachment.cs
  9. 21 2
      spine-csharp/src/Attachments/VertexAttachment.cs
  10. 1 1
      spine-csharp/src/Bone.cs
  11. 7 1
      spine-csharp/src/BoneData.cs
  12. 30 7
      spine-csharp/src/ConstraintData.cs
  13. 1 1
      spine-csharp/src/EventData.cs
  14. 1 6
      spine-csharp/src/IkConstraint.cs
  15. 2 20
      spine-csharp/src/IkConstraintData.cs
  16. 1 6
      spine-csharp/src/PathConstraint.cs
  17. 4 14
      spine-csharp/src/PathConstraintData.cs
  18. 36 19
      spine-csharp/src/Skeleton.cs
  19. 20 8
      spine-csharp/src/SkeletonBinary.cs
  20. 111 67
      spine-csharp/src/SkeletonJson.cs
  21. 109 42
      spine-csharp/src/Skin.cs
  22. 5 1
      spine-csharp/src/SlotData.cs
  23. 1 2
      spine-csharp/src/TransformConstraint.cs
  24. 2 12
      spine-csharp/src/TransformConstraintData.cs
  25. 1 1
      spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs
  26. 1 1
      spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs
  27. 4 3
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/PointFollowerEditor.cs
  28. 21 13
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonBaker.cs
  29. 19 6
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDataAssetInspector.cs
  30. 13 2
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDebugWindow.cs
  31. 27 18
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs
  32. 22 11
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs
  33. 4 3
      spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs
  34. 7 1
      spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs
  35. 7 7
      spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs
  36. 18 16
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs
  37. 6 6
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/BoundingBoxFollower/BoundingBoxFollower.cs
  38. 6 5
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs
  39. 5 5
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs
  40. 18 39
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/README.md
  41. BIN
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/add-menu.png
  42. BIN
      spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png
  43. 0 15
      spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs

+ 1 - 1
spine-csharp/src/Animation.cs

@@ -52,7 +52,7 @@ namespace Spine {
 		/// <summary>The duration of the animation in seconds, which is the highest time of all keys in the timeline.</summary>
 		public float Duration { get { return duration; } set { duration = value; } }
 
-		/// <summary>The animation's name, which is unique within the skeleton.</summary>
+		/// <summary>The animation's name, which is unique across all animations in the skeleton.</summary>
 		public string Name { get { return name; } }
 
 		/// <summary>Applies all the animation's timelines to the specified skeleton.</summary>

+ 3 - 0
spine-csharp/src/Attachments/Attachment.cs

@@ -41,6 +41,9 @@ namespace Spine {
 		override public string ToString () {
 			return Name;
 		}
+
+		///<summary>Returns a copy of the attachment.</summary>
+		public abstract Attachment Copy ();
 	}
 
 	public interface IHasRendererObject {

+ 6 - 0
spine-csharp/src/Attachments/BoundingBoxAttachment.cs

@@ -35,5 +35,11 @@ namespace Spine {
 		public BoundingBoxAttachment (string name)
 			: base(name) {
 		}
+
+		public override Attachment Copy () {
+			BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name);
+			CopyTo(copy);
+			return copy;
+		}
 	}
 }

+ 8 - 1
spine-csharp/src/Attachments/ClippingAttachment.cs

@@ -37,5 +37,12 @@ namespace Spine {
 
         public ClippingAttachment(string name) : base(name) {
         }
-    }
+
+		public override Attachment Copy () {
+			ClippingAttachment copy = new ClippingAttachment(this.Name);
+			CopyTo(copy);
+			copy.endSlot = endSlot;
+			return copy;
+		}
+	}
 }

+ 37 - 0
spine-csharp/src/Attachments/MeshAttachment.cs

@@ -155,5 +155,42 @@ namespace Spine {
 		override public bool ApplyDeform (VertexAttachment sourceAttachment) {
 			return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment);
 		}
+
+		public override Attachment Copy () {
+			MeshAttachment copy = new MeshAttachment(this.Name);
+			copy.regionOffsetX = regionOffsetX;
+			copy.regionOffsetY = regionOffsetY;
+			copy.regionWidth = regionWidth;
+			copy.regionHeight = regionHeight;
+			copy.regionOriginalWidth = regionOriginalWidth;
+			copy.regionOriginalHeight = regionOriginalHeight;
+
+			copy.Path = Path;
+			
+			if (parentMesh == null) {
+				CopyTo(copy);
+				copy.regionUVs = new float[regionUVs.Length];
+				Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length);
+				copy.uvs = new float[uvs.Length];
+				Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length);
+				copy.triangles = new int[triangles.Length];
+				Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length);
+				copy.HullLength = HullLength;
+				
+				copy.inheritDeform = inheritDeform;
+				
+				// Nonessential.
+				if (Edges != null) {
+					copy.Edges = new int[Edges.Length];
+					Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length);
+				}
+				copy.Width = Width;
+				copy.Height = Height;
+			}
+			else
+				copy.ParentMesh = parentMesh;
+			
+			return copy;
+		}
 	}
 }

+ 11 - 1
spine-csharp/src/Attachments/PathAttachment.cs

@@ -42,6 +42,16 @@ namespace Spine {
 
 		public PathAttachment (String name)
 			: base(name) {
-		}			
+		}
+
+		public override Attachment Copy () {
+			PathAttachment copy = new PathAttachment(this.Name);
+			CopyTo(copy);
+			copy.lengths = new float[lengths.Length];
+			Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length);
+			copy.closed = closed;
+			copy.constantSpeed = constantSpeed;
+			       return copy;
+		}
 	}
 }

+ 8 - 0
spine-csharp/src/Attachments/PointAttachment.cs

@@ -55,5 +55,13 @@ namespace Spine {
 			float iy = cos * bone.c + sin * bone.d;
 			return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg;
 		}
+
+		public override Attachment Copy () {
+			PointAttachment copy = new PointAttachment(this.Name);
+			copy.x = x;
+			copy.y = y;
+			copy.rotation = rotation;
+			return copy;
+		}
 	}
 }

+ 21 - 0
spine-csharp/src/Attachments/RegionAttachment.cs

@@ -178,5 +178,26 @@ namespace Spine {
 			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
 			//offset += stride;
 		}
+
+		public override Attachment Copy () {
+			RegionAttachment copy = new RegionAttachment(this.Name);
+			copy.regionOffsetX = regionOffsetX;
+			copy.regionOffsetY = regionOffsetY;
+			copy.regionWidth = regionWidth;
+			copy.regionHeight = regionHeight;
+			copy.regionOriginalWidth = regionOriginalWidth;
+			copy.regionOriginalHeight = regionOriginalHeight;
+			copy.Path = Path;
+			copy.x = x;
+			copy.y = y;
+			copy.scaleX = scaleX;
+			copy.scaleY = scaleY;
+			copy.rotation = rotation;
+			copy.width = width;
+			copy.height = height;
+			Array.Copy(uvs, 0, copy.uvs, 0, 8);
+			Array.Copy(offset, 0, copy.offset, 0, 8);
+			return copy;
+		}
 	}
 }

+ 21 - 2
spine-csharp/src/Attachments/VertexAttachment.cs

@@ -32,7 +32,7 @@ using System;
 namespace Spine {
 	/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's
 	/// <see cref="Slot.Deform"/>.</summary> 
-	public class VertexAttachment : Attachment {
+	public abstract class VertexAttachment : Attachment {
 		static int nextID = 0;
 		static readonly Object nextIdLock = new Object();
 
@@ -131,6 +131,25 @@ namespace Spine {
 		/// <summary>Returns true if a deform originally applied to the specified attachment should be applied to this attachment.</summary>
 		virtual public bool ApplyDeform (VertexAttachment sourceAttachment) {
 			return this == sourceAttachment;
-		}			
+		}
+
+		///<summary>Does not copy id (generated) or name (set on construction).</summary>
+		internal void CopyTo (VertexAttachment attachment) {
+			if (bones != null) {
+				attachment.bones = new int[bones.Length];
+				Array.Copy(bones, 0, attachment.bones, 0, bones.Length);
+			}
+			else
+				attachment.bones = null;
+			
+			if (vertices != null) {
+				attachment.vertices = new float[vertices.Length];
+				Array.Copy(vertices, 0, attachment.vertices, 0, vertices.Length);
+			}
+			else
+				attachment.vertices = null;
+			
+			attachment.worldVerticesLength = worldVerticesLength;
+		}
 	}
 }

+ 1 - 1
spine-csharp/src/Bone.cs

@@ -52,7 +52,7 @@ namespace Spine {
 		internal float a, b, worldX;
 		internal float c, d, worldY;
 
-		internal bool sorted;
+		internal bool sorted, update;
 
 		public BoneData Data { get { return data; } }
 		public Skeleton Skeleton { get { return skeleton; } }

+ 7 - 1
spine-csharp/src/BoneData.cs

@@ -37,11 +37,12 @@ namespace Spine {
 		internal float length;
 		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
 		internal TransformMode transformMode = TransformMode.Normal;
+		internal bool skinRequired;
 
 		/// <summary>The index of the bone in Skeleton.Bones</summary>
 		public int Index { get { return index; } }
 
-		/// <summary>The name of the bone, which is unique within the skeleton.</summary>
+		/// <summary>The name of the bone, which is unique across all bones in the skeleton.</summary>
 		public string Name { get { return name; } }
 
 		/// <summary>May be null.</summary>
@@ -73,6 +74,11 @@ namespace Spine {
 		/// <summary>The transform mode for how parent world transforms affect this bone.</summary>
 		public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
 
+		///<summary>When true, <see cref="Skeleton.UpdateWorldTransform()"/> only updates this bone if the <see cref="Skeleton.Skin"/> contains this
+		/// bone.</summary>
+		/// <seealso cref="Skin.Bones"/>
+		public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
+
 		/// <param name="parent">May be null.</param>
 		public BoneData (int index, string name, BoneData parent) {
 			if (index < 0) throw new ArgumentException("index must be >= 0", "index");

+ 30 - 7
spine-csharp/src/IConstraint.cs → spine-csharp/src/ConstraintData.cs

@@ -27,13 +27,36 @@
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
-namespace Spine {
-	
-	/// <summary>The interface for all constraints.</summary>
-	public interface IConstraint : IUpdatable {
-		/// <summary>The ordinal for the order a skeleton's constraints will be applied.</summary>
-		int Order { get; }
+using System;
+using System.Collections.Generic;
 
-	}
+namespace Spine
+{
+	/// <summary>The base class for all constraint datas.</summary>
+	public abstract class ConstraintData {
+		internal readonly string name;
+		internal int order;
+		internal bool skinRequired;
+
+		public ConstraintData (string name) {
+			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
+			this.name = name;
+		}
+
+		/// <summary> The constraint's name, which is unique across all constraints in the skeleton of the same type.</summary>
+		public string Name { get { return name; } }
 
+		///<summary>The ordinal of this constraint for the order a skeleton's constraints will be applied by
+		/// <see cref="Skeleton.UpdateWorldTransform()"/>.</summary>
+		public int Order { get { return order; } set { order = value; } }
+
+		///<summary>When true, <see cref="Skeleton.UpdateWorldTransform()"/> only updates this constraint if the <see cref="Skeleton.Skin"/> contains
+		/// this constraint.</summary>
+		///<seealso cref="Skin.Constraints"/>
+		public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } }
+
+		override public string ToString () {
+			return name;
+		}
+	}
 }

+ 1 - 1
spine-csharp/src/EventData.cs

@@ -34,7 +34,7 @@ namespace Spine {
 	public class EventData {
 		internal string name;
 
-		/// <summary>The name of the event, which is unique within the skeleton.</summary>
+		/// <summary>The name of the event, which is unique across all events in the skeleton.</summary>
 		public string Name { get { return name; } }
 		public int Int { get; set; }
 		public float Float { get; set; }

+ 1 - 6
spine-csharp/src/IkConstraint.cs

@@ -37,7 +37,7 @@ namespace Spine {
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-ik-constraints">IK constraints</a> in the Spine User Guide.</para>
 	/// </summary>
-	public class IkConstraint : IConstraint {
+	public class IkConstraint : IUpdatable {
 		internal IkConstraintData data;
 		internal ExposedList<Bone> bones = new ExposedList<Bone>();
 		internal Bone target;
@@ -93,11 +93,6 @@ namespace Spine {
 			}
 		}
 
-
-		public int Order {
-			get { return data.order; }
-		}
-
 		/// <summary>The bones that will be modified by this IK constraint.</summary>
 		public ExposedList<Bone> Bones {
 			get { return bones; }

+ 2 - 20
spine-csharp/src/IkConstraintData.cs

@@ -32,23 +32,14 @@ using System.Collections.Generic;
 
 namespace Spine {
 	/// <summary>Stores the setup pose for an IkConstraint.</summary>
-	public class IkConstraintData {
-		internal string name;
-		internal int order;
+	public class IkConstraintData : ConstraintData {
 		internal List<BoneData> bones = new List<BoneData>();
 		internal BoneData target;
 		internal int bendDirection = 1;
 		internal bool compress, stretch, uniform;
 		internal float mix = 1;
 
-		/// <summary>The IK constraint's name, which is unique within the skeleton.</summary>
-		public string Name {
-			get { return name; }
-		}
-
-		public int Order {
-			get { return order; }
-			set { order = value; }
+		public IkConstraintData (string name) : base(name) {
 		}
 
 		/// <summary>The bones that are constrained by this IK Constraint.</summary>
@@ -98,14 +89,5 @@ namespace Spine {
 			get { return uniform; }
 			set { uniform = value; }
 		}
-
-		public IkConstraintData (string name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		override public string ToString () {
-			return name;
-		}
 	}
 }

+ 1 - 6
spine-csharp/src/PathConstraint.cs

@@ -38,7 +38,7 @@ namespace Spine {
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para>
 	/// </summary>
-	public class PathConstraint : IConstraint {
+	public class PathConstraint : IUpdatable {
 		const int NONE = -1, BEFORE = -2, AFTER = -3;
 		const float Epsilon = 0.00001f;
 
@@ -446,7 +446,6 @@ namespace Spine {
 			}
 		}
 		
-		public int Order { get { return data.order; } }
 		/// <summary>The position along the path.</summary>
 		public float Position { get { return position; } set { position = value; } }
 		/// <summary>The spacing between bones.</summary>
@@ -461,9 +460,5 @@ namespace Spine {
 		public Slot Target { get { return target; } set { target = value; } }
 		/// <summary>The path constraint's setup pose data.</summary>
 		public PathConstraintData Data { get { return data; } }
-
-		override public string ToString () {
-			return data.name;
-		}
 	}
 }

+ 4 - 14
spine-csharp/src/PathConstraintData.cs

@@ -30,9 +30,7 @@
 using System;
 
 namespace Spine {
-	public class PathConstraintData {
-		internal string name;
-		internal int order;
+	public class PathConstraintData : ConstraintData {
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal SlotData target;
 		internal PositionMode positionMode;
@@ -41,8 +39,9 @@ namespace Spine {
 		internal float offsetRotation;
 		internal float position, spacing, rotateMix, translateMix;
 
-		public string Name { get { return name; } }
-		public int Order { get { return order; } set { order = value; } }
+		public PathConstraintData (string name) : base(name) {
+		}
+
 		public ExposedList<BoneData> Bones { get { return bones; } }
 		public SlotData Target { get { return target; } set { target = value; } }			
 		public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } }
@@ -53,15 +52,6 @@ namespace Spine {
 		public float Spacing { get { return spacing; } set { spacing = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = value; } }
-
-		public PathConstraintData (String name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		public override string ToString () {
-			return name;
-		}
 	}
 	
 	public enum PositionMode {

+ 36 - 19
spine-csharp/src/Skeleton.cs

@@ -55,7 +55,7 @@ namespace Spine {
 		public ExposedList<IkConstraint> IkConstraints { get { return ikConstraints; } }
 		public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
 		public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
-		public Skin Skin { get { return skin; } set { skin = value; } }
+		public Skin Skin { get { return skin; } set { SetSkin(value); } }
 		public float R { get { return r; } set { r = value; } }
 		public float G { get { return g; } set { g = value; } }
 		public float B { get { return b; } set { b = value; } }
@@ -118,21 +118,25 @@ namespace Spine {
 			UpdateWorldTransform();
 		}
 
-		/// <summary>Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added
-		/// or removed.</summary>
+		/// <summary>Caches information about bones and constraints. Must be called if the skin is modified or if bones, constraints, or
+		/// weighted path attachments are added or removed.</summary>
 		public void UpdateCache () {
 			var updateCache = this.updateCache;
 			updateCache.Clear();
 			this.updateCacheReset.Clear();
 
+			int boneCount = this.bones.Items.Length;
 			var bones = this.bones;
-			for (int i = 0, n = bones.Count; i < n; i++)
-				bones.Items[i].sorted = false;
+			for (int i = 0; i < boneCount; i++) {
+				Bone bone = bones.Items[i];
+				bone.update = !bone.data.skinRequired || (skin != null && skin.bones.Contains(bone.data));
+				bone.sorted = !bone.update;
+			}
 
+			int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count;
 			var ikConstraints = this.ikConstraints;
 			var transformConstraints = this.transformConstraints;
 			var pathConstraints = this.pathConstraints;
-			int ikCount = ikConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count;
 			int constraintCount = ikCount + transformCount + pathCount;
 			//outer:
 			for (int i = 0; i < constraintCount; i++) {
@@ -160,11 +164,13 @@ namespace Spine {
 				continue_outer: {}
 			}
 
-			for (int i = 0, n = bones.Count; i < n; i++)
+			for (int i = 0; i < boneCount; i++)
 				SortBone(bones.Items[i]);
 		}
 
 		private void SortIkConstraint (IkConstraint constraint) {
+			if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return;
+
 			Bone target = constraint.target;
 			SortBone(target);
 
@@ -185,15 +191,15 @@ namespace Spine {
 		}
 
 		private void SortPathConstraint (PathConstraint constraint) {
+			if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return;
+
 			Slot slot = constraint.target;
 			int slotIndex = slot.data.index;
 			Bone slotBone = slot.bone;
 			if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
 			if (data.defaultSkin != null && data.defaultSkin != skin)
 				SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
-			for (int ii = 0, nn = data.skins.Count; ii < nn; ii++)
-				SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone);
-
+			
 			Attachment attachment = slot.attachment;
 			if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
 
@@ -211,6 +217,8 @@ namespace Spine {
 		}
 
 		private void SortTransformConstraint (TransformConstraint constraint) {
+			if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return;
+
 			SortBone(constraint.target);
 
 			var constrained = constraint.bones;
@@ -235,8 +243,10 @@ namespace Spine {
 		}
 
 		private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
-			foreach (var entry in skin.Attachments)
-				if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone);
+			foreach (var entryObj in skin.Attachments.Keys) {
+				var entry = (Skin.SkinEntry)entryObj;
+				if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone);
+			}
 		}
 
 		private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
@@ -267,6 +277,7 @@ namespace Spine {
 			var bonesItems = bones.Items;
 			for (int i = 0, n = bones.Count; i < n; i++) {
 				Bone bone = bonesItems[i];
+				if (!bone.update) continue;
 				if (bone.sorted) SortReset(bone.children);
 				bone.sorted = false;
 			}
@@ -293,7 +304,8 @@ namespace Spine {
 		}
 
 		/// <summary>
-		/// Updates the world transform for each bone and applies all constraints. The root bone will be temporarily parented to the specified bone.
+		/// Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
+		/// all constraints.
 	 	/// </summary>
 		public void UpdateWorldTransform (Bone parent) {
 			// This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
@@ -313,8 +325,7 @@ namespace Spine {
 				bone.appliedValid = true;
 			}
 
-			// Apply the parent bone transform to the root bone. The root bone
-			// always inherits scale, rotation and reflection.
+			// Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection.
 			Bone rootBone = this.RootBone;
 			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
 			rootBone.worldX = pa * x + pb * y + parent.worldX;
@@ -445,8 +456,12 @@ namespace Spine {
 		}
 
 		/// <summary>
-		/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. 
-		/// If there was no old skin, each slot's setup mode attachment is attached from the new skin.</para>
+		/// <para>Sets the skin used to look up attachments before looking in the <see cref="SkeletonData.DefaultSkin"/>. If the
+		/// skin is changed, <see cref="UpdateCache()"/> is called.
+		/// </para>
+	 	/// <para>Attachments from the new skin are attached if the corresponding attachment from the old skin was attached. 
+		/// If there was no old skin, each slot's setup mode attachment is attached from the new skin.
+		/// </para>
 		/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling 
 		/// <see cref="Skeleton.SetSlotsToSetupPose()"/>. 
 		/// Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the 
@@ -454,6 +469,7 @@ namespace Spine {
 		/// </summary>
 		/// <param name="newSkin">May be null.</param>
 		public void SetSkin (Skin newSkin) {
+			if (newSkin == skin) return;
 			if (newSkin != null) {
 				if (skin != null)
 					newSkin.AttachAll(this, skin);
@@ -470,6 +486,7 @@ namespace Spine {
 				}
 			}
 			skin = newSkin;
+			UpdateCache();
 		}
 
 		/// <summary>Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name.</summary>
@@ -526,7 +543,7 @@ namespace Spine {
 			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
 			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
 				TransformConstraint transformConstraint = transformConstraints.Items[i];
-				if (transformConstraint.data.name == constraintName) return transformConstraint;
+				if (transformConstraint.data.Name == constraintName) return transformConstraint;
 			}
 			return null;
 		}
@@ -537,7 +554,7 @@ namespace Spine {
 			ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
 			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
 				PathConstraint constraint = pathConstraints.Items[i];
-				if (constraint.data.name.Equals(constraintName)) return constraint;
+				if (constraint.data.Name.Equals(constraintName)) return constraint;
 			}
 			return null;
 		}

+ 20 - 8
spine-csharp/src/SkeletonBinary.cs

@@ -174,6 +174,7 @@ namespace Spine {
 				data.shearY = ReadFloat(input);
 				data.length = ReadFloat(input) * scale;
 				data.transformMode = TransformModeValues[ReadVarint(input, true)];
+				data.skinRequired = ReadBoolean(input);
 				if (nonessential) ReadInt(input); // Skip bone color.
 				skeletonData.bones.Add(data);
 			}
@@ -206,6 +207,7 @@ namespace Spine {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				IkConstraintData data = new IkConstraintData(ReadString(input));
 				data.order = ReadVarint(input, true);
+				data.skinRequired = ReadBoolean(input);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
 					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
 				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
@@ -221,6 +223,7 @@ namespace Spine {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				TransformConstraintData data = new TransformConstraintData(ReadString(input));
 				data.order = ReadVarint(input, true);
+				data.skinRequired = ReadBoolean(input);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
 				    data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
 				data.target = skeletonData.bones.Items[ReadVarint(input, true)];
@@ -243,6 +246,7 @@ namespace Spine {
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				PathConstraintData data = new PathConstraintData(ReadString(input));
 				data.order = ReadVarint(input, true);
+				data.skinRequired = ReadBoolean(input);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++)
 					data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
 				data.target = skeletonData.slots.Items[ReadVarint(input, true)];
@@ -260,7 +264,7 @@ namespace Spine {
 			}
 
 			// Default skin.
-			Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential);
+			Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential);
 			if (defaultSkin != null) {
 				skeletonData.defaultSkin = defaultSkin;
 				skeletonData.skins.Add(defaultSkin);
@@ -268,7 +272,7 @@ namespace Spine {
 
 			// Skins.
 			for (int i = 0, n = ReadVarint(input, true); i < n; i++)
-				skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential));
+				skeletonData.skins.Add(ReadSkin(input, skeletonData, false, nonessential));
 
 			// Linked meshes.
 			for (int i = 0, n = linkedMeshes.Count; i < n; i++) {
@@ -312,16 +316,24 @@ namespace Spine {
 
 
 		/// <returns>May be null.</returns>
-		private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) {
-			int slotCount = ReadVarint(input, true);
-			if (slotCount == 0) return null;
-			Skin skin = new Skin(skinName);
-			for (int i = 0; i < slotCount; i++) {
+		private Skin ReadSkin (Stream input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) {
+			Skin skin = new Skin(defaultSkin ? "default" : ReadString(input));
+			if (!defaultSkin) {
+				for (int i = 0, n = ReadVarint(input, true); i < n; i++)
+					skin.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]);
+				for (int i = 0, n = ReadVarint(input, true); i < n; i++)
+					skin.constraints.Add(skeletonData.ikConstraints.Items[ReadVarint(input, true)]);
+				for (int i = 0, n = ReadVarint(input, true); i < n; i++)
+					skin.constraints.Add(skeletonData.transformConstraints.Items[ReadVarint(input, true)]);
+				for (int i = 0, n = ReadVarint(input, true); i < n; i++)
+					skin.constraints.Add(skeletonData.pathConstraints.Items[ReadVarint(input, true)]);
+			}
+			for (int i = 0, n = ReadVarint(input, true); i < n; i++) {
 				int slotIndex = ReadVarint(input, true);
 				for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) {
 					String name = ReadString(input);
 					Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential);
-					if (attachment != null) skin.AddAttachment(slotIndex, name, attachment);
+					if (attachment != null) skin.SetAttachment(slotIndex, name, attachment);
 				}
 			}
 			return skin;

+ 111 - 67
spine-csharp/src/SkeletonJson.cs

@@ -109,27 +109,30 @@ namespace Spine {
 			}
 
 			// Bones.
-			foreach (Dictionary<string, Object> boneMap in (List<Object>)root["bones"]) {
-				BoneData parent = null;
-				if (boneMap.ContainsKey("parent")) {
-					parent = skeletonData.FindBone((string)boneMap["parent"]);
-					if (parent == null)
-						throw new Exception("Parent bone not found: " + boneMap["parent"]);
+			if (root.ContainsKey("bones")) {
+				foreach (Dictionary<string, Object> boneMap in (List<Object>)root["bones"]) {
+					BoneData parent = null;
+					if (boneMap.ContainsKey("parent")) {
+						parent = skeletonData.FindBone((string)boneMap["parent"]);
+						if (parent == null)
+							throw new Exception("Parent bone not found: " + boneMap["parent"]);
+					}
+					var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent);
+					data.length = GetFloat(boneMap, "length", 0) * scale;
+					data.x = GetFloat(boneMap, "x", 0) * scale;
+					data.y = GetFloat(boneMap, "y", 0) * scale;
+					data.rotation = GetFloat(boneMap, "rotation", 0);
+					data.scaleX = GetFloat(boneMap, "scaleX", 1);
+					data.scaleY = GetFloat(boneMap, "scaleY", 1);
+					data.shearX = GetFloat(boneMap, "shearX", 0);
+					data.shearY = GetFloat(boneMap, "shearY", 0);
+
+					string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString());
+					data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true);
+					data.skinRequired = GetBoolean(boneMap, "skin", false);
+
+					skeletonData.bones.Add(data);
 				}
-				var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent);
-				data.length = GetFloat(boneMap, "length", 0) * scale;
-				data.x = GetFloat(boneMap, "x", 0) * scale;
-				data.y = GetFloat(boneMap, "y", 0) * scale;
-				data.rotation = GetFloat(boneMap, "rotation", 0);
-				data.scaleX = GetFloat(boneMap, "scaleX", 1);
-				data.scaleY = GetFloat(boneMap, "scaleY", 1);
-				data.shearX = GetFloat(boneMap, "shearX", 0);
-				data.shearY = GetFloat(boneMap, "shearY", 0);
-
-				string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString());
-				data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true);
-
-				skeletonData.bones.Add(data);
 			}
 
 			// Slots.
@@ -171,16 +174,19 @@ namespace Spine {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["ik"]) {
 					IkConstraintData data = new IkConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
+					data.skinRequired = GetBoolean(constraintMap,"skin", false);
 
-					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
-						BoneData bone = skeletonData.FindBone(boneName);
-						if (bone == null) throw new Exception("IK constraint bone not found: " + boneName);
-						data.bones.Add(bone);
+					if (constraintMap.ContainsKey("bones")) {
+						foreach (string boneName in (List<Object>)constraintMap["bones"]) {
+							BoneData bone = skeletonData.FindBone(boneName);
+							if (bone == null) throw new Exception("IK bone not found: " + boneName);
+							data.bones.Add(bone);
+						}
 					}
 
 					string targetName = (string)constraintMap["target"];
 					data.target = skeletonData.FindBone(targetName);
-					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
+					if (data.target == null) throw new Exception("IK target bone not found: " + targetName);
 					data.mix = GetFloat(constraintMap, "mix", 1);
 					data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
 					data.compress = GetBoolean(constraintMap, "compress", false);
@@ -196,16 +202,19 @@ namespace Spine {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["transform"]) {
 					TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
+					data.skinRequired = GetBoolean(constraintMap,"skin", false);
 
-					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
-						BoneData bone = skeletonData.FindBone(boneName);
-						if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
-						data.bones.Add(bone);
+					if (constraintMap.ContainsKey("bones")) {
+						foreach (string boneName in (List<Object>)constraintMap["bones"]) {
+							BoneData bone = skeletonData.FindBone(boneName);
+							if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName);
+							data.bones.Add(bone);
+						}
 					}
 
 					string targetName = (string)constraintMap["target"];
 					data.target = skeletonData.FindBone(targetName);
-					if (data.target == null) throw new Exception("Target bone not found: " + targetName);
+					if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName);
 
 					data.local = GetBoolean(constraintMap, "local", false);
 					data.relative = GetBoolean(constraintMap, "relative", false);
@@ -231,16 +240,19 @@ namespace Spine {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["path"]) {
 					PathConstraintData data = new PathConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
+					data.skinRequired = GetBoolean(constraintMap,"skin", false);
 
-					foreach (string boneName in (List<Object>)constraintMap["bones"]) {
-						BoneData bone = skeletonData.FindBone(boneName);
-						if (bone == null) throw new Exception("Path bone not found: " + boneName);
-						data.bones.Add(bone);
+					if (constraintMap.ContainsKey("bones")) {
+						foreach (string boneName in (List<Object>)constraintMap["bones"]) {
+							BoneData bone = skeletonData.FindBone(boneName);
+							if (bone == null) throw new Exception("Path bone not found: " + boneName);
+							data.bones.Add(bone);
+						}
 					}
 
 					string targetName = (string)constraintMap["target"];
 					data.target = skeletonData.FindSlot(targetName);
-					if (data.target == null) throw new Exception("Target slot not found: " + targetName);
+					if (data.target == null) throw new Exception("Path target slot not found: " + targetName);
 
 					data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
 					data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
@@ -259,18 +271,48 @@ namespace Spine {
 
 			// Skins.
 			if (root.ContainsKey("skins")) {
-					foreach (KeyValuePair<string, Object> skinMap in (Dictionary<string, Object>)root["skins"]) {
-					var skin = new Skin(skinMap.Key);
-					foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap.Value) {
-						int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
-						foreach (KeyValuePair<string, Object> entry in ((Dictionary<string, Object>)slotEntry.Value)) {
-							try {
-								Attachment attachment = ReadAttachment((Dictionary<string, Object>)entry.Value, skin, slotIndex, entry.Key, skeletonData);
-								if (attachment != null) skin.AddAttachment(slotIndex, entry.Key, attachment);
-							} catch (Exception e) {
-								throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
-							}
-						} 
+				foreach (Dictionary<string, object> skinMap in (List<object>)root["skins"]) { 
+					Skin skin = new Skin((string)skinMap["name"]);
+					if (skinMap.ContainsKey("bones")) {
+						foreach (string entryName in (List<Object>)skinMap["bones"]) {
+							BoneData bone = skeletonData.FindBone(entryName);
+							if (bone == null) throw new Exception("Skin bone not found: " + entryName);
+							skin.bones.Add(bone);
+						}
+					}
+					if (skinMap.ContainsKey("ik")) {
+						foreach (string entryName in (List<Object>)skinMap["ik"]) {
+							IkConstraintData constraint = skeletonData.FindIkConstraint(entryName);
+							if (constraint == null) throw new Exception("Skin IK constraint not found: " + entryName);
+							skin.constraints.Add(constraint);
+						}
+					}
+					if (skinMap.ContainsKey("transform")) {
+						foreach (string entryName in (List<Object>)skinMap["transform"]) {
+							TransformConstraintData constraint = skeletonData.FindTransformConstraint(entryName);
+							if (constraint == null) throw new Exception("Skin transform constraint not found: " + entryName);
+							skin.constraints.Add(constraint);
+						}
+					}
+					if (skinMap.ContainsKey("path")) {
+						foreach (string entryName in (List<Object>)skinMap["path"]) {
+							PathConstraintData constraint = skeletonData.FindPathConstraint(entryName);
+							if (constraint == null) throw new Exception("Skin path constraint not found: " + entryName);
+							skin.constraints.Add(constraint);
+						}
+					}
+					if (skinMap.ContainsKey("attachments")) {
+						foreach (KeyValuePair<string, Object> slotEntry in (Dictionary<string, Object>)skinMap["attachments"]) {
+							int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key);
+							foreach (KeyValuePair<string, Object> entry in ((Dictionary<string, Object>)slotEntry.Value)) {
+								try {
+									Attachment attachment = ReadAttachment((Dictionary<string, Object>)entry.Value, skin, slotIndex, entry.Key, skeletonData);
+									if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment);
+								} catch (Exception e) {
+									throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e);
+								}
+							} 
+						}
 					}
 					skeletonData.skins.Add(skin);
 					if (skin.name == "default") skeletonData.defaultSkin = skin;
@@ -494,7 +536,7 @@ namespace Spine {
 
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
+								float time = GetFloat(valueMap, "time", 0);
 								timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]);
 							}
 							timelines.Add(timeline);
@@ -506,7 +548,7 @@ namespace Spine {
 
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
+								float time = GetFloat(valueMap, "time", 0);
 								string c = (string)valueMap["color"];
 								timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
 								ReadCurve(valueMap, timeline, frameIndex);
@@ -521,7 +563,7 @@ namespace Spine {
 
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
+								float time = GetFloat(valueMap, "time", 0);
 								string light = (string)valueMap["light"];
 								string dark = (string)valueMap["dark"];
 								timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3),
@@ -554,7 +596,7 @@ namespace Spine {
 
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]);
+								timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0));
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 							}
@@ -563,9 +605,11 @@ namespace Spine {
 
 						} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
 							TranslateTimeline timeline;
-							float timelineScale = 1;
-							if (timelineName == "scale")
+							float timelineScale = 1, defaultValue = 0;
+							if (timelineName == "scale") {
 								timeline = new ScaleTimeline(values.Count);
+								defaultValue = 1;
+							}
 							else if (timelineName == "shear")
 								timeline = new ShearTimeline(values.Count);
 							else {
@@ -576,9 +620,9 @@ namespace Spine {
 
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
-								float x = GetFloat(valueMap, "x", 0);
-								float y = GetFloat(valueMap, "y", 0);
+								float time = GetFloat(valueMap, "time", 0);
+								float x = GetFloat(valueMap, "x", defaultValue);
+								float y = GetFloat(valueMap, "y", defaultValue);
 								timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
@@ -603,7 +647,7 @@ namespace Spine {
 					foreach (Dictionary<string, Object> valueMap in values) {
 						timeline.SetFrame(
 							frameIndex,
-							(float)valueMap["time"],
+							GetFloat(valueMap, "time", 0),
 							GetFloat(valueMap, "mix", 1),
 							GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
 							GetBoolean(valueMap, "compress", true),
@@ -626,7 +670,7 @@ namespace Spine {
 					timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
 					int frameIndex = 0;
 					foreach (Dictionary<string, Object> valueMap in values) {
-						float time = (float)valueMap["time"];
+						float time = GetFloat(valueMap, "time", 0);
 						float rotateMix = GetFloat(valueMap, "rotateMix", 1);
 						float translateMix = GetFloat(valueMap, "translateMix", 1);
 						float scaleMix = GetFloat(valueMap, "scaleMix", 1);
@@ -641,8 +685,8 @@ namespace Spine {
 			}
 
 			// Path constraint timelines.
-			if (map.ContainsKey("paths")) {
-				foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["paths"]) {
+			if (map.ContainsKey("path")) {
+				foreach (KeyValuePair<string, Object> constraintMap in (Dictionary<string, Object>)map["path"]) {
 					int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
 					if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
 					PathConstraintData data = skeletonData.pathConstraints.Items[index];
@@ -664,7 +708,7 @@ namespace Spine {
 							timeline.pathConstraintIndex = index;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale);
+								timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, timelineName, 0) * timelineScale);
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 							}
@@ -676,7 +720,7 @@ namespace Spine {
 							timeline.pathConstraintIndex = index;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1));
+								timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1));
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 							}
@@ -727,7 +771,7 @@ namespace Spine {
 									}
 								}
 
-								timeline.SetFrame(frameIndex, (float)valueMap["time"], deform);
+								timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), deform);
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 							}
@@ -770,7 +814,7 @@ namespace Spine {
 						for (int i = slotCount - 1; i >= 0; i--)
 							if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
 					}
-					timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder);
+					timeline.SetFrame(frameIndex++, GetFloat(drawOrderMap, "time", 0), drawOrder);
 				}
 				timelines.Add(timeline);
 				duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
@@ -784,7 +828,7 @@ namespace Spine {
 				foreach (Dictionary<string, Object> eventMap in eventsMap) {
 					EventData eventData = skeletonData.FindEvent((string)eventMap["name"]);
 					if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]);
-					var e = new Event((float)eventMap["time"], eventData) {
+					var e = new Event(GetFloat(eventMap, "time", 0), eventData) {
 						intValue = GetInt(eventMap, "int", eventData.Int),
 						floatValue = GetFloat(eventMap, "float", eventData.Float),
 						stringValue = GetString(eventMap, "string", eventData.String)
@@ -807,12 +851,12 @@ namespace Spine {
 			if (!valueMap.ContainsKey("curve"))
 				return;
 			Object curveObject = valueMap["curve"];
-			if (curveObject.Equals("stepped"))
+			if (curveObject is string)
 				timeline.SetStepped(frameIndex);
 			else {
 				var curve = curveObject as List<Object>;
 				if (curve != null)
-					timeline.SetCurve(frameIndex, (float)curve[0], (float)curve[1], (float)curve[2], (float)curve[3]);
+					timeline.SetCurve(frameIndex, (float)curveObject, GetFloat(valueMap, "c2", 0), GetFloat(valueMap, "c3", 1), GetFloat(valueMap, "c4", 1));
 			}
 		}
 

+ 109 - 42
spine-csharp/src/Skin.cs

@@ -28,7 +28,9 @@
  *****************************************************************************/
 
 using System;
+using System.Collections;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 
 namespace Spine {
 	/// <summary>Stores attachments by slot index and attachment name.
@@ -37,53 +39,91 @@ namespace Spine {
 	/// </summary>
 	public class Skin {
 		internal string name;
-		private Dictionary<AttachmentKeyTuple, Attachment> attachments =
-			new Dictionary<AttachmentKeyTuple, Attachment>(AttachmentKeyTupleComparer.Instance);
+		private OrderedDictionary attachments = new OrderedDictionary(SkinEntryComparer.Instance); // contains <SkinEntry, Attachment>
+		internal readonly ExposedList<BoneData> bones = new ExposedList<BoneData>();
+		internal readonly ExposedList<ConstraintData> constraints = new ExposedList<ConstraintData>();
 
 		public string Name { get { return name; } }
-		public Dictionary<AttachmentKeyTuple, Attachment> Attachments { get { return attachments; } }
-
+		public OrderedDictionary Attachments { get { return attachments; } }
+		public ExposedList<BoneData> Bones { get { return bones; } }
+		public ExposedList<ConstraintData> Constraints { get { return constraints; } }
+		
 		public Skin (string name) {
 			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			this.name = name;
 		}
 
-		/// <summary>Adds an attachment to the skin for the specified slot index and name. If the name already exists for the slot, the previous value is replaced.</summary>
-		public void AddAttachment (int slotIndex, string name, Attachment attachment) {
+		/// <summary>Adds an attachment to the skin for the specified slot index and name.
+		/// If the name already exists for the slot, the previous value is replaced.</summary>
+		public void SetAttachment (int slotIndex, string name, Attachment attachment) {
 			if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null.");
-			attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment;
+			if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0.");
+			attachments[new SkinEntry(slotIndex, name, attachment)] = attachment;
+		}
+
+		///<summary>Adds all attachments, bones, and constraints from the specified skin to this skin.</summary>
+		public void AddSkin (Skin skin) {
+			foreach (BoneData data in skin.bones)
+				if (!bones.Contains(data)) bones.Add(data);
+
+			foreach (ConstraintData data in skin.constraints)
+				if (!constraints.Contains(data)) constraints.Add(data);
+
+			foreach (SkinEntry entry in skin.attachments.Keys)
+				SetAttachment(entry.SlotIndex, entry.Name, entry.Attachment);
+		}
+
+		///<summary>Adds all attachments from the specified skin to this skin. Attachments are deep copied.</summary>
+		public void CopySkin (Skin skin) {
+			foreach (BoneData data in skin.bones)
+				if (!bones.Contains(data)) bones.Add(data);
+
+			foreach (ConstraintData data in skin.constraints)
+				if (!constraints.Contains(data)) constraints.Add(data);
+
+			foreach (SkinEntry entry in skin.attachments.Keys) {
+				Attachment attachment = entry.Attachment.Copy();
+				if (attachment is MeshAttachment) {
+				}
+				SetAttachment(entry.SlotIndex, entry.Name, attachment);
+			}
 		}
 
 		/// <summary>Returns the attachment for the specified slot index and name, or null.</summary>
 		/// <returns>May be null.</returns>
 		public Attachment GetAttachment (int slotIndex, string name) {
-			Attachment attachment;
-			attachments.TryGetValue(new AttachmentKeyTuple(slotIndex, name), out attachment);
-			return attachment;
+			var lookup = new SkinEntry(slotIndex, name, null);
+			var obj = attachments[lookup];
+			return (obj == null) ? null : (Attachment)obj;
 		}
 
 		/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
 		public void RemoveAttachment (int slotIndex, string name) {
 			if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0");
-			attachments.Remove(new AttachmentKeyTuple(slotIndex, name));
+			var lookup = new SkinEntry(slotIndex, name, null);
+			attachments.Remove(lookup);
 		}
 
-		/// <summary>Finds the skin keys for a given slot. The results are added to the passed List(names).</summary>
-		/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
-		/// <param name="names">Found skin key names will be added to this list.</param>
-		public void FindNamesForSlot (int slotIndex, List<string> names) {
-			if (names == null) throw new ArgumentNullException("names", "names cannot be null.");
-			foreach (AttachmentKeyTuple key in attachments.Keys)
-				if (key.slotIndex == slotIndex) names.Add(key.name);
+		///<summary>Returns all attachments contained in this skin.</summary>
+		public List<SkinEntry> GetAttachments () {
+			List<SkinEntry> entries = new List<SkinEntry>();
+			foreach (SkinEntry entry in this.attachments.Keys)
+				entries.Add(entry);
+			return entries;
 		}
 
-		/// <summary>Finds the attachments for a given slot. The results are added to the passed List(Attachment).</summary>
+		/// <summary>Returns all attachments in this skin for the specified slot index.</summary>
 		/// <param name="slotIndex">The target slotIndex. To find the slot index, use <see cref="Spine.Skeleton.FindSlotIndex"/> or <see cref="Spine.SkeletonData.FindSlotIndex"/>
-		/// <param name="attachments">Found Attachments will be added to this list.</param>
-		public void FindAttachmentsForSlot (int slotIndex, List<Attachment> attachments) {
-			if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null.");
-			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in this.attachments)
-				if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value);
+		public void GetAttachments (int slotIndex, List<SkinEntry> attachments) {
+			foreach (SkinEntry entry in this.attachments.Keys)
+				if (entry.SlotIndex == slotIndex) attachments.Add(entry);
+		}
+
+		///<summary>Clears all attachments, bones, and constraints.</summary>
+		public void Clear () {
+			attachments.Clear();
+			bones.Clear();
+			constraints.Clear();
 		}
 
 		override public string ToString () {
@@ -92,38 +132,65 @@ namespace Spine {
 
 		/// <summary>Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached.</summary>
 		internal void AttachAll (Skeleton skeleton, Skin oldSkin) {
-			foreach (KeyValuePair<AttachmentKeyTuple, Attachment> entry in oldSkin.attachments) {
-				int slotIndex = entry.Key.slotIndex;
+			foreach (SkinEntry entry in oldSkin.attachments.Keys) {
+				int slotIndex = entry.SlotIndex;
 				Slot slot = skeleton.slots.Items[slotIndex];
-				if (slot.Attachment == entry.Value) {
-					Attachment attachment = GetAttachment(slotIndex, entry.Key.name);
+				if (slot.Attachment == entry.Attachment) {
+					Attachment attachment = GetAttachment(slotIndex, entry.Name);
 					if (attachment != null) slot.Attachment = attachment;
 				}
 			}
 		}
 
-		public struct AttachmentKeyTuple {
-			public readonly int slotIndex;
-			public readonly string name;
-			internal readonly int nameHashCode;
+		/// <summary>Stores an entry in the skin consisting of the slot index, name, and attachment.</summary>
+		public struct SkinEntry {
+			private readonly int slotIndex;
+			private readonly string name;
+			private readonly Attachment attachment;
+			internal readonly int hashCode;
 
-			public AttachmentKeyTuple (int slotIndex, string name) {
+			public SkinEntry (int slotIndex, string name, Attachment attachment) {
 				this.slotIndex = slotIndex;
 				this.name = name;
-				nameHashCode = this.name.GetHashCode();
+				this.attachment = attachment;
+				this.hashCode = this.name.GetHashCode() + this.slotIndex * 37;
 			}
-		}
 
-		// Avoids boxing in the dictionary.
-		class AttachmentKeyTupleComparer : IEqualityComparer<AttachmentKeyTuple> {
-			internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer();
+			public int SlotIndex {
+				get {
+					return slotIndex;
+				}
+			}
 
-			bool IEqualityComparer<AttachmentKeyTuple>.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) {
-				return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && string.Equals(o1.name, o2.name, StringComparison.Ordinal);
+			/// <summary>The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor.</summary>
+			public String Name {
+				get {
+					return name;
+				}
+			}
+
+			public Attachment Attachment {
+				get {
+					return attachment;
+				}
+			}
+		}
+	
+		// Avoids boxing in the dictionary and is necessary to omit entry.attachment in the comparison.
+		class SkinEntryComparer : IEqualityComparer {
+			internal static readonly SkinEntryComparer Instance = new SkinEntryComparer();
+
+			bool IEqualityComparer.Equals (object o1, object o2) {
+				var e1 = (SkinEntry)o1;
+				var e2 = (SkinEntry)o2;
+				if (e1.SlotIndex != e2.SlotIndex) return false;
+				if (!string.Equals(e1.Name, e2.Name, StringComparison.Ordinal)) return false;
+				return true;
 			}
 
-			int IEqualityComparer<AttachmentKeyTuple>.GetHashCode (AttachmentKeyTuple o) {
-				return o.slotIndex;
+			int IEqualityComparer.GetHashCode (object o) {
+				var e = (SkinEntry)o;
+				return e.Name.GetHashCode() + e.SlotIndex * 37;
 			}
 		}
 	}

+ 5 - 1
spine-csharp/src/SlotData.cs

@@ -40,8 +40,11 @@ namespace Spine {
 		internal string attachmentName;
 		internal BlendMode blendMode;
 
+		/// <summary>The index of the slot in <see cref="Skeleton.Slots"/>.</summary>
 		public int Index { get { return index; } }
+		/// <summary>The name of the slot, which is unique across all slots in the skeleton.</summary>
 		public string Name { get { return name; } }
+		/// <summary>The bone this slot belongs to.</summary>
 		public BoneData BoneData { get { return boneData; } }
 		public float R { get { return r; } set { r = value; } }
 		public float G { get { return g; } set { g = value; } }
@@ -53,8 +56,9 @@ namespace Spine {
 		public float B2 { get { return b2; } set { b2 = value; } }
 		public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } }
 
-		/// <summary>May be null.</summary>
+		/// <summary>The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible.</summary>
 		public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } }
+		/// <summary>The blend mode for drawing the slot's attachment.</summary>
 		public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
 
 		public SlotData (int index, String name, BoneData boneData) {

+ 1 - 2
spine-csharp/src/TransformConstraint.cs

@@ -37,7 +37,7 @@ namespace Spine {
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide.</para>
 	/// </summary>
-	public class TransformConstraint : IConstraint {
+	public class TransformConstraint : IUpdatable {
 		internal TransformConstraintData data;
 		internal ExposedList<Bone> bones;
 		internal Bone target;
@@ -288,7 +288,6 @@ namespace Spine {
 			}
 		}
 
-		public int Order { get { return data.order; } }
 		/// <summary>The bones that will be modified by this transform constraint.</summary>
 		public ExposedList<Bone> Bones { get { return bones; } }
 		/// <summary>The target bone whose world transform will be copied to the constrained bones.</summary>

+ 2 - 12
spine-csharp/src/TransformConstraintData.cs

@@ -30,17 +30,13 @@
 using System;
 
 namespace Spine {
-	public class TransformConstraintData {
-		internal string name;
-		internal int order;
+	public class TransformConstraintData : ConstraintData {
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal BoneData target;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
 		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 		internal bool relative, local;
 
-		public string Name { get { return name; } }
-		public int Order { get { return order; } set { order = value; } }
 		public ExposedList<BoneData> Bones { get { return bones; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
@@ -58,13 +54,7 @@ namespace Spine {
 		public bool Relative { get { return relative; } set { relative = value; } }
 		public bool Local { get { return local; } set { local = value; } }
 
-		public TransformConstraintData (string name) {
-			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
-			this.name = name;
-		}
-
-		override public string ToString () {
-			return name;
+		public TransformConstraintData (string name) : base(name) {
 		}
 	}
 }

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs

@@ -60,7 +60,7 @@ namespace Spine.Unity.Examples {
 		}
 
 		public void Equip (int slotIndex, string attachmentName, Attachment attachment) {
-			equipsSkin.AddAttachment(slotIndex, attachmentName, attachment);
+			equipsSkin.SetAttachment(slotIndex, attachmentName, attachment);
 			skeletonAnimation.Skeleton.SetSkin(equipsSkin);
 			RefreshSkeletonAttachments();
 		}

+ 1 - 1
spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs

@@ -170,7 +170,7 @@ namespace Spine.Unity.Examples {
 			if (skinName != "")
 				skin = skeletonData.FindSkin(skinName);
 
-			skin.AddAttachment(slotIndex, att.Name, att);
+			skin.SetAttachment(slotIndex, att.Name, att);
 
 			return att;
 		}

+ 4 - 3
spine-unity/Assets/Spine/Editor/spine-unity/Editor/PointFollowerEditor.cs

@@ -27,6 +27,7 @@
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 
+using System.Collections;
 using UnityEditor;
 using UnityEngine;
 
@@ -110,11 +111,11 @@ namespace Spine.Unity.Editor {
 		}
 
 		static void DrawPointsInSkin (Skin skin, Skeleton skeleton, Transform transform) {
-			foreach (var skinEntry in skin.Attachments) {
+			foreach (DictionaryEntry skinEntry in skin.Attachments) {
 				var attachment = skinEntry.Value as PointAttachment;
 				if (attachment != null) {
-					var skinKey = skinEntry.Key;
-					var slot = skeleton.Slots.Items[skinKey.slotIndex];
+					var skinKey = (Skin.SkinEntry)skinEntry.Key;
+					var slot = skeleton.Slots.Items[skinKey.SlotIndex];
 					DrawPointAttachmentWithLabel(attachment, slot.Bone, transform);
 				}
 			}

+ 21 - 13
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonBaker.cs

@@ -228,11 +228,11 @@ namespace Spine.Unity.Editor {
 					List<string> attachmentNames = new List<string>();
 					for (int i = 0; i < skinCount; i++) {
 						var skin = skins.Items[i];
-						List<string> temp = new List<string>();
-						skin.FindNamesForSlot(s, temp);
-						foreach (string str in temp) {
-							if (!attachmentNames.Contains(str))
-								attachmentNames.Add(str);
+						var skinEntries = new List<Skin.SkinEntry>();
+						skin.GetAttachments(s, skinEntries);
+						foreach (var entry in skinEntries) {
+							if (!attachmentNames.Contains(entry.Name))
+								attachmentNames.Add(entry.Name);
 						}
 					}
 					slotLookup.Add(s, attachmentNames);
@@ -333,8 +333,8 @@ namespace Spine.Unity.Editor {
 				}
 
 				//create slots and attachments
-				for (int i = 0; i < skeletonData.Slots.Count; i++) {
-					var slotData = skeletonData.Slots.Items[i];
+				for (int slotIndex = 0; slotIndex < skeletonData.Slots.Count; slotIndex++) {
+					var slotData = skeletonData.Slots.Items[slotIndex];
 					Transform slotTransform = SpineEditorUtilities.EditorInstantiation.NewGameObject(slotData.Name).transform;
 					slotTransform.parent = prefabRoot.transform;
 					slotTable.Add(slotData.Name, slotTransform);
@@ -342,12 +342,20 @@ namespace Spine.Unity.Editor {
 					List<Attachment> attachments = new List<Attachment>();
 					List<string> attachmentNames = new List<string>();
 
-					skin.FindAttachmentsForSlot(i, attachments);
-					skin.FindNamesForSlot(i, attachmentNames);
+					var skinEntries = new List<Skin.SkinEntry>();
+					skin.GetAttachments(slotIndex, skinEntries);
+					foreach (var entry in skinEntries) {
+						attachments.Add(entry.Attachment);
+						attachmentNames.Add(entry.Name);
+					}
 
 					if (skin != skeletonData.DefaultSkin) {
-						skeletonData.DefaultSkin.FindAttachmentsForSlot(i, attachments);
-						skeletonData.DefaultSkin.FindNamesForSlot(i, attachmentNames);
+						skinEntries.Clear();
+						skeletonData.DefaultSkin.GetAttachments(slotIndex, skinEntries);
+						foreach (var entry in skinEntries) {
+							attachments.Add(entry.Attachment);
+							attachmentNames.Add(entry.Name);
+						}
 					}
 
 					for (int a = 0; a < attachments.Count; a++) {
@@ -380,7 +388,7 @@ namespace Spine.Unity.Editor {
 							rotation = 0;
 
 							if (isWeightedMesh)
-								mesh = ExtractWeightedMeshAttachment(attachmentMeshName, meshAttachment, i, skeletonData, boneList, mesh);
+								mesh = ExtractWeightedMeshAttachment(attachmentMeshName, meshAttachment, slotIndex, skeletonData, boneList, mesh);
 							else
 								mesh = ExtractMeshAttachment(attachmentMeshName, meshAttachment, mesh);
 							
@@ -410,7 +418,7 @@ namespace Spine.Unity.Editor {
 						}
 
 						attachmentTransform.GetComponent<Renderer>().sharedMaterial = material;
-						attachmentTransform.GetComponent<Renderer>().sortingOrder = i;
+						attachmentTransform.GetComponent<Renderer>().sortingOrder = slotIndex;
 
 						if (attachmentName != slotData.AttachmentName)
 							attachmentTransform.gameObject.SetActive(false);

+ 19 - 6
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDataAssetInspector.cs

@@ -498,15 +498,28 @@ namespace Spine.Unity.Editor {
 
 						using (new SpineInspectorUtility.IndentScope()) {
 							{
-								skin.FindNamesForSlot(i, slotAttachmentNames);
-								skin.FindAttachmentsForSlot(i, slotAttachments);
+								var skinEntries = new List<Skin.SkinEntry>();
+								skin.GetAttachments(i, skinEntries);
+								foreach (var entry in skinEntries) {
+									slotAttachments.Add(entry.Attachment);
+									slotAttachmentNames.Add(entry.Name);
+								}
 
 								if (skin != defaultSkin) {
-									defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
-									defaultSkin.FindNamesForSlot(i, slotAttachmentNames);
-									defaultSkin.FindAttachmentsForSlot(i, slotAttachments);
+									skinEntries.Clear();
+									defaultSkin.GetAttachments(i, skinEntries);
+									foreach (var entry in skinEntries) {
+										slotAttachments.Add(entry.Attachment);
+										slotAttachmentNames.Add(entry.Name);
+										defaultSkinAttachmentNames.Add(entry.Name);
+									}
+
 								} else {
-									defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
+									skinEntries.Clear();
+									defaultSkin.GetAttachments(i, skinEntries);
+									foreach (var entry in skinEntries) {
+										defaultSkinAttachmentNames.Add(entry.Name);
+									}
 								}
 							}
 

+ 13 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDebugWindow.cs

@@ -574,8 +574,19 @@ namespace Spine.Unity.Editor {
 			for (int i = skeleton.Slots.Count - 1; i >= 0; i--) {
 				var attachments = new List<Attachment>();
 				attachmentTable.Add(skeleton.Slots.Items[i], attachments);
-				skin.FindAttachmentsForSlot(i, attachments); // Add skin attachments.
-				if (notDefaultSkin) defaultSkin.FindAttachmentsForSlot(i, attachments); // Add default skin attachments.
+				// Add skin attachments.
+				var skinEntries = new List<Skin.SkinEntry>();
+				skin.GetAttachments(i, skinEntries);
+				foreach (var entry in skinEntries) {
+					attachments.Add(entry.Attachment);
+				}
+				if (notDefaultSkin) { // Add default skin attachments.
+					skinEntries.Clear();
+					defaultSkin.GetAttachments(i, skinEntries);
+					foreach (var entry in skinEntries) {
+						attachments.Add(entry.Attachment);
+					}
+				}
 			}
 
 			activeSkin = skeleton.Skin;

+ 27 - 18
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs

@@ -163,6 +163,7 @@ namespace Spine.Unity.Editor {
 		override public void OnInspectorGUI () {
 			bool multi = serializedObject.isEditingMultipleObjects;
 			DrawInspectorGUI(multi);
+			HandleSkinChange();
 			if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current) ||
 				AreAnyMaskMaterialsMissing()) {
 				if (!Application.isPlaying) {
@@ -174,21 +175,6 @@ namespace Spine.Unity.Editor {
 					SceneView.RepaintAll();
 				}
 			}
-
-			if (!Application.isPlaying && Event.current.type == EventType.Layout) {
-				bool mismatchDetected = false;
-				if (multi) {
-					foreach (var o in targets)
-						mismatchDetected |= UpdateIfSkinMismatch((SkeletonRenderer)o);
-				} else {
-					mismatchDetected |= UpdateIfSkinMismatch(target as SkeletonRenderer);
-				}
-
-				if (mismatchDetected) {
-					mismatchDetected = false;
-					SceneView.RepaintAll();
-				}
-			}
 		}
 
 		protected virtual void DrawInspectorGUI (bool multi) {
@@ -493,19 +479,42 @@ namespace Spine.Unity.Editor {
 			}
 		}
 
-		static bool UpdateIfSkinMismatch (SkeletonRenderer skeletonRenderer) {
+		void HandleSkinChange() {
+			if (!Application.isPlaying && Event.current.type == EventType.Layout && !initialSkinName.hasMultipleDifferentValues) {
+				bool mismatchDetected = false;
+				string newSkinName = initialSkinName.stringValue;
+				foreach (var o in targets) {
+					mismatchDetected |= UpdateIfSkinMismatch((SkeletonRenderer)o, newSkinName);
+				}
+
+				if (mismatchDetected) {
+					mismatchDetected = false;
+					SceneView.RepaintAll();
+				}
+			}
+		}
+
+		static bool UpdateIfSkinMismatch (SkeletonRenderer skeletonRenderer, string componentSkinName) {
 			if (!skeletonRenderer.valid) return false;
 
 			var skin = skeletonRenderer.Skeleton.Skin;
 			string skeletonSkinName = skin != null ? skin.Name : null;
-			string componentSkinName = skeletonRenderer.initialSkinName;
 			bool defaultCase = skin == null && string.IsNullOrEmpty(componentSkinName);
 			bool fieldMatchesSkin = defaultCase || string.Equals(componentSkinName, skeletonSkinName, System.StringComparison.Ordinal);
 
 			if (!fieldMatchesSkin) {
 				Skin skinToSet = string.IsNullOrEmpty(componentSkinName) ? null : skeletonRenderer.Skeleton.Data.FindSkin(componentSkinName);
-				skeletonRenderer.Skeleton.Skin = skinToSet;
+				skeletonRenderer.Skeleton.SetSkin(skinToSet);
 				skeletonRenderer.Skeleton.SetSlotsToSetupPose();
+
+				// Note: the UpdateIfSkinMismatch concept shall be replaced with e.g. an OnValidate based
+				// solution or in a separate commit. The current solution does not repaint the Game view because
+				// it is first applying values and in the next editor pass is calling this skin-changing method.
+				if (skeletonRenderer is SkeletonAnimation)
+					((SkeletonAnimation) skeletonRenderer).Update(0f);
+				else if (skeletonRenderer is SkeletonMecanim)
+					((SkeletonMecanim) skeletonRenderer).Update();
+
 				skeletonRenderer.LateUpdate();
 				return true;
 			}

+ 22 - 11
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs

@@ -177,19 +177,19 @@ namespace Spine.Unity.Editor {
 			if (TargetAttribute.includeNone)
 				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
-			for (int i = 0; i < data.Slots.Count; i++) {
-				string name = data.Slots.Items[i].Name;
+			for (int slotIndex = 0; slotIndex < data.Slots.Count; slotIndex++) {
+				string name = data.Slots.Items[slotIndex].Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
 
 					if (targetAttribute.containsBoundingBoxes) {
-						int slotIndex = i;
-						var attachments = new List<Attachment>();
-						foreach (var skin in data.Skins)
-							skin.FindAttachmentsForSlot(slotIndex, attachments);
+						var skinEntries = new List<Skin.SkinEntry>();
+						foreach (var skin in data.Skins) {
+							skin.GetAttachments(slotIndex, skinEntries);
+						}
 
 						bool hasBoundingBox = false;
-						foreach (var attachment in attachments) {
-							var bbAttachment = attachment as BoundingBoxAttachment;
+						foreach (var entry in skinEntries) {
+							var bbAttachment = entry.Attachment as BoundingBoxAttachment;
 							if (bbAttachment != null) {
 								string menuLabel = bbAttachment.IsWeighted() ? name + " (!)" : name;
 								menu.AddItem(new GUIContent(menuLabel), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
@@ -470,10 +470,21 @@ namespace Spine.Unity.Editor {
 					attachmentNames.Clear();
 					placeholderNames.Clear();
 
-					skin.FindNamesForSlot(i, attachmentNames);
+					var skinEntries = new List<Skin.SkinEntry>();
+					skin.GetAttachments(i, skinEntries);
+					foreach (var entry in skinEntries) {
+						attachmentNames.Add(entry.Name);
+					}
+
 					if (skin != defaultSkin) {
-						defaultSkin.FindNamesForSlot(i, attachmentNames);
-						skin.FindNamesForSlot(i, placeholderNames);
+						foreach (var entry in skinEntries) {
+							placeholderNames.Add(entry.Name);
+						}
+						skinEntries.Clear();
+						defaultSkin.GetAttachments(i, skinEntries);
+						foreach (var entry in skinEntries) {
+							attachmentNames.Add(entry.Name);
+						}
 					}
 
 					for (int a = 0; a < attachmentNames.Count; a++) {

+ 4 - 3
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs

@@ -707,9 +707,10 @@ namespace Spine.Unity.Editor {
 				if (root == null || !root.ContainsKey("skins"))
 					return requiredPaths;
 
-				foreach (var skin in (Dictionary<string, object>)root["skins"]) {
-					foreach (var slot in (Dictionary<string, object>)skin.Value) {
-
+				foreach (Dictionary<string, object> skinMap in (List<object>)root["skins"]) {
+					if (!skinMap.ContainsKey("attachments"))
+						continue;
+					foreach (var slot in (Dictionary<string, object>)skinMap["attachments"]) {
 						foreach (var attachment in ((Dictionary<string, object>)slot.Value)) {
 							var data = ((Dictionary<string, object>)attachment.Value);
 

+ 7 - 1
spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs

@@ -81,7 +81,13 @@ namespace Spine.Unity.Editor {
 				Slot slot = skeletonUtility.skeletonRenderer.skeleton.Slots.Items[i];
 				if (slot.Bone == utilityBone.bone) {
 					var slotAttachments = new List<Attachment>();
-					skin.FindAttachmentsForSlot(skeleton.FindSlotIndex(slot.Data.Name), slotAttachments);
+					var skinEntries = new List<Skin.SkinEntry>();
+					int slotIndex = skeleton.FindSlotIndex(slot.Data.Name);
+					skin.GetAttachments(slotIndex, skinEntries);
+					foreach (var entry in skinEntries) {
+						slotAttachments.Add(entry.Attachment);
+					}
+
 					var boundingBoxes = new List<BoundingBoxAttachment>();
 					foreach (var att in slotAttachments) {
 						var boundingBoxAttachment = att as BoundingBoxAttachment;

+ 7 - 7
spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs

@@ -52,16 +52,16 @@ namespace Spine.Unity {
 			if (skeletonData == null) throw new ArgumentNullException("skeletonData");
 
 			using (var materialCache = new AtlasMaterialCache()) {
-				var attachmentBuffer = new List<Attachment>();
+				var entryBuffer = new List<Skin.SkinEntry>();
 				var slotsItems = skeletonData.Slots.Items;
-				for (int i = 0, slotCount = skeletonData.Slots.Count; i < slotCount; i++) {
-					var slot = slotsItems[i];
+				for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) {
+					var slot = slotsItems[slotIndex];
 					if (slot.blendMode == BlendMode.Normal) continue;
 					if (!includeAdditiveSlots && slot.blendMode == BlendMode.Additive) continue;
 
-					attachmentBuffer.Clear();
+					entryBuffer.Clear();
 					foreach (var skin in skeletonData.Skins)
-						skin.FindAttachmentsForSlot(i, attachmentBuffer);
+						skin.GetAttachments(slotIndex, entryBuffer);
 
 					Material templateMaterial = null;
 					switch (slot.blendMode) {
@@ -77,8 +77,8 @@ namespace Spine.Unity {
 					}
 					if (templateMaterial == null) continue;
 
-					foreach (var attachment in attachmentBuffer) {
-						var renderableAttachment = attachment as IHasRendererObject;
+					foreach (var entry in entryBuffer) {
+						var renderableAttachment = entry.Attachment as IHasRendererObject;
 						if (renderableAttachment != null) {
 							renderableAttachment.RendererObject = materialCache.CloneAtlasRegionWithMaterial((AtlasRegion)renderableAttachment.RendererObject, templateMaterial);
 						}

+ 18 - 16
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs

@@ -29,6 +29,7 @@
 
 using UnityEngine;
 using System.Collections.Generic;
+using System.Collections;
 
 namespace Spine.Unity.Modules.AttachmentTools {
 	public static class AttachmentRegionExtensions {
@@ -503,9 +504,10 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			var texturesToPack = new List<Texture2D>();
 			var originalRegions = new List<AtlasRegion>();
 			int newRegionIndex = 0;
-			foreach (var skinEntry in skinAttachments) {
-				var originalKey = skinEntry.Key;
-				var originalAttachment = skinEntry.Value;
+			
+			foreach (DictionaryEntry skinEntry in skinAttachments) {
+				var originalKey = (Skin.SkinEntry)skinEntry.Key;
+				var originalAttachment = (Attachment)skinEntry.Value;
 
 				Attachment newAttachment;
 				if (IsRenderable(originalAttachment)) {
@@ -523,9 +525,9 @@ namespace Spine.Unity.Modules.AttachmentTools {
 					}
 
 					repackedAttachments.Add(newAttachment);
-					newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, newAttachment);
+					newSkin.SetAttachment(originalKey.SlotIndex, originalKey.Name, newAttachment);
 				} else {
-					newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, useOriginalNonrenderables ? originalAttachment : originalAttachment.GetClone(true));
+					newSkin.SetAttachment(originalKey.SlotIndex, originalKey.Name, useOriginalNonrenderables ? originalAttachment : originalAttachment.GetClone(true));
 				}	
 			}
 
@@ -793,7 +795,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			var newSkin = new Skin(original.name + " clone");
 			var newSkinAttachments = newSkin.Attachments;
 
-			foreach (var a in original.Attachments)
+			foreach (DictionaryEntry a in original.Attachments)
 				newSkinAttachments[a.Key] = a.Value;
 
 			return newSkin;
@@ -804,7 +806,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			int slotIndex = skeleton.FindSlotIndex(slotName);
 			if (skeleton == null) throw new System.ArgumentNullException("skeleton", "skeleton cannot be null.");
 			if (slotIndex == -1) throw new System.ArgumentException(string.Format("Slot '{0}' does not exist in skeleton.", slotName), "slotName");
-			skin.AddAttachment(slotIndex, keyName, attachment);
+			skin.SetAttachment(slotIndex, keyName, attachment);
 		}
 
 		/// <summary>Adds skin items from another skin. For items that already exist, the previous values are replaced.</summary>
@@ -823,7 +825,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 
 		/// <summary>Adds an attachment to the skin for the specified slot index and name. If the name already exists for the slot, the previous value is replaced.</summary>
 		public static void SetAttachment (this Skin skin, int slotIndex, string keyName, Attachment attachment) {
-			skin.AddAttachment(slotIndex, keyName, attachment);
+			skin.SetAttachment(slotIndex, keyName, attachment);
 		}
 		
 		public static void RemoveAttachment (this Skin skin, string slotName, string keyName, SkeletonData skeletonData) {
@@ -848,21 +850,21 @@ namespace Spine.Unity.Modules.AttachmentTools {
 
 			if (cloneAttachments) {
 				if (overwrite) {
-					foreach (var e in sourceAttachments)
-						destinationAttachments[e.Key] = e.Value.GetClone(cloneMeshesAsLinked);
+					foreach (DictionaryEntry e in sourceAttachments)
+						destinationAttachments[e.Key] = ((Attachment)e.Value).GetClone(cloneMeshesAsLinked);
 				} else {
-					foreach (var e in sourceAttachments) {
-						if (destinationAttachments.ContainsKey(e.Key)) continue;
-						destinationAttachments.Add(e.Key, e.Value.GetClone(cloneMeshesAsLinked));
+					foreach (DictionaryEntry e in sourceAttachments) {
+						if (destinationAttachments.Contains(e.Key)) continue;
+						destinationAttachments.Add(e.Key, ((Attachment)e.Value).GetClone(cloneMeshesAsLinked));
 					}
 				}
 			} else {
 				if (overwrite) {
-					foreach (var e in sourceAttachments)
+					foreach (DictionaryEntry e in sourceAttachments)
 						destinationAttachments[e.Key] = e.Value;
 				} else {
-					foreach (var e in sourceAttachments) {
-						if (destinationAttachments.ContainsKey(e.Key)) continue;
+					foreach (DictionaryEntry e in sourceAttachments) {
+						if (destinationAttachments.Contains(e.Key)) continue;
 						destinationAttachments.Add(e.Key, e.Value);
 					}
 				}

+ 6 - 6
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/BoundingBoxFollower/BoundingBoxFollower.cs

@@ -139,11 +139,11 @@ namespace Spine.Unity {
 
 		void AddSkin (Skin skin, int slotIndex) {
 			if (skin == null) return;
-			var attachmentNames = new List<string>();
-			skin.FindNamesForSlot(slotIndex, attachmentNames);
-
-			foreach (var skinKey in attachmentNames) {
-				var attachment = skin.GetAttachment(slotIndex, skinKey);
+			var skinEntries = new List<Skin.SkinEntry>();
+			skin.GetAttachments(slotIndex, skinEntries);
+			
+			foreach (var entry in skinEntries) {
+				var attachment = skin.GetAttachment(slotIndex, entry.Name);
 				var boundingBoxAttachment = attachment as BoundingBoxAttachment;
 
 				if (BoundingBoxFollower.DebugMessages && attachment != null && boundingBoxAttachment == null)
@@ -157,7 +157,7 @@ namespace Spine.Unity {
 						bbCollider.hideFlags = HideFlags.NotEditable;
 						bbCollider.isTrigger = IsTrigger;
 						colliderTable.Add(boundingBoxAttachment, bbCollider);
-						nameTable.Add(boundingBoxAttachment, skinKey);
+						nameTable.Add(boundingBoxAttachment, entry.Name);
 					}
 				}
 			}

+ 6 - 5
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs

@@ -365,14 +365,15 @@ namespace Spine.Unity.Modules {
 			GameObject go = t.gameObject;
 			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
 
-			var attachments = new List<Attachment>();
+			var skinEntries = new List<Skin.SkinEntry>();
 			foreach (Slot s in skeleton.Slots) {
 				if (s.Bone == b) {
-					skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments);
-					foreach (var a in attachments) {
-						var bbAttachment = a as BoundingBoxAttachment;
+					skin.GetAttachments(skeleton.Slots.IndexOf(s), skinEntries);
+					
+					foreach (var entry in skinEntries) {
+						var bbAttachment = entry.Attachment as BoundingBoxAttachment;
 						if (bbAttachment != null) {
-							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
+							if (!entry.Name.ToLower().Contains(AttachmentNameMarker))
 								continue;
 
 							var bbCollider = go.AddComponent<BoxCollider>();

+ 5 - 5
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs

@@ -360,16 +360,16 @@ namespace Spine.Unity.Modules {
 			var colliders = new List<Collider2D>();
 			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
 
-			var attachments = new List<Attachment>();
+			var skinEntries = new List<Skin.SkinEntry>();
 			foreach (Slot slot in skeleton.Slots) {
 				if (slot.bone == b) {
-					skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(slot), attachments);
+					skin.GetAttachments(skeleton.Slots.IndexOf(slot), skinEntries);
 
 					bool bbAttachmentAdded = false;
-					foreach (var a in attachments) {
-						var bbAttachment = a as BoundingBoxAttachment;
+					foreach (var entry in skinEntries) {
+						var bbAttachment = entry.Attachment as BoundingBoxAttachment;
 						if (bbAttachment != null) {
-							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
+							if (!entry.Name.ToLower().Contains(AttachmentNameMarker))
 								continue;
 
 							bbAttachmentAdded = true;

+ 18 - 39
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/README.md

@@ -5,31 +5,37 @@ If this documentation contains mistakes or doesn't cover some questions, please
 
 ![](add-menu.png)  
 
-## Spine Animation Track
-Controls the skeleton pose a given Spine component with animations.
+## Spine AnimationState Track
+![](animationstate-clip-inspector.png)  
+Sets animations on the target SkeletonAnimation's AnimationState.
 
 **Status:**
-- Currently only SkeletonAnimation (via SkeletonAnimationPlayableHandle)
-- Mixing has some significant bugs. It should work fine if you don't include mixing, or if all your animations have perfectly matching dopesheet properties.
+- Currently only SkeletonAnimation (directly)
 
 **To use:**
 1. Add `SkeletonAnimationPlayableHandle` component to your SkeletonAnimation GameObject.
-2. With an existing Unity Playable Direction, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Animation Track**.
-3. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine Skeleton Flip Track.
-4. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Animation Clip Clip**.
+2. With an existing Unity Playable Director, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Animation State Track**.
+3. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine AnimationState Track.
+4. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Animation State Clip Clip**.
 5. Adjust the start and end times of the new clip, name it appropriately at the top of the Inspector.
 6. Click on the clip inspector's SkeletonDataAsset field and choose your target skeleton's SkeletonDataAsset. This will enable the animation name dropdown to appear.
 7. Choose the appropriate animation name, loop, and mix settings.
-8. For easier readability, rename your clip to the animation name or something descriptive. 
+- For easier readability, rename your clip to the animation name or something descriptive.
+- To avoid having to do steps 4-6 repeatedly, use the Duplicate function (`CTRL`/`CMD` + `D`)  
 
 **Track Behavior**
-- Currently buggy
-- 
-
+- `AnimationState.SetAnimation` will be called at the beginning of every clip based on the animationName.
+- Clip durations don't matter. Animations won't be cleared where there is no active clip at certain slices of time.
+- **EMPTY ANIMATION**: If a clip has no name specified, it will call SetEmptyAnimation instead.
+- **ERROR HANDLING**: If the animation with the provided animationName is not found, it will do nothing (the previous animation will continue playing normally).
+- Animations playing before the timeline starts playing will not be interrupted until the first clip starts playing.
+- At the end of the last clip and at the end of the timeline, nothing happens. This means the effect of the last clip's SetAnimation call will persist until you give other commands to that AnimationState.
+- If "custom duration" is unchecked, it will do a normal lookup of the AnimationState data's specified transition-pair mix setting, or the default mix.
+- Edit mode preview mixing may look different from Play Mode mixing. Please check in actual Play Mode to see the real results.
 
 ## Spine Skeleton Flip Track
 ![](skeleton-flip-clip-inspector.png)  
-Controls skeleton flip a given Spine component.
+Controls skeleton flip at a given Spine component.
 
 **Status:**
 - Currently only SkeletonAnimation (via SkeletonAnimationPlayableHandle)
@@ -45,33 +51,6 @@ Controls skeleton flip a given Spine component.
 - The specified skeleton flip values will be applied for every frame within the duration of each track.
 - At the end of the timeline, the track will revert the skeleton flip to the flip values it captures when it starts playing that timeline. 
 
-## Spine AnimationState Track
-![](animationstate-clip-inspector.png)  
-Sets Animations on the target SkeletonAnimation's AnimationState (via SetAnimation).
-
-**Status:**
-- Currently only SkeletonAnimation (directly)
-
-**To use:**
-1. With an existing Unity Playable Director, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Animation State Track**.
-2. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine AnimationState Track.
-3. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Animation State Clip Clip**.
-4. Adjust the start and end times of the new clip, name it appropriately at the top of the Inspector.
-5. Click on the clip inspector's SkeletonDataAsset field and choose your target skeleton's SkeletonDataAsset. This will enable the animation name dropdown to appear.
-6. Choose the appropriate animation name, loop, and mix settings.
-- For easier readability, rename your clip to the animation name or something descriptive.
-- To avoid having to do steps 4-6 repeatedly, use the Duplicate function (`CTRL`/`CMD` + `D`)  
-
-**Track Behavior**
-- `AnimationState.SetAnimation` will be called at the beginning of every clip based on the animationName.
-- Clip durations don't matter. Animations won't be cleared where there is no active clip at certain slices of time.
-- **EMPTY ANIMATION**: If a clip has no name specified, it will call SetEmptyAnimation instead.
-- **ERROR HANDLING**: If the animation with the provided animationName is not found, it will do nothing (the previous animation will continue playing normally).
-- Animations playing before the timeline starts playing will not be interrupted until the first clip starts playing.
-- At the end of the last clip and at the end of the timeline, nothing happens. This means the effect of the last clip's SetAnimation call will persist until you give other commands to that AnimationState.
-- If "custom duration" is unchecked, it will do a normal lookup of the AnimationState data's specified transition-pair mix setting, or the default mix.
-- Edit mode preview mixing may look different from Play Mode mixing. Please check in actual Play Mode to see the real results.
-
 ## Known Issues
 Spine Timeline support is currently experimental and has some known issues and inconveniences.
 - The Console logs an incorrect/harmless error `DrivenPropertyManager has failed to register property "m_Script" of object "Spine GameObject (spineboy-pro)" with driver "" because the property doesn't exist.`. This is a known issue on Unity's end. See more here: https://forum.unity.com/threads/default-playables-text-switcher-track-error.502903/

BIN
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/add-menu.png


BIN
spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png


+ 0 - 15
spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs

@@ -608,21 +608,6 @@ namespace Spine {
 		public static void AllowImmediateQueue (this TrackEntry trackEntry) {
 			if (trackEntry.nextTrackLast < 0) trackEntry.nextTrackLast = 0;
 		}
-
-		#endregion
-
-		#region Skins
-		/// <summary><see cref="Spine.Skin.FindNamesForSlot(int,List)"/></summary>
-		public static void FindNamesForSlot (this Skin skin, string slotName, SkeletonData skeletonData, List<string> results) {
-			int slotIndex = skeletonData.FindSlotIndex(slotName);
-			skin.FindNamesForSlot(slotIndex, results);
-		}
-
-		/// <summary><see cref="Spine.Skin.FindAttachmentsForSlot(int,List)"/></summary>
-		public static void FindAttachmentsForSlot (this Skin skin, string slotName, SkeletonData skeletonData, List<Attachment> results) {
-			int slotIndex = skeletonData.FindSlotIndex(slotName);
-			skin.FindAttachmentsForSlot(slotIndex, results);
-		}
 		#endregion
 	}
 }