Quellcode durchsuchen

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

badlogic vor 6 Jahren
Ursprung
Commit
a0c9c9bb0e
43 geänderte Dateien mit 626 neuen und 364 gelöschten Zeilen
  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>
 		/// <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; } }
 		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; } }
 		public string Name { get { return name; } }
 
 
 		/// <summary>Applies all the animation's timelines to the specified skeleton.</summary>
 		/// <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 () {
 		override public string ToString () {
 			return Name;
 			return Name;
 		}
 		}
+
+		///<summary>Returns a copy of the attachment.</summary>
+		public abstract Attachment Copy ();
 	}
 	}
 
 
 	public interface IHasRendererObject {
 	public interface IHasRendererObject {

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

@@ -35,5 +35,11 @@ namespace Spine {
 		public BoundingBoxAttachment (string name)
 		public BoundingBoxAttachment (string name)
 			: base(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 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) {
 		override public bool ApplyDeform (VertexAttachment sourceAttachment) {
 			return this == sourceAttachment || (inheritDeform && parentMesh == 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)
 		public PathAttachment (String name)
 			: base(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;
 			float iy = cos * bone.c + sin * bone.d;
 			return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg;
 			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;
 			worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy;
 			//offset += stride;
 			//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 {
 namespace Spine {
 	/// <summary>>An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's
 	/// <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> 
 	/// <see cref="Slot.Deform"/>.</summary> 
-	public class VertexAttachment : Attachment {
+	public abstract class VertexAttachment : Attachment {
 		static int nextID = 0;
 		static int nextID = 0;
 		static readonly Object nextIdLock = new Object();
 		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>
 		/// <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) {
 		virtual public bool ApplyDeform (VertexAttachment sourceAttachment) {
 			return this == 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 a, b, worldX;
 		internal float c, d, worldY;
 		internal float c, d, worldY;
 
 
-		internal bool sorted;
+		internal bool sorted, update;
 
 
 		public BoneData Data { get { return data; } }
 		public BoneData Data { get { return data; } }
 		public Skeleton Skeleton { get { return skeleton; } }
 		public Skeleton Skeleton { get { return skeleton; } }

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

@@ -37,11 +37,12 @@ namespace Spine {
 		internal float length;
 		internal float length;
 		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
 		internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
 		internal TransformMode transformMode = TransformMode.Normal;
 		internal TransformMode transformMode = TransformMode.Normal;
+		internal bool skinRequired;
 
 
 		/// <summary>The index of the bone in Skeleton.Bones</summary>
 		/// <summary>The index of the bone in Skeleton.Bones</summary>
 		public int Index { get { return index; } }
 		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; } }
 		public string Name { get { return name; } }
 
 
 		/// <summary>May be null.</summary>
 		/// <summary>May be null.</summary>
@@ -73,6 +74,11 @@ namespace Spine {
 		/// <summary>The transform mode for how parent world transforms affect this bone.</summary>
 		/// <summary>The transform mode for how parent world transforms affect this bone.</summary>
 		public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } }
 		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>
 		/// <param name="parent">May be null.</param>
 		public BoneData (int index, string name, BoneData parent) {
 		public BoneData (int index, string name, BoneData parent) {
 			if (index < 0) throw new ArgumentException("index must be >= 0", "index");
 			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.
  * 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 {
 	public class EventData {
 		internal string name;
 		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 string Name { get { return name; } }
 		public int Int { get; set; }
 		public int Int { get; set; }
 		public float Float { get; set; }
 		public float Float { get; set; }

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

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

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

@@ -32,23 +32,14 @@ using System.Collections.Generic;
 
 
 namespace Spine {
 namespace Spine {
 	/// <summary>Stores the setup pose for an IkConstraint.</summary>
 	/// <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 List<BoneData> bones = new List<BoneData>();
 		internal BoneData target;
 		internal BoneData target;
 		internal int bendDirection = 1;
 		internal int bendDirection = 1;
 		internal bool compress, stretch, uniform;
 		internal bool compress, stretch, uniform;
 		internal float mix = 1;
 		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>
 		/// <summary>The bones that are constrained by this IK Constraint.</summary>
@@ -98,14 +89,5 @@ namespace Spine {
 			get { return uniform; }
 			get { return uniform; }
 			set { uniform = value; }
 			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>
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para>
 	/// See <a href="http://esotericsoftware.com/spine-path-constraints">Path constraints</a> in the Spine User Guide.</para>
 	/// </summary>
 	/// </summary>
-	public class PathConstraint : IConstraint {
+	public class PathConstraint : IUpdatable {
 		const int NONE = -1, BEFORE = -2, AFTER = -3;
 		const int NONE = -1, BEFORE = -2, AFTER = -3;
 		const float Epsilon = 0.00001f;
 		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>
 		/// <summary>The position along the path.</summary>
 		public float Position { get { return position; } set { position = value; } }
 		public float Position { get { return position; } set { position = value; } }
 		/// <summary>The spacing between bones.</summary>
 		/// <summary>The spacing between bones.</summary>
@@ -461,9 +460,5 @@ namespace Spine {
 		public Slot Target { get { return target; } set { target = value; } }
 		public Slot Target { get { return target; } set { target = value; } }
 		/// <summary>The path constraint's setup pose data.</summary>
 		/// <summary>The path constraint's setup pose data.</summary>
 		public PathConstraintData Data { get { return data; } }
 		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;
 using System;
 
 
 namespace Spine {
 namespace Spine {
-	public class PathConstraintData {
-		internal string name;
-		internal int order;
+	public class PathConstraintData : ConstraintData {
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal SlotData target;
 		internal SlotData target;
 		internal PositionMode positionMode;
 		internal PositionMode positionMode;
@@ -41,8 +39,9 @@ namespace Spine {
 		internal float offsetRotation;
 		internal float offsetRotation;
 		internal float position, spacing, rotateMix, translateMix;
 		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 ExposedList<BoneData> Bones { get { return bones; } }
 		public SlotData Target { get { return target; } set { target = value; } }			
 		public SlotData Target { get { return target; } set { target = value; } }			
 		public PositionMode PositionMode { get { return positionMode; } set { positionMode = 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 Spacing { get { return spacing; } set { spacing = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = value; } }
 		public float TranslateMix { get { return translateMix; } set { translateMix = 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 {
 	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<IkConstraint> IkConstraints { get { return ikConstraints; } }
 		public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
 		public ExposedList<PathConstraint> PathConstraints { get { return pathConstraints; } }
 		public ExposedList<TransformConstraint> TransformConstraints { get { return transformConstraints; } }
 		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 R { get { return r; } set { r = value; } }
 		public float G { get { return g; } set { g = value; } }
 		public float G { get { return g; } set { g = value; } }
 		public float B { get { return b; } set { b = value; } }
 		public float B { get { return b; } set { b = value; } }
@@ -118,21 +118,25 @@ namespace Spine {
 			UpdateWorldTransform();
 			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 () {
 		public void UpdateCache () {
 			var updateCache = this.updateCache;
 			var updateCache = this.updateCache;
 			updateCache.Clear();
 			updateCache.Clear();
 			this.updateCacheReset.Clear();
 			this.updateCacheReset.Clear();
 
 
+			int boneCount = this.bones.Items.Length;
 			var bones = this.bones;
 			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 ikConstraints = this.ikConstraints;
 			var transformConstraints = this.transformConstraints;
 			var transformConstraints = this.transformConstraints;
 			var pathConstraints = this.pathConstraints;
 			var pathConstraints = this.pathConstraints;
-			int ikCount = ikConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count;
 			int constraintCount = ikCount + transformCount + pathCount;
 			int constraintCount = ikCount + transformCount + pathCount;
 			//outer:
 			//outer:
 			for (int i = 0; i < constraintCount; i++) {
 			for (int i = 0; i < constraintCount; i++) {
@@ -160,11 +164,13 @@ namespace Spine {
 				continue_outer: {}
 				continue_outer: {}
 			}
 			}
 
 
-			for (int i = 0, n = bones.Count; i < n; i++)
+			for (int i = 0; i < boneCount; i++)
 				SortBone(bones.Items[i]);
 				SortBone(bones.Items[i]);
 		}
 		}
 
 
 		private void SortIkConstraint (IkConstraint constraint) {
 		private void SortIkConstraint (IkConstraint constraint) {
+			if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return;
+
 			Bone target = constraint.target;
 			Bone target = constraint.target;
 			SortBone(target);
 			SortBone(target);
 
 
@@ -185,15 +191,15 @@ namespace Spine {
 		}
 		}
 
 
 		private void SortPathConstraint (PathConstraint constraint) {
 		private void SortPathConstraint (PathConstraint constraint) {
+			if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return;
+
 			Slot slot = constraint.target;
 			Slot slot = constraint.target;
 			int slotIndex = slot.data.index;
 			int slotIndex = slot.data.index;
 			Bone slotBone = slot.bone;
 			Bone slotBone = slot.bone;
 			if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
 			if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone);
 			if (data.defaultSkin != null && data.defaultSkin != skin)
 			if (data.defaultSkin != null && data.defaultSkin != skin)
 				SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone);
 				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;
 			Attachment attachment = slot.attachment;
 			if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
 			if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone);
 
 
@@ -211,6 +217,8 @@ namespace Spine {
 		}
 		}
 
 
 		private void SortTransformConstraint (TransformConstraint constraint) {
 		private void SortTransformConstraint (TransformConstraint constraint) {
+			if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return;
+
 			SortBone(constraint.target);
 			SortBone(constraint.target);
 
 
 			var constrained = constraint.bones;
 			var constrained = constraint.bones;
@@ -235,8 +243,10 @@ namespace Spine {
 		}
 		}
 
 
 		private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) {
 		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) {
 		private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) {
@@ -267,6 +277,7 @@ namespace Spine {
 			var bonesItems = bones.Items;
 			var bonesItems = bones.Items;
 			for (int i = 0, n = bones.Count; i < n; i++) {
 			for (int i = 0, n = bones.Count; i < n; i++) {
 				Bone bone = bonesItems[i];
 				Bone bone = bonesItems[i];
+				if (!bone.update) continue;
 				if (bone.sorted) SortReset(bone.children);
 				if (bone.sorted) SortReset(bone.children);
 				bone.sorted = false;
 				bone.sorted = false;
 			}
 			}
@@ -293,7 +304,8 @@ namespace Spine {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 	 	/// </summary>
 		public void UpdateWorldTransform (Bone parent) {
 		public void UpdateWorldTransform (Bone parent) {
 			// This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated
 			// 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;
 				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;
 			Bone rootBone = this.RootBone;
 			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
 			float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d;
 			rootBone.worldX = pa * x + pb * y + parent.worldX;
 			rootBone.worldX = pa * x + pb * y + parent.worldX;
@@ -445,8 +456,12 @@ namespace Spine {
 		}
 		}
 
 
 		/// <summary>
 		/// <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 
 		/// <para>After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling 
 		/// <see cref="Skeleton.SetSlotsToSetupPose()"/>. 
 		/// <see cref="Skeleton.SetSlotsToSetupPose()"/>. 
 		/// Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the 
 		/// Also, often <see cref="AnimationState.Apply(Skeleton)"/> is called before the next time the 
@@ -454,6 +469,7 @@ namespace Spine {
 		/// </summary>
 		/// </summary>
 		/// <param name="newSkin">May be null.</param>
 		/// <param name="newSkin">May be null.</param>
 		public void SetSkin (Skin newSkin) {
 		public void SetSkin (Skin newSkin) {
+			if (newSkin == skin) return;
 			if (newSkin != null) {
 			if (newSkin != null) {
 				if (skin != null)
 				if (skin != null)
 					newSkin.AttachAll(this, skin);
 					newSkin.AttachAll(this, skin);
@@ -470,6 +486,7 @@ namespace Spine {
 				}
 				}
 			}
 			}
 			skin = newSkin;
 			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>
 		/// <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;
 			ExposedList<TransformConstraint> transformConstraints = this.transformConstraints;
 			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
 			for (int i = 0, n = transformConstraints.Count; i < n; i++) {
 				TransformConstraint transformConstraint = transformConstraints.Items[i];
 				TransformConstraint transformConstraint = transformConstraints.Items[i];
-				if (transformConstraint.data.name == constraintName) return transformConstraint;
+				if (transformConstraint.data.Name == constraintName) return transformConstraint;
 			}
 			}
 			return null;
 			return null;
 		}
 		}
@@ -537,7 +554,7 @@ namespace Spine {
 			ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
 			ExposedList<PathConstraint> pathConstraints = this.pathConstraints;
 			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
 			for (int i = 0, n = pathConstraints.Count; i < n; i++) {
 				PathConstraint constraint = pathConstraints.Items[i];
 				PathConstraint constraint = pathConstraints.Items[i];
-				if (constraint.data.name.Equals(constraintName)) return constraint;
+				if (constraint.data.Name.Equals(constraintName)) return constraint;
 			}
 			}
 			return null;
 			return null;
 		}
 		}

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

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

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

@@ -109,27 +109,30 @@ namespace Spine {
 			}
 			}
 
 
 			// Bones.
 			// 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.
 			// Slots.
@@ -171,16 +174,19 @@ namespace Spine {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["ik"]) {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["ik"]) {
 					IkConstraintData data = new IkConstraintData((string)constraintMap["name"]);
 					IkConstraintData data = new IkConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
 					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"];
 					string targetName = (string)constraintMap["target"];
 					data.target = skeletonData.FindBone(targetName);
 					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.mix = GetFloat(constraintMap, "mix", 1);
 					data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
 					data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1;
 					data.compress = GetBoolean(constraintMap, "compress", false);
 					data.compress = GetBoolean(constraintMap, "compress", false);
@@ -196,16 +202,19 @@ namespace Spine {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["transform"]) {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["transform"]) {
 					TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]);
 					TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
 					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"];
 					string targetName = (string)constraintMap["target"];
 					data.target = skeletonData.FindBone(targetName);
 					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.local = GetBoolean(constraintMap, "local", false);
 					data.relative = GetBoolean(constraintMap, "relative", false);
 					data.relative = GetBoolean(constraintMap, "relative", false);
@@ -231,16 +240,19 @@ namespace Spine {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["path"]) {
 				foreach (Dictionary<string, Object> constraintMap in (List<Object>)root["path"]) {
 					PathConstraintData data = new PathConstraintData((string)constraintMap["name"]);
 					PathConstraintData data = new PathConstraintData((string)constraintMap["name"]);
 					data.order = GetInt(constraintMap, "order", 0);
 					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"];
 					string targetName = (string)constraintMap["target"];
 					data.target = skeletonData.FindSlot(targetName);
 					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.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true);
 					data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
 					data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true);
@@ -259,18 +271,48 @@ namespace Spine {
 
 
 			// Skins.
 			// Skins.
 			if (root.ContainsKey("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);
 					skeletonData.skins.Add(skin);
 					if (skin.name == "default") skeletonData.defaultSkin = skin;
 					if (skin.name == "default") skeletonData.defaultSkin = skin;
@@ -494,7 +536,7 @@ namespace Spine {
 
 
 							int frameIndex = 0;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
 							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"]);
 								timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]);
 							}
 							}
 							timelines.Add(timeline);
 							timelines.Add(timeline);
@@ -506,7 +548,7 @@ namespace Spine {
 
 
 							int frameIndex = 0;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
 							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
+								float time = GetFloat(valueMap, "time", 0);
 								string c = (string)valueMap["color"];
 								string c = (string)valueMap["color"];
 								timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
 								timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3));
 								ReadCurve(valueMap, timeline, frameIndex);
 								ReadCurve(valueMap, timeline, frameIndex);
@@ -521,7 +563,7 @@ namespace Spine {
 
 
 							int frameIndex = 0;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
 							foreach (Dictionary<string, Object> valueMap in values) {
-								float time = (float)valueMap["time"];
+								float time = GetFloat(valueMap, "time", 0);
 								string light = (string)valueMap["light"];
 								string light = (string)valueMap["light"];
 								string dark = (string)valueMap["dark"];
 								string dark = (string)valueMap["dark"];
 								timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3),
 								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;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
 							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);
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 								frameIndex++;
 							}
 							}
@@ -563,9 +605,11 @@ namespace Spine {
 
 
 						} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
 						} else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") {
 							TranslateTimeline timeline;
 							TranslateTimeline timeline;
-							float timelineScale = 1;
-							if (timelineName == "scale")
+							float timelineScale = 1, defaultValue = 0;
+							if (timelineName == "scale") {
 								timeline = new ScaleTimeline(values.Count);
 								timeline = new ScaleTimeline(values.Count);
+								defaultValue = 1;
+							}
 							else if (timelineName == "shear")
 							else if (timelineName == "shear")
 								timeline = new ShearTimeline(values.Count);
 								timeline = new ShearTimeline(values.Count);
 							else {
 							else {
@@ -576,9 +620,9 @@ namespace Spine {
 
 
 							int frameIndex = 0;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
 							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);
 								timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale);
 								ReadCurve(valueMap, timeline, frameIndex);
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 								frameIndex++;
@@ -603,7 +647,7 @@ namespace Spine {
 					foreach (Dictionary<string, Object> valueMap in values) {
 					foreach (Dictionary<string, Object> valueMap in values) {
 						timeline.SetFrame(
 						timeline.SetFrame(
 							frameIndex,
 							frameIndex,
-							(float)valueMap["time"],
+							GetFloat(valueMap, "time", 0),
 							GetFloat(valueMap, "mix", 1),
 							GetFloat(valueMap, "mix", 1),
 							GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
 							GetBoolean(valueMap, "bendPositive", true) ? 1 : -1,
 							GetBoolean(valueMap, "compress", true),
 							GetBoolean(valueMap, "compress", true),
@@ -626,7 +670,7 @@ namespace Spine {
 					timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
 					timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint);
 					int frameIndex = 0;
 					int frameIndex = 0;
 					foreach (Dictionary<string, Object> valueMap in values) {
 					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 rotateMix = GetFloat(valueMap, "rotateMix", 1);
 						float translateMix = GetFloat(valueMap, "translateMix", 1);
 						float translateMix = GetFloat(valueMap, "translateMix", 1);
 						float scaleMix = GetFloat(valueMap, "scaleMix", 1);
 						float scaleMix = GetFloat(valueMap, "scaleMix", 1);
@@ -641,8 +685,8 @@ namespace Spine {
 			}
 			}
 
 
 			// Path constraint timelines.
 			// 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);
 					int index = skeletonData.FindPathConstraintIndex(constraintMap.Key);
 					if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
 					if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key);
 					PathConstraintData data = skeletonData.pathConstraints.Items[index];
 					PathConstraintData data = skeletonData.pathConstraints.Items[index];
@@ -664,7 +708,7 @@ namespace Spine {
 							timeline.pathConstraintIndex = index;
 							timeline.pathConstraintIndex = index;
 							int frameIndex = 0;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
 							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);
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 								frameIndex++;
 							}
 							}
@@ -676,7 +720,7 @@ namespace Spine {
 							timeline.pathConstraintIndex = index;
 							timeline.pathConstraintIndex = index;
 							int frameIndex = 0;
 							int frameIndex = 0;
 							foreach (Dictionary<string, Object> valueMap in values) {
 							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);
 								ReadCurve(valueMap, timeline, frameIndex);
 								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);
 								ReadCurve(valueMap, timeline, frameIndex);
 								frameIndex++;
 								frameIndex++;
 							}
 							}
@@ -770,7 +814,7 @@ namespace Spine {
 						for (int i = slotCount - 1; i >= 0; i--)
 						for (int i = slotCount - 1; i >= 0; i--)
 							if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex];
 							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);
 				timelines.Add(timeline);
 				duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
 				duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]);
@@ -784,7 +828,7 @@ namespace Spine {
 				foreach (Dictionary<string, Object> eventMap in eventsMap) {
 				foreach (Dictionary<string, Object> eventMap in eventsMap) {
 					EventData eventData = skeletonData.FindEvent((string)eventMap["name"]);
 					EventData eventData = skeletonData.FindEvent((string)eventMap["name"]);
 					if (eventData == null) throw new Exception("Event not found: " + 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),
 						intValue = GetInt(eventMap, "int", eventData.Int),
 						floatValue = GetFloat(eventMap, "float", eventData.Float),
 						floatValue = GetFloat(eventMap, "float", eventData.Float),
 						stringValue = GetString(eventMap, "string", eventData.String)
 						stringValue = GetString(eventMap, "string", eventData.String)
@@ -807,12 +851,12 @@ namespace Spine {
 			if (!valueMap.ContainsKey("curve"))
 			if (!valueMap.ContainsKey("curve"))
 				return;
 				return;
 			Object curveObject = valueMap["curve"];
 			Object curveObject = valueMap["curve"];
-			if (curveObject.Equals("stepped"))
+			if (curveObject is string)
 				timeline.SetStepped(frameIndex);
 				timeline.SetStepped(frameIndex);
 			else {
 			else {
 				var curve = curveObject as List<Object>;
 				var curve = curveObject as List<Object>;
 				if (curve != null)
 				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;
+using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Collections.Specialized;
 
 
 namespace Spine {
 namespace Spine {
 	/// <summary>Stores attachments by slot index and attachment name.
 	/// <summary>Stores attachments by slot index and attachment name.
@@ -37,53 +39,91 @@ namespace Spine {
 	/// </summary>
 	/// </summary>
 	public class Skin {
 	public class Skin {
 		internal string name;
 		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 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) {
 		public Skin (string name) {
 			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			if (name == null) throw new ArgumentNullException("name", "name cannot be null.");
 			this.name = name;
 			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.");
 			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>
 		/// <summary>Returns the attachment for the specified slot index and name, or null.</summary>
 		/// <returns>May be null.</returns>
 		/// <returns>May be null.</returns>
 		public Attachment GetAttachment (int slotIndex, string name) {
 		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>
 		/// <summary> Removes the attachment in the skin for the specified slot index and name, if any.</summary>
 		public void RemoveAttachment (int slotIndex, string name) {
 		public void RemoveAttachment (int slotIndex, string name) {
 			if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0");
 			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="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 () {
 		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>
 		/// <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) {
 		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];
 				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;
 					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.slotIndex = slotIndex;
 				this.name = name;
 				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 string attachmentName;
 		internal BlendMode blendMode;
 		internal BlendMode blendMode;
 
 
+		/// <summary>The index of the slot in <see cref="Skeleton.Slots"/>.</summary>
 		public int Index { get { return index; } }
 		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; } }
 		public string Name { get { return name; } }
+		/// <summary>The bone this slot belongs to.</summary>
 		public BoneData BoneData { get { return boneData; } }
 		public BoneData BoneData { get { return boneData; } }
 		public float R { get { return r; } set { r = value; } }
 		public float R { get { return r; } set { r = value; } }
 		public float G { get { return g; } set { g = 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 float B2 { get { return b2; } set { b2 = value; } }
 		public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = 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; } }
 		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 BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } }
 
 
 		public SlotData (int index, String name, BoneData boneData) {
 		public SlotData (int index, String name, BoneData boneData) {

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

@@ -37,7 +37,7 @@ namespace Spine {
 	/// <para>
 	/// <para>
 	/// See <a href="http://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide.</para>
 	/// See <a href="http://esotericsoftware.com/spine-transform-constraints">Transform constraints</a> in the Spine User Guide.</para>
 	/// </summary>
 	/// </summary>
-	public class TransformConstraint : IConstraint {
+	public class TransformConstraint : IUpdatable {
 		internal TransformConstraintData data;
 		internal TransformConstraintData data;
 		internal ExposedList<Bone> bones;
 		internal ExposedList<Bone> bones;
 		internal Bone target;
 		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>
 		/// <summary>The bones that will be modified by this transform constraint.</summary>
 		public ExposedList<Bone> Bones { get { return bones; } }
 		public ExposedList<Bone> Bones { get { return bones; } }
 		/// <summary>The target bone whose world transform will be copied to the constrained bones.</summary>
 		/// <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;
 using System;
 
 
 namespace Spine {
 namespace Spine {
-	public class TransformConstraintData {
-		internal string name;
-		internal int order;
+	public class TransformConstraintData : ConstraintData {
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal ExposedList<BoneData> bones = new ExposedList<BoneData>();
 		internal BoneData target;
 		internal BoneData target;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
 		internal float rotateMix, translateMix, scaleMix, shearMix;
 		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 		internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
 		internal bool relative, local;
 		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 ExposedList<BoneData> Bones { get { return bones; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public BoneData Target { get { return target; } set { target = value; } }
 		public float RotateMix { get { return rotateMix; } set { rotateMix = 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 Relative { get { return relative; } set { relative = value; } }
 		public bool Local { get { return local; } set { local = 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) {
 		public void Equip (int slotIndex, string attachmentName, Attachment attachment) {
-			equipsSkin.AddAttachment(slotIndex, attachmentName, attachment);
+			equipsSkin.SetAttachment(slotIndex, attachmentName, attachment);
 			skeletonAnimation.Skeleton.SetSkin(equipsSkin);
 			skeletonAnimation.Skeleton.SetSkin(equipsSkin);
 			RefreshSkeletonAttachments();
 			RefreshSkeletonAttachments();
 		}
 		}

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

@@ -170,7 +170,7 @@ namespace Spine.Unity.Examples {
 			if (skinName != "")
 			if (skinName != "")
 				skin = skeletonData.FindSkin(skinName);
 				skin = skeletonData.FindSkin(skinName);
 
 
-			skin.AddAttachment(slotIndex, att.Name, att);
+			skin.SetAttachment(slotIndex, att.Name, att);
 
 
 			return 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.
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
  *****************************************************************************/
 
 
+using System.Collections;
 using UnityEditor;
 using UnityEditor;
 using UnityEngine;
 using UnityEngine;
 
 
@@ -110,11 +111,11 @@ namespace Spine.Unity.Editor {
 		}
 		}
 
 
 		static void DrawPointsInSkin (Skin skin, Skeleton skeleton, Transform transform) {
 		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;
 				var attachment = skinEntry.Value as PointAttachment;
 				if (attachment != null) {
 				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);
 					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>();
 					List<string> attachmentNames = new List<string>();
 					for (int i = 0; i < skinCount; i++) {
 					for (int i = 0; i < skinCount; i++) {
 						var skin = skins.Items[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);
 					slotLookup.Add(s, attachmentNames);
@@ -333,8 +333,8 @@ namespace Spine.Unity.Editor {
 				}
 				}
 
 
 				//create slots and attachments
 				//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;
 					Transform slotTransform = SpineEditorUtilities.EditorInstantiation.NewGameObject(slotData.Name).transform;
 					slotTransform.parent = prefabRoot.transform;
 					slotTransform.parent = prefabRoot.transform;
 					slotTable.Add(slotData.Name, slotTransform);
 					slotTable.Add(slotData.Name, slotTransform);
@@ -342,12 +342,20 @@ namespace Spine.Unity.Editor {
 					List<Attachment> attachments = new List<Attachment>();
 					List<Attachment> attachments = new List<Attachment>();
 					List<string> attachmentNames = new List<string>();
 					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) {
 					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++) {
 					for (int a = 0; a < attachments.Count; a++) {
@@ -380,7 +388,7 @@ namespace Spine.Unity.Editor {
 							rotation = 0;
 							rotation = 0;
 
 
 							if (isWeightedMesh)
 							if (isWeightedMesh)
-								mesh = ExtractWeightedMeshAttachment(attachmentMeshName, meshAttachment, i, skeletonData, boneList, mesh);
+								mesh = ExtractWeightedMeshAttachment(attachmentMeshName, meshAttachment, slotIndex, skeletonData, boneList, mesh);
 							else
 							else
 								mesh = ExtractMeshAttachment(attachmentMeshName, meshAttachment, mesh);
 								mesh = ExtractMeshAttachment(attachmentMeshName, meshAttachment, mesh);
 							
 							
@@ -410,7 +418,7 @@ namespace Spine.Unity.Editor {
 						}
 						}
 
 
 						attachmentTransform.GetComponent<Renderer>().sharedMaterial = material;
 						attachmentTransform.GetComponent<Renderer>().sharedMaterial = material;
-						attachmentTransform.GetComponent<Renderer>().sortingOrder = i;
+						attachmentTransform.GetComponent<Renderer>().sortingOrder = slotIndex;
 
 
 						if (attachmentName != slotData.AttachmentName)
 						if (attachmentName != slotData.AttachmentName)
 							attachmentTransform.gameObject.SetActive(false);
 							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()) {
 						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) {
 								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 {
 								} 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--) {
 			for (int i = skeleton.Slots.Count - 1; i >= 0; i--) {
 				var attachments = new List<Attachment>();
 				var attachments = new List<Attachment>();
 				attachmentTable.Add(skeleton.Slots.Items[i], attachments);
 				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;
 			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 () {
 		override public void OnInspectorGUI () {
 			bool multi = serializedObject.isEditingMultipleObjects;
 			bool multi = serializedObject.isEditingMultipleObjects;
 			DrawInspectorGUI(multi);
 			DrawInspectorGUI(multi);
+			HandleSkinChange();
 			if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current) ||
 			if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current) ||
 				AreAnyMaskMaterialsMissing()) {
 				AreAnyMaskMaterialsMissing()) {
 				if (!Application.isPlaying) {
 				if (!Application.isPlaying) {
@@ -174,21 +175,6 @@ namespace Spine.Unity.Editor {
 					SceneView.RepaintAll();
 					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) {
 		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;
 			if (!skeletonRenderer.valid) return false;
 
 
 			var skin = skeletonRenderer.Skeleton.Skin;
 			var skin = skeletonRenderer.Skeleton.Skin;
 			string skeletonSkinName = skin != null ? skin.Name : null;
 			string skeletonSkinName = skin != null ? skin.Name : null;
-			string componentSkinName = skeletonRenderer.initialSkinName;
 			bool defaultCase = skin == null && string.IsNullOrEmpty(componentSkinName);
 			bool defaultCase = skin == null && string.IsNullOrEmpty(componentSkinName);
 			bool fieldMatchesSkin = defaultCase || string.Equals(componentSkinName, skeletonSkinName, System.StringComparison.Ordinal);
 			bool fieldMatchesSkin = defaultCase || string.Equals(componentSkinName, skeletonSkinName, System.StringComparison.Ordinal);
 
 
 			if (!fieldMatchesSkin) {
 			if (!fieldMatchesSkin) {
 				Skin skinToSet = string.IsNullOrEmpty(componentSkinName) ? null : skeletonRenderer.Skeleton.Data.FindSkin(componentSkinName);
 				Skin skinToSet = string.IsNullOrEmpty(componentSkinName) ? null : skeletonRenderer.Skeleton.Data.FindSkin(componentSkinName);
-				skeletonRenderer.Skeleton.Skin = skinToSet;
+				skeletonRenderer.Skeleton.SetSkin(skinToSet);
 				skeletonRenderer.Skeleton.SetSlotsToSetupPose();
 				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();
 				skeletonRenderer.LateUpdate();
 				return true;
 				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)
 			if (TargetAttribute.includeNone)
 				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 				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 (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
 
 
 					if (targetAttribute.containsBoundingBoxes) {
 					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;
 						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) {
 							if (bbAttachment != null) {
 								string menuLabel = bbAttachment.IsWeighted() ? name + " (!)" : name;
 								string menuLabel = bbAttachment.IsWeighted() ? name + " (!)" : name;
 								menu.AddItem(new GUIContent(menuLabel), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 								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();
 					attachmentNames.Clear();
 					placeholderNames.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) {
 					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++) {
 					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"))
 				if (root == null || !root.ContainsKey("skins"))
 					return requiredPaths;
 					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)) {
 						foreach (var attachment in ((Dictionary<string, object>)slot.Value)) {
 							var data = ((Dictionary<string, object>)attachment.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];
 				Slot slot = skeletonUtility.skeletonRenderer.skeleton.Slots.Items[i];
 				if (slot.Bone == utilityBone.bone) {
 				if (slot.Bone == utilityBone.bone) {
 					var slotAttachments = new List<Attachment>();
 					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>();
 					var boundingBoxes = new List<BoundingBoxAttachment>();
 					foreach (var att in slotAttachments) {
 					foreach (var att in slotAttachments) {
 						var boundingBoxAttachment = att as BoundingBoxAttachment;
 						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");
 			if (skeletonData == null) throw new ArgumentNullException("skeletonData");
 
 
 			using (var materialCache = new AtlasMaterialCache()) {
 			using (var materialCache = new AtlasMaterialCache()) {
-				var attachmentBuffer = new List<Attachment>();
+				var entryBuffer = new List<Skin.SkinEntry>();
 				var slotsItems = skeletonData.Slots.Items;
 				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 (slot.blendMode == BlendMode.Normal) continue;
 					if (!includeAdditiveSlots && slot.blendMode == BlendMode.Additive) continue;
 					if (!includeAdditiveSlots && slot.blendMode == BlendMode.Additive) continue;
 
 
-					attachmentBuffer.Clear();
+					entryBuffer.Clear();
 					foreach (var skin in skeletonData.Skins)
 					foreach (var skin in skeletonData.Skins)
-						skin.FindAttachmentsForSlot(i, attachmentBuffer);
+						skin.GetAttachments(slotIndex, entryBuffer);
 
 
 					Material templateMaterial = null;
 					Material templateMaterial = null;
 					switch (slot.blendMode) {
 					switch (slot.blendMode) {
@@ -77,8 +77,8 @@ namespace Spine.Unity {
 					}
 					}
 					if (templateMaterial == null) continue;
 					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) {
 						if (renderableAttachment != null) {
 							renderableAttachment.RendererObject = materialCache.CloneAtlasRegionWithMaterial((AtlasRegion)renderableAttachment.RendererObject, templateMaterial);
 							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 UnityEngine;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Collections;
 
 
 namespace Spine.Unity.Modules.AttachmentTools {
 namespace Spine.Unity.Modules.AttachmentTools {
 	public static class AttachmentRegionExtensions {
 	public static class AttachmentRegionExtensions {
@@ -503,9 +504,10 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			var texturesToPack = new List<Texture2D>();
 			var texturesToPack = new List<Texture2D>();
 			var originalRegions = new List<AtlasRegion>();
 			var originalRegions = new List<AtlasRegion>();
 			int newRegionIndex = 0;
 			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;
 				Attachment newAttachment;
 				if (IsRenderable(originalAttachment)) {
 				if (IsRenderable(originalAttachment)) {
@@ -523,9 +525,9 @@ namespace Spine.Unity.Modules.AttachmentTools {
 					}
 					}
 
 
 					repackedAttachments.Add(newAttachment);
 					repackedAttachments.Add(newAttachment);
-					newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, newAttachment);
+					newSkin.SetAttachment(originalKey.SlotIndex, originalKey.Name, newAttachment);
 				} else {
 				} 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 newSkin = new Skin(original.name + " clone");
 			var newSkinAttachments = newSkin.Attachments;
 			var newSkinAttachments = newSkin.Attachments;
 
 
-			foreach (var a in original.Attachments)
+			foreach (DictionaryEntry a in original.Attachments)
 				newSkinAttachments[a.Key] = a.Value;
 				newSkinAttachments[a.Key] = a.Value;
 
 
 			return newSkin;
 			return newSkin;
@@ -804,7 +806,7 @@ namespace Spine.Unity.Modules.AttachmentTools {
 			int slotIndex = skeleton.FindSlotIndex(slotName);
 			int slotIndex = skeleton.FindSlotIndex(slotName);
 			if (skeleton == null) throw new System.ArgumentNullException("skeleton", "skeleton cannot be null.");
 			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");
 			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>
 		/// <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>
 		/// <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) {
 		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) {
 		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 (cloneAttachments) {
 				if (overwrite) {
 				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 {
 				} 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 {
 			} else {
 				if (overwrite) {
 				if (overwrite) {
-					foreach (var e in sourceAttachments)
+					foreach (DictionaryEntry e in sourceAttachments)
 						destinationAttachments[e.Key] = e.Value;
 						destinationAttachments[e.Key] = e.Value;
 				} else {
 				} 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);
 						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) {
 		void AddSkin (Skin skin, int slotIndex) {
 			if (skin == null) return;
 			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;
 				var boundingBoxAttachment = attachment as BoundingBoxAttachment;
 
 
 				if (BoundingBoxFollower.DebugMessages && attachment != null && boundingBoxAttachment == null)
 				if (BoundingBoxFollower.DebugMessages && attachment != null && boundingBoxAttachment == null)
@@ -157,7 +157,7 @@ namespace Spine.Unity {
 						bbCollider.hideFlags = HideFlags.NotEditable;
 						bbCollider.hideFlags = HideFlags.NotEditable;
 						bbCollider.isTrigger = IsTrigger;
 						bbCollider.isTrigger = IsTrigger;
 						colliderTable.Add(boundingBoxAttachment, bbCollider);
 						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;
 			GameObject go = t.gameObject;
 			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
 			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
 
 
-			var attachments = new List<Attachment>();
+			var skinEntries = new List<Skin.SkinEntry>();
 			foreach (Slot s in skeleton.Slots) {
 			foreach (Slot s in skeleton.Slots) {
 				if (s.Bone == b) {
 				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 (bbAttachment != null) {
-							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
+							if (!entry.Name.ToLower().Contains(AttachmentNameMarker))
 								continue;
 								continue;
 
 
 							var bbCollider = go.AddComponent<BoxCollider>();
 							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 colliders = new List<Collider2D>();
 			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
 			var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin;
 
 
-			var attachments = new List<Attachment>();
+			var skinEntries = new List<Skin.SkinEntry>();
 			foreach (Slot slot in skeleton.Slots) {
 			foreach (Slot slot in skeleton.Slots) {
 				if (slot.bone == b) {
 				if (slot.bone == b) {
-					skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(slot), attachments);
+					skin.GetAttachments(skeleton.Slots.IndexOf(slot), skinEntries);
 
 
 					bool bbAttachmentAdded = false;
 					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 (bbAttachment != null) {
-							if (!a.Name.ToLower().Contains(AttachmentNameMarker))
+							if (!entry.Name.ToLower().Contains(AttachmentNameMarker))
 								continue;
 								continue;
 
 
 							bbAttachmentAdded = true;
 							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)  
 ![](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:**
 **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:**
 **To use:**
 1. Add `SkeletonAnimationPlayableHandle` component to your SkeletonAnimation GameObject.
 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.
 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.
 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.
 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**
 **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
 ## Spine Skeleton Flip Track
 ![](skeleton-flip-clip-inspector.png)  
 ![](skeleton-flip-clip-inspector.png)  
-Controls skeleton flip a given Spine component.
+Controls skeleton flip at a given Spine component.
 
 
 **Status:**
 **Status:**
 - Currently only SkeletonAnimation (via SkeletonAnimationPlayableHandle)
 - 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.
 - 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. 
 - 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
 ## Known Issues
 Spine Timeline support is currently experimental and has some known issues and inconveniences.
 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/
 - 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) {
 		public static void AllowImmediateQueue (this TrackEntry trackEntry) {
 			if (trackEntry.nextTrackLast < 0) trackEntry.nextTrackLast = 0;
 			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
 		#endregion
 	}
 	}
 }
 }