diff --git a/spine-csharp/src/Animation.cs b/spine-csharp/src/Animation.cs index 9989f4861..a0589396f 100644 --- a/spine-csharp/src/Animation.cs +++ b/spine-csharp/src/Animation.cs @@ -52,7 +52,7 @@ namespace Spine { /// The duration of the animation in seconds, which is the highest time of all keys in the timeline. public float Duration { get { return duration; } set { duration = value; } } - /// The animation's name, which is unique within the skeleton. + /// The animation's name, which is unique across all animations in the skeleton. public string Name { get { return name; } } /// Applies all the animation's timelines to the specified skeleton. diff --git a/spine-csharp/src/Attachments/Attachment.cs b/spine-csharp/src/Attachments/Attachment.cs index 78e264aae..328a8bf25 100644 --- a/spine-csharp/src/Attachments/Attachment.cs +++ b/spine-csharp/src/Attachments/Attachment.cs @@ -41,6 +41,9 @@ namespace Spine { override public string ToString () { return Name; } + + ///Returns a copy of the attachment. + public abstract Attachment Copy (); } public interface IHasRendererObject { diff --git a/spine-csharp/src/Attachments/BoundingBoxAttachment.cs b/spine-csharp/src/Attachments/BoundingBoxAttachment.cs index 1afccfa4c..eb69e7d27 100644 --- a/spine-csharp/src/Attachments/BoundingBoxAttachment.cs +++ b/spine-csharp/src/Attachments/BoundingBoxAttachment.cs @@ -35,5 +35,11 @@ namespace Spine { public BoundingBoxAttachment (string name) : base(name) { } + + public override Attachment Copy () { + BoundingBoxAttachment copy = new BoundingBoxAttachment(this.Name); + CopyTo(copy); + return copy; + } } } diff --git a/spine-csharp/src/Attachments/ClippingAttachment.cs b/spine-csharp/src/Attachments/ClippingAttachment.cs index f106ba4e8..8c8e857c2 100644 --- a/spine-csharp/src/Attachments/ClippingAttachment.cs +++ b/spine-csharp/src/Attachments/ClippingAttachment.cs @@ -37,5 +37,12 @@ namespace Spine { public ClippingAttachment(string name) : base(name) { } - } + + public override Attachment Copy () { + ClippingAttachment copy = new ClippingAttachment(this.Name); + CopyTo(copy); + copy.endSlot = endSlot; + return copy; + } + } } diff --git a/spine-csharp/src/Attachments/MeshAttachment.cs b/spine-csharp/src/Attachments/MeshAttachment.cs index 14b2ca38e..5eb19247e 100644 --- a/spine-csharp/src/Attachments/MeshAttachment.cs +++ b/spine-csharp/src/Attachments/MeshAttachment.cs @@ -155,5 +155,42 @@ namespace Spine { override public bool ApplyDeform (VertexAttachment sourceAttachment) { return this == sourceAttachment || (inheritDeform && parentMesh == sourceAttachment); } + + public override Attachment Copy () { + MeshAttachment copy = new MeshAttachment(this.Name); + copy.regionOffsetX = regionOffsetX; + copy.regionOffsetY = regionOffsetY; + copy.regionWidth = regionWidth; + copy.regionHeight = regionHeight; + copy.regionOriginalWidth = regionOriginalWidth; + copy.regionOriginalHeight = regionOriginalHeight; + + copy.Path = Path; + + if (parentMesh == null) { + CopyTo(copy); + copy.regionUVs = new float[regionUVs.Length]; + Array.Copy(regionUVs, 0, copy.regionUVs, 0, regionUVs.Length); + copy.uvs = new float[uvs.Length]; + Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length); + copy.triangles = new int[triangles.Length]; + Array.Copy(triangles, 0, copy.triangles, 0, triangles.Length); + copy.HullLength = HullLength; + + copy.inheritDeform = inheritDeform; + + // Nonessential. + if (Edges != null) { + copy.Edges = new int[Edges.Length]; + Array.Copy(Edges, 0, copy.Edges, 0, Edges.Length); + } + copy.Width = Width; + copy.Height = Height; + } + else + copy.ParentMesh = parentMesh; + + return copy; + } } } diff --git a/spine-csharp/src/Attachments/PathAttachment.cs b/spine-csharp/src/Attachments/PathAttachment.cs index 94d59f9ef..4ed81df55 100644 --- a/spine-csharp/src/Attachments/PathAttachment.cs +++ b/spine-csharp/src/Attachments/PathAttachment.cs @@ -42,6 +42,16 @@ namespace Spine { public PathAttachment (String name) : base(name) { - } + } + + public override Attachment Copy () { + PathAttachment copy = new PathAttachment(this.Name); + CopyTo(copy); + copy.lengths = new float[lengths.Length]; + Array.Copy(lengths, 0, copy.lengths, 0, lengths.Length); + copy.closed = closed; + copy.constantSpeed = constantSpeed; + return copy; + } } } diff --git a/spine-csharp/src/Attachments/PointAttachment.cs b/spine-csharp/src/Attachments/PointAttachment.cs index 4d001c11c..87997e93c 100644 --- a/spine-csharp/src/Attachments/PointAttachment.cs +++ b/spine-csharp/src/Attachments/PointAttachment.cs @@ -55,5 +55,13 @@ namespace Spine { float iy = cos * bone.c + sin * bone.d; return MathUtils.Atan2(iy, ix) * MathUtils.RadDeg; } + + public override Attachment Copy () { + PointAttachment copy = new PointAttachment(this.Name); + copy.x = x; + copy.y = y; + copy.rotation = rotation; + return copy; + } } } diff --git a/spine-csharp/src/Attachments/RegionAttachment.cs b/spine-csharp/src/Attachments/RegionAttachment.cs index c10bbe846..021069298 100644 --- a/spine-csharp/src/Attachments/RegionAttachment.cs +++ b/spine-csharp/src/Attachments/RegionAttachment.cs @@ -178,5 +178,26 @@ namespace Spine { worldVertices[offset + 1] = offsetX * c + offsetY * d + bwy; //offset += stride; } + + public override Attachment Copy () { + RegionAttachment copy = new RegionAttachment(this.Name); + copy.regionOffsetX = regionOffsetX; + copy.regionOffsetY = regionOffsetY; + copy.regionWidth = regionWidth; + copy.regionHeight = regionHeight; + copy.regionOriginalWidth = regionOriginalWidth; + copy.regionOriginalHeight = regionOriginalHeight; + copy.Path = Path; + copy.x = x; + copy.y = y; + copy.scaleX = scaleX; + copy.scaleY = scaleY; + copy.rotation = rotation; + copy.width = width; + copy.height = height; + Array.Copy(uvs, 0, copy.uvs, 0, 8); + Array.Copy(offset, 0, copy.offset, 0, 8); + return copy; + } } } diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs index 48a9d1962..71bb42ea0 100644 --- a/spine-csharp/src/Attachments/VertexAttachment.cs +++ b/spine-csharp/src/Attachments/VertexAttachment.cs @@ -32,7 +32,7 @@ using System; namespace Spine { /// >An attachment with vertices that are transformed by one or more bones and can be deformed by a slot's /// . - public class VertexAttachment : Attachment { + public abstract class VertexAttachment : Attachment { static int nextID = 0; static readonly Object nextIdLock = new Object(); @@ -131,6 +131,25 @@ namespace Spine { /// Returns true if a deform originally applied to the specified attachment should be applied to this attachment. virtual public bool ApplyDeform (VertexAttachment sourceAttachment) { return this == sourceAttachment; - } + } + + ///Does not copy id (generated) or name (set on construction). + 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; + } } } diff --git a/spine-csharp/src/Bone.cs b/spine-csharp/src/Bone.cs index 79dcdf5b3..a91d3978e 100644 --- a/spine-csharp/src/Bone.cs +++ b/spine-csharp/src/Bone.cs @@ -52,7 +52,7 @@ namespace Spine { internal float a, b, worldX; internal float c, d, worldY; - internal bool sorted; + internal bool sorted, update; public BoneData Data { get { return data; } } public Skeleton Skeleton { get { return skeleton; } } diff --git a/spine-csharp/src/BoneData.cs b/spine-csharp/src/BoneData.cs index 40eef4ac1..9c80fbe74 100644 --- a/spine-csharp/src/BoneData.cs +++ b/spine-csharp/src/BoneData.cs @@ -37,11 +37,12 @@ namespace Spine { internal float length; internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY; internal TransformMode transformMode = TransformMode.Normal; + internal bool skinRequired; /// The index of the bone in Skeleton.Bones public int Index { get { return index; } } - /// The name of the bone, which is unique within the skeleton. + /// The name of the bone, which is unique across all bones in the skeleton. public string Name { get { return name; } } /// May be null. @@ -73,6 +74,11 @@ namespace Spine { /// The transform mode for how parent world transforms affect this bone. public TransformMode TransformMode { get { return transformMode; } set { transformMode = value; } } + ///When true, only updates this bone if the contains this + /// bone. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + /// May be null. public BoneData (int index, string name, BoneData parent) { if (index < 0) throw new ArgumentException("index must be >= 0", "index"); diff --git a/spine-csharp/src/IConstraint.cs b/spine-csharp/src/ConstraintData.cs similarity index 59% rename from spine-csharp/src/IConstraint.cs rename to spine-csharp/src/ConstraintData.cs index 8f17c0439..7f50cc5fa 100644 --- a/spine-csharp/src/IConstraint.cs +++ b/spine-csharp/src/ConstraintData.cs @@ -27,13 +27,36 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -namespace Spine { - - /// The interface for all constraints. - public interface IConstraint : IUpdatable { - /// The ordinal for the order a skeleton's constraints will be applied. - int Order { get; } +using System; +using System.Collections.Generic; +namespace Spine +{ + /// The base class for all constraint datas. + 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; + } + + /// The constraint's name, which is unique across all constraints in the skeleton of the same type. + public string Name { get { return name; } } + + ///The ordinal of this constraint for the order a skeleton's constraints will be applied by + /// . + public int Order { get { return order; } set { order = value; } } + + ///When true, only updates this constraint if the contains + /// this constraint. + /// + public bool SkinRequired { get { return skinRequired; } set { skinRequired = value; } } + + override public string ToString () { + return name; + } } - } diff --git a/spine-csharp/src/EventData.cs b/spine-csharp/src/EventData.cs index 26bca7efc..2f1928d77 100644 --- a/spine-csharp/src/EventData.cs +++ b/spine-csharp/src/EventData.cs @@ -34,7 +34,7 @@ namespace Spine { public class EventData { internal string name; - /// The name of the event, which is unique within the skeleton. + /// The name of the event, which is unique across all events in the skeleton. public string Name { get { return name; } } public int Int { get; set; } public float Float { get; set; } diff --git a/spine-csharp/src/IkConstraint.cs b/spine-csharp/src/IkConstraint.cs index 8b5ba1a36..92b51f2a7 100644 --- a/spine-csharp/src/IkConstraint.cs +++ b/spine-csharp/src/IkConstraint.cs @@ -37,7 +37,7 @@ namespace Spine { /// /// See IK constraints in the Spine User Guide. /// - public class IkConstraint : IConstraint { + public class IkConstraint : IUpdatable { internal IkConstraintData data; internal ExposedList bones = new ExposedList(); internal Bone target; @@ -93,11 +93,6 @@ namespace Spine { } } - - public int Order { - get { return data.order; } - } - /// The bones that will be modified by this IK constraint. public ExposedList Bones { get { return bones; } diff --git a/spine-csharp/src/IkConstraintData.cs b/spine-csharp/src/IkConstraintData.cs index 5a4f7fca7..de231a39e 100644 --- a/spine-csharp/src/IkConstraintData.cs +++ b/spine-csharp/src/IkConstraintData.cs @@ -32,23 +32,14 @@ using System.Collections.Generic; namespace Spine { /// Stores the setup pose for an IkConstraint. - public class IkConstraintData { - internal string name; - internal int order; + public class IkConstraintData : ConstraintData { internal List bones = new List(); internal BoneData target; internal int bendDirection = 1; internal bool compress, stretch, uniform; internal float mix = 1; - /// The IK constraint's name, which is unique within the skeleton. - public string Name { - get { return name; } - } - - public int Order { - get { return order; } - set { order = value; } + public IkConstraintData (string name) : base(name) { } /// The bones that are constrained by this IK Constraint. @@ -98,14 +89,5 @@ namespace Spine { get { return uniform; } set { uniform = value; } } - - public IkConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - - override public string ToString () { - return name; - } } } diff --git a/spine-csharp/src/PathConstraint.cs b/spine-csharp/src/PathConstraint.cs index 74bd08a2c..efaa29a7d 100644 --- a/spine-csharp/src/PathConstraint.cs +++ b/spine-csharp/src/PathConstraint.cs @@ -38,7 +38,7 @@ namespace Spine { /// /// See Path constraints in the Spine User Guide. /// - public class PathConstraint : IConstraint { + public class PathConstraint : IUpdatable { const int NONE = -1, BEFORE = -2, AFTER = -3; const float Epsilon = 0.00001f; @@ -446,7 +446,6 @@ namespace Spine { } } - public int Order { get { return data.order; } } /// The position along the path. public float Position { get { return position; } set { position = value; } } /// The spacing between bones. @@ -461,9 +460,5 @@ namespace Spine { public Slot Target { get { return target; } set { target = value; } } /// The path constraint's setup pose data. public PathConstraintData Data { get { return data; } } - - override public string ToString () { - return data.name; - } } } diff --git a/spine-csharp/src/PathConstraintData.cs b/spine-csharp/src/PathConstraintData.cs index 924c99a06..a8f5a4e44 100644 --- a/spine-csharp/src/PathConstraintData.cs +++ b/spine-csharp/src/PathConstraintData.cs @@ -30,9 +30,7 @@ using System; namespace Spine { - public class PathConstraintData { - internal string name; - internal int order; + public class PathConstraintData : ConstraintData { internal ExposedList bones = new ExposedList(); internal SlotData target; internal PositionMode positionMode; @@ -41,8 +39,9 @@ namespace Spine { internal float offsetRotation; internal float position, spacing, rotateMix, translateMix; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } + public PathConstraintData (string name) : base(name) { + } + public ExposedList Bones { get { return bones; } } public SlotData Target { get { return target; } set { target = value; } } public PositionMode PositionMode { get { return positionMode; } set { positionMode = value; } } @@ -53,15 +52,6 @@ namespace Spine { public float Spacing { get { return spacing; } set { spacing = value; } } public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } public float TranslateMix { get { return translateMix; } set { translateMix = value; } } - - public PathConstraintData (String name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - - public override string ToString () { - return name; - } } public enum PositionMode { diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index 8e0491b03..af8ddc642 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -55,7 +55,7 @@ namespace Spine { public ExposedList IkConstraints { get { return ikConstraints; } } public ExposedList PathConstraints { get { return pathConstraints; } } public ExposedList TransformConstraints { get { return transformConstraints; } } - public Skin Skin { get { return skin; } set { skin = value; } } + public Skin Skin { get { return skin; } set { SetSkin(value); } } public float R { get { return r; } set { r = value; } } public float G { get { return g; } set { g = value; } } public float B { get { return b; } set { b = value; } } @@ -118,21 +118,25 @@ namespace Spine { UpdateWorldTransform(); } - /// Caches information about bones and constraints. Must be called if bones, constraints or weighted path attachments are added - /// or removed. + /// 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. public void UpdateCache () { var updateCache = this.updateCache; updateCache.Clear(); this.updateCacheReset.Clear(); + int boneCount = this.bones.Items.Length; var bones = this.bones; - for (int i = 0, n = bones.Count; i < n; i++) - bones.Items[i].sorted = false; + for (int i = 0; i < boneCount; i++) { + Bone bone = bones.Items[i]; + bone.update = !bone.data.skinRequired || (skin != null && skin.bones.Contains(bone.data)); + bone.sorted = !bone.update; + } + int ikCount = this.ikConstraints.Count, transformCount = this.transformConstraints.Count, pathCount = this.pathConstraints.Count; var ikConstraints = this.ikConstraints; var transformConstraints = this.transformConstraints; var pathConstraints = this.pathConstraints; - int ikCount = ikConstraints.Count, transformCount = transformConstraints.Count, pathCount = pathConstraints.Count; int constraintCount = ikCount + transformCount + pathCount; //outer: for (int i = 0; i < constraintCount; i++) { @@ -160,11 +164,13 @@ namespace Spine { continue_outer: {} } - for (int i = 0, n = bones.Count; i < n; i++) + for (int i = 0; i < boneCount; i++) SortBone(bones.Items[i]); } private void SortIkConstraint (IkConstraint constraint) { + if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return; + Bone target = constraint.target; SortBone(target); @@ -185,15 +191,15 @@ namespace Spine { } private void SortPathConstraint (PathConstraint constraint) { + if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return; + Slot slot = constraint.target; int slotIndex = slot.data.index; Bone slotBone = slot.bone; if (skin != null) SortPathConstraintAttachment(skin, slotIndex, slotBone); if (data.defaultSkin != null && data.defaultSkin != skin) SortPathConstraintAttachment(data.defaultSkin, slotIndex, slotBone); - for (int ii = 0, nn = data.skins.Count; ii < nn; ii++) - SortPathConstraintAttachment(data.skins.Items[ii], slotIndex, slotBone); - + Attachment attachment = slot.attachment; if (attachment is PathAttachment) SortPathConstraintAttachment(attachment, slotBone); @@ -211,6 +217,8 @@ namespace Spine { } private void SortTransformConstraint (TransformConstraint constraint) { + if (constraint.data.skinRequired && (skin == null || !skin.constraints.Contains(constraint.data))) return; + SortBone(constraint.target); var constrained = constraint.bones; @@ -235,8 +243,10 @@ namespace Spine { } private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments) - if (entry.Key.slotIndex == slotIndex) SortPathConstraintAttachment(entry.Value, slotBone); + foreach (var entryObj in skin.Attachments.Keys) { + var entry = (Skin.SkinEntry)entryObj; + if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); + } } private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { @@ -267,6 +277,7 @@ namespace Spine { var bonesItems = bones.Items; for (int i = 0, n = bones.Count; i < n; i++) { Bone bone = bonesItems[i]; + if (!bone.update) continue; if (bone.sorted) SortReset(bone.children); bone.sorted = false; } @@ -293,7 +304,8 @@ namespace Spine { } /// - /// 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. /// public void UpdateWorldTransform (Bone parent) { // This partial update avoids computing the world transform for constrained bones when 1) the bone is not updated @@ -313,8 +325,7 @@ namespace Spine { bone.appliedValid = true; } - // Apply the parent bone transform to the root bone. The root bone - // always inherits scale, rotation and reflection. + // Apply the parent bone transform to the root bone. The root bone always inherits scale, rotation and reflection. Bone rootBone = this.RootBone; float pa = parent.a, pb = parent.b, pc = parent.c, pd = parent.d; rootBone.worldX = pa * x + pb * y + parent.worldX; @@ -445,8 +456,12 @@ namespace Spine { } /// - /// 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. + /// Sets the skin used to look up attachments before looking in the . If the + /// skin is changed, is called. + /// + /// 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. + /// /// After changing the skin, the visible attachments can be reset to those attached in the setup pose by calling /// . /// Also, often is called before the next time the @@ -454,6 +469,7 @@ namespace Spine { /// /// May be null. public void SetSkin (Skin newSkin) { + if (newSkin == skin) return; if (newSkin != null) { if (skin != null) newSkin.AttachAll(this, skin); @@ -470,6 +486,7 @@ namespace Spine { } } skin = newSkin; + UpdateCache(); } /// Finds an attachment by looking in the {@link #skin} and {@link SkeletonData#defaultSkin} using the slot name and attachment name. @@ -526,7 +543,7 @@ namespace Spine { ExposedList transformConstraints = this.transformConstraints; for (int i = 0, n = transformConstraints.Count; i < n; i++) { TransformConstraint transformConstraint = transformConstraints.Items[i]; - if (transformConstraint.data.name == constraintName) return transformConstraint; + if (transformConstraint.data.Name == constraintName) return transformConstraint; } return null; } @@ -537,7 +554,7 @@ namespace Spine { ExposedList pathConstraints = this.pathConstraints; for (int i = 0, n = pathConstraints.Count; i < n; i++) { PathConstraint constraint = pathConstraints.Items[i]; - if (constraint.data.name.Equals(constraintName)) return constraint; + if (constraint.data.Name.Equals(constraintName)) return constraint; } return null; } diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index 84c635c0c..a660ddda4 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -174,6 +174,7 @@ namespace Spine { data.shearY = ReadFloat(input); data.length = ReadFloat(input) * scale; data.transformMode = TransformModeValues[ReadVarint(input, true)]; + data.skinRequired = ReadBoolean(input); if (nonessential) ReadInt(input); // Skip bone color. skeletonData.bones.Add(data); } @@ -206,6 +207,7 @@ namespace Spine { for (int i = 0, n = ReadVarint(input, true); i < n; i++) { IkConstraintData data = new IkConstraintData(ReadString(input)); data.order = ReadVarint(input, true); + data.skinRequired = ReadBoolean(input); for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); data.target = skeletonData.bones.Items[ReadVarint(input, true)]; @@ -221,6 +223,7 @@ namespace Spine { for (int i = 0, n = ReadVarint(input, true); i < n; i++) { TransformConstraintData data = new TransformConstraintData(ReadString(input)); data.order = ReadVarint(input, true); + data.skinRequired = ReadBoolean(input); for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); data.target = skeletonData.bones.Items[ReadVarint(input, true)]; @@ -243,6 +246,7 @@ namespace Spine { for (int i = 0, n = ReadVarint(input, true); i < n; i++) { PathConstraintData data = new PathConstraintData(ReadString(input)); data.order = ReadVarint(input, true); + data.skinRequired = ReadBoolean(input); for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) data.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); data.target = skeletonData.slots.Items[ReadVarint(input, true)]; @@ -260,7 +264,7 @@ namespace Spine { } // Default skin. - Skin defaultSkin = ReadSkin(input, skeletonData, "default", nonessential); + Skin defaultSkin = ReadSkin(input, skeletonData, true, nonessential); if (defaultSkin != null) { skeletonData.defaultSkin = defaultSkin; skeletonData.skins.Add(defaultSkin); @@ -268,7 +272,7 @@ namespace Spine { // Skins. for (int i = 0, n = ReadVarint(input, true); i < n; i++) - skeletonData.skins.Add(ReadSkin(input, skeletonData, ReadString(input), nonessential)); + skeletonData.skins.Add(ReadSkin(input, skeletonData, false, nonessential)); // Linked meshes. for (int i = 0, n = linkedMeshes.Count; i < n; i++) { @@ -312,16 +316,24 @@ namespace Spine { /// May be null. - private Skin ReadSkin (Stream input, SkeletonData skeletonData, String skinName, bool nonessential) { - int slotCount = ReadVarint(input, true); - if (slotCount == 0) return null; - Skin skin = new Skin(skinName); - for (int i = 0; i < slotCount; i++) { + private Skin ReadSkin (Stream input, SkeletonData skeletonData, bool defaultSkin, bool nonessential) { + Skin skin = new Skin(defaultSkin ? "default" : ReadString(input)); + if (!defaultSkin) { + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skin.bones.Add(skeletonData.bones.Items[ReadVarint(input, true)]); + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skin.constraints.Add(skeletonData.ikConstraints.Items[ReadVarint(input, true)]); + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skin.constraints.Add(skeletonData.transformConstraints.Items[ReadVarint(input, true)]); + for (int i = 0, n = ReadVarint(input, true); i < n; i++) + skin.constraints.Add(skeletonData.pathConstraints.Items[ReadVarint(input, true)]); + } + for (int i = 0, n = ReadVarint(input, true); i < n; i++) { int slotIndex = ReadVarint(input, true); for (int ii = 0, nn = ReadVarint(input, true); ii < nn; ii++) { String name = ReadString(input); Attachment attachment = ReadAttachment(input, skeletonData, skin, slotIndex, name, nonessential); - if (attachment != null) skin.AddAttachment(slotIndex, name, attachment); + if (attachment != null) skin.SetAttachment(slotIndex, name, attachment); } } return skin; diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index a86b5500d..b42d35276 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -109,27 +109,30 @@ namespace Spine { } // Bones. - foreach (Dictionary boneMap in (List)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 boneMap in (List)root["bones"]) { + BoneData parent = null; + if (boneMap.ContainsKey("parent")) { + parent = skeletonData.FindBone((string)boneMap["parent"]); + if (parent == null) + throw new Exception("Parent bone not found: " + boneMap["parent"]); + } + var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); + data.length = GetFloat(boneMap, "length", 0) * scale; + data.x = GetFloat(boneMap, "x", 0) * scale; + data.y = GetFloat(boneMap, "y", 0) * scale; + data.rotation = GetFloat(boneMap, "rotation", 0); + data.scaleX = GetFloat(boneMap, "scaleX", 1); + data.scaleY = GetFloat(boneMap, "scaleY", 1); + data.shearX = GetFloat(boneMap, "shearX", 0); + data.shearY = GetFloat(boneMap, "shearY", 0); + + string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); + data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); + data.skinRequired = GetBoolean(boneMap, "skin", false); + + skeletonData.bones.Add(data); } - var data = new BoneData(skeletonData.Bones.Count, (string)boneMap["name"], parent); - data.length = GetFloat(boneMap, "length", 0) * scale; - data.x = GetFloat(boneMap, "x", 0) * scale; - data.y = GetFloat(boneMap, "y", 0) * scale; - data.rotation = GetFloat(boneMap, "rotation", 0); - data.scaleX = GetFloat(boneMap, "scaleX", 1); - data.scaleY = GetFloat(boneMap, "scaleY", 1); - data.shearX = GetFloat(boneMap, "shearX", 0); - data.shearY = GetFloat(boneMap, "shearY", 0); - - string tm = GetString(boneMap, "transform", TransformMode.Normal.ToString()); - data.transformMode = (TransformMode)Enum.Parse(typeof(TransformMode), tm, true); - - skeletonData.bones.Add(data); } // Slots. @@ -171,16 +174,19 @@ namespace Spine { foreach (Dictionary constraintMap in (List)root["ik"]) { IkConstraintData data = new IkConstraintData((string)constraintMap["name"]); data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap,"skin", false); - foreach (string boneName in (List)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)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("IK bone not found: " + boneName); + data.bones.Add(bone); + } } string targetName = (string)constraintMap["target"]; data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); + if (data.target == null) throw new Exception("IK target bone not found: " + targetName); data.mix = GetFloat(constraintMap, "mix", 1); data.bendDirection = GetBoolean(constraintMap, "bendPositive", true) ? 1 : -1; data.compress = GetBoolean(constraintMap, "compress", false); @@ -196,16 +202,19 @@ namespace Spine { foreach (Dictionary constraintMap in (List)root["transform"]) { TransformConstraintData data = new TransformConstraintData((string)constraintMap["name"]); data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap,"skin", false); - foreach (string boneName in (List)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)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Transform constraint bone not found: " + boneName); + data.bones.Add(bone); + } } string targetName = (string)constraintMap["target"]; data.target = skeletonData.FindBone(targetName); - if (data.target == null) throw new Exception("Target bone not found: " + targetName); + if (data.target == null) throw new Exception("Transform constraint target bone not found: " + targetName); data.local = GetBoolean(constraintMap, "local", false); data.relative = GetBoolean(constraintMap, "relative", false); @@ -231,16 +240,19 @@ namespace Spine { foreach (Dictionary constraintMap in (List)root["path"]) { PathConstraintData data = new PathConstraintData((string)constraintMap["name"]); data.order = GetInt(constraintMap, "order", 0); + data.skinRequired = GetBoolean(constraintMap,"skin", false); - foreach (string boneName in (List)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)constraintMap["bones"]) { + BoneData bone = skeletonData.FindBone(boneName); + if (bone == null) throw new Exception("Path bone not found: " + boneName); + data.bones.Add(bone); + } } string targetName = (string)constraintMap["target"]; data.target = skeletonData.FindSlot(targetName); - if (data.target == null) throw new Exception("Target slot not found: " + targetName); + if (data.target == null) throw new Exception("Path target slot not found: " + targetName); data.positionMode = (PositionMode)Enum.Parse(typeof(PositionMode), GetString(constraintMap, "positionMode", "percent"), true); data.spacingMode = (SpacingMode)Enum.Parse(typeof(SpacingMode), GetString(constraintMap, "spacingMode", "length"), true); @@ -259,18 +271,48 @@ namespace Spine { // Skins. if (root.ContainsKey("skins")) { - foreach (KeyValuePair skinMap in (Dictionary)root["skins"]) { - var skin = new Skin(skinMap.Key); - foreach (KeyValuePair slotEntry in (Dictionary)skinMap.Value) { - int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); - foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { - try { - Attachment attachment = ReadAttachment((Dictionary)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 skinMap in (List)root["skins"]) { + Skin skin = new Skin((string)skinMap["name"]); + if (skinMap.ContainsKey("bones")) { + foreach (string entryName in (List)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)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)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)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 slotEntry in (Dictionary)skinMap["attachments"]) { + int slotIndex = skeletonData.FindSlotIndex(slotEntry.Key); + foreach (KeyValuePair entry in ((Dictionary)slotEntry.Value)) { + try { + Attachment attachment = ReadAttachment((Dictionary)entry.Value, skin, slotIndex, entry.Key, skeletonData); + if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); + } catch (Exception e) { + throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); + } + } + } } skeletonData.skins.Add(skin); if (skin.name == "default") skeletonData.defaultSkin = skin; @@ -494,7 +536,7 @@ namespace Spine { int frameIndex = 0; foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; + float time = GetFloat(valueMap, "time", 0); timeline.SetFrame(frameIndex++, time, (string)valueMap["name"]); } timelines.Add(timeline); @@ -506,7 +548,7 @@ namespace Spine { int frameIndex = 0; foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; + float time = GetFloat(valueMap, "time", 0); string c = (string)valueMap["color"]; timeline.SetFrame(frameIndex, time, ToColor(c, 0), ToColor(c, 1), ToColor(c, 2), ToColor(c, 3)); ReadCurve(valueMap, timeline, frameIndex); @@ -521,7 +563,7 @@ namespace Spine { int frameIndex = 0; foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; + float time = GetFloat(valueMap, "time", 0); string light = (string)valueMap["light"]; string dark = (string)valueMap["dark"]; timeline.SetFrame(frameIndex, time, ToColor(light, 0), ToColor(light, 1), ToColor(light, 2), ToColor(light, 3), @@ -554,7 +596,7 @@ namespace Spine { int frameIndex = 0; foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], (float)valueMap["angle"]); + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "angle", 0)); ReadCurve(valueMap, timeline, frameIndex); frameIndex++; } @@ -563,9 +605,11 @@ namespace Spine { } else if (timelineName == "translate" || timelineName == "scale" || timelineName == "shear") { TranslateTimeline timeline; - float timelineScale = 1; - if (timelineName == "scale") + float timelineScale = 1, defaultValue = 0; + if (timelineName == "scale") { timeline = new ScaleTimeline(values.Count); + defaultValue = 1; + } else if (timelineName == "shear") timeline = new ShearTimeline(values.Count); else { @@ -576,9 +620,9 @@ namespace Spine { int frameIndex = 0; foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; - float x = GetFloat(valueMap, "x", 0); - float y = GetFloat(valueMap, "y", 0); + float time = GetFloat(valueMap, "time", 0); + float x = GetFloat(valueMap, "x", defaultValue); + float y = GetFloat(valueMap, "y", defaultValue); timeline.SetFrame(frameIndex, time, x * timelineScale, y * timelineScale); ReadCurve(valueMap, timeline, frameIndex); frameIndex++; @@ -603,7 +647,7 @@ namespace Spine { foreach (Dictionary valueMap in values) { timeline.SetFrame( frameIndex, - (float)valueMap["time"], + GetFloat(valueMap, "time", 0), GetFloat(valueMap, "mix", 1), GetBoolean(valueMap, "bendPositive", true) ? 1 : -1, GetBoolean(valueMap, "compress", true), @@ -626,7 +670,7 @@ namespace Spine { timeline.transformConstraintIndex = skeletonData.transformConstraints.IndexOf(constraint); int frameIndex = 0; foreach (Dictionary valueMap in values) { - float time = (float)valueMap["time"]; + float time = GetFloat(valueMap, "time", 0); float rotateMix = GetFloat(valueMap, "rotateMix", 1); float translateMix = GetFloat(valueMap, "translateMix", 1); float scaleMix = GetFloat(valueMap, "scaleMix", 1); @@ -641,8 +685,8 @@ namespace Spine { } // Path constraint timelines. - if (map.ContainsKey("paths")) { - foreach (KeyValuePair constraintMap in (Dictionary)map["paths"]) { + if (map.ContainsKey("path")) { + foreach (KeyValuePair constraintMap in (Dictionary)map["path"]) { int index = skeletonData.FindPathConstraintIndex(constraintMap.Key); if (index == -1) throw new Exception("Path constraint not found: " + constraintMap.Key); PathConstraintData data = skeletonData.pathConstraints.Items[index]; @@ -664,7 +708,7 @@ namespace Spine { timeline.pathConstraintIndex = index; int frameIndex = 0; foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, timelineName, 0) * timelineScale); + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, timelineName, 0) * timelineScale); ReadCurve(valueMap, timeline, frameIndex); frameIndex++; } @@ -676,7 +720,7 @@ namespace Spine { timeline.pathConstraintIndex = index; int frameIndex = 0; foreach (Dictionary valueMap in values) { - timeline.SetFrame(frameIndex, (float)valueMap["time"], GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), GetFloat(valueMap, "rotateMix", 1), GetFloat(valueMap, "translateMix", 1)); ReadCurve(valueMap, timeline, frameIndex); frameIndex++; } @@ -727,7 +771,7 @@ namespace Spine { } } - timeline.SetFrame(frameIndex, (float)valueMap["time"], deform); + timeline.SetFrame(frameIndex, GetFloat(valueMap, "time", 0), deform); ReadCurve(valueMap, timeline, frameIndex); frameIndex++; } @@ -770,7 +814,7 @@ namespace Spine { for (int i = slotCount - 1; i >= 0; i--) if (drawOrder[i] == -1) drawOrder[i] = unchanged[--unchangedIndex]; } - timeline.SetFrame(frameIndex++, (float)drawOrderMap["time"], drawOrder); + timeline.SetFrame(frameIndex++, GetFloat(drawOrderMap, "time", 0), drawOrder); } timelines.Add(timeline); duration = Math.Max(duration, timeline.frames[timeline.FrameCount - 1]); @@ -784,7 +828,7 @@ namespace Spine { foreach (Dictionary eventMap in eventsMap) { EventData eventData = skeletonData.FindEvent((string)eventMap["name"]); if (eventData == null) throw new Exception("Event not found: " + eventMap["name"]); - var e = new Event((float)eventMap["time"], eventData) { + var e = new Event(GetFloat(eventMap, "time", 0), eventData) { intValue = GetInt(eventMap, "int", eventData.Int), floatValue = GetFloat(eventMap, "float", eventData.Float), stringValue = GetString(eventMap, "string", eventData.String) @@ -807,12 +851,12 @@ namespace Spine { if (!valueMap.ContainsKey("curve")) return; Object curveObject = valueMap["curve"]; - if (curveObject.Equals("stepped")) + if (curveObject is string) timeline.SetStepped(frameIndex); else { var curve = curveObject as List; 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)); } } diff --git a/spine-csharp/src/Skin.cs b/spine-csharp/src/Skin.cs index cf8680c4a..e57b3d33d 100644 --- a/spine-csharp/src/Skin.cs +++ b/spine-csharp/src/Skin.cs @@ -28,7 +28,9 @@ *****************************************************************************/ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; namespace Spine { /// Stores attachments by slot index and attachment name. @@ -37,53 +39,91 @@ namespace Spine { /// public class Skin { internal string name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); + private OrderedDictionary attachments = new OrderedDictionary(SkinEntryComparer.Instance); // contains + internal readonly ExposedList bones = new ExposedList(); + internal readonly ExposedList constraints = new ExposedList(); public string Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } - + public OrderedDictionary Attachments { get { return attachments; } } + public ExposedList Bones { get { return bones; } } + public ExposedList Constraints { get { return constraints; } } + public Skin (string name) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); this.name = name; } - /// 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. - public void AddAttachment (int slotIndex, string name, Attachment attachment) { + /// 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. + public void SetAttachment (int slotIndex, string name, Attachment attachment) { if (attachment == null) throw new ArgumentNullException("attachment", "attachment cannot be null."); - attachments[new AttachmentKeyTuple(slotIndex, name)] = attachment; + if (slotIndex < 0) throw new ArgumentNullException("slotIndex", "slotIndex must be >= 0."); + attachments[new SkinEntry(slotIndex, name, attachment)] = attachment; + } + + ///Adds all attachments, bones, and constraints from the specified skin to this skin. + 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); + } + + ///Adds all attachments from the specified skin to this skin. Attachments are deep copied. + 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); + } } /// Returns the attachment for the specified slot index and name, or null. /// May be null. 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; } /// Removes the attachment in the skin for the specified slot index and name, if any. public void RemoveAttachment (int slotIndex, string name) { if (slotIndex < 0) throw new ArgumentOutOfRangeException("slotIndex", "slotIndex must be >= 0"); - attachments.Remove(new AttachmentKeyTuple(slotIndex, name)); + var lookup = new SkinEntry(slotIndex, name, null); + attachments.Remove(lookup); } - /// Finds the skin keys for a given slot. The results are added to the passed List(names). - /// The target slotIndex. To find the slot index, use or - /// Found skin key names will be added to this list. - public void FindNamesForSlot (int slotIndex, List 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); + ///Returns all attachments contained in this skin. + public List GetAttachments () { + List entries = new List(); + foreach (SkinEntry entry in this.attachments.Keys) + entries.Add(entry); + return entries; } - /// Finds the attachments for a given slot. The results are added to the passed List(Attachment). + /// Returns all attachments in this skin for the specified slot index. /// The target slotIndex. To find the slot index, use or - /// Found Attachments will be added to this list. - public void FindAttachmentsForSlot (int slotIndex, List attachments) { - if (attachments == null) throw new ArgumentNullException("attachments", "attachments cannot be null."); - foreach (KeyValuePair entry in this.attachments) - if (entry.Key.slotIndex == slotIndex) attachments.Add(entry.Value); + public void GetAttachments (int slotIndex, List attachments) { + foreach (SkinEntry entry in this.attachments.Keys) + if (entry.SlotIndex == slotIndex) attachments.Add(entry); + } + + ///Clears all attachments, bones, and constraints. + public void Clear () { + attachments.Clear(); + bones.Clear(); + constraints.Clear(); } override public string ToString () { @@ -92,38 +132,65 @@ namespace Spine { /// Attach all attachments from this skin if the corresponding attachment from the old skin is currently attached. internal void AttachAll (Skeleton skeleton, Skin oldSkin) { - foreach (KeyValuePair entry in oldSkin.attachments) { - int slotIndex = entry.Key.slotIndex; + foreach (SkinEntry entry in oldSkin.attachments.Keys) { + int slotIndex = entry.SlotIndex; Slot slot = skeleton.slots.Items[slotIndex]; - if (slot.Attachment == entry.Value) { - Attachment attachment = GetAttachment(slotIndex, entry.Key.name); + if (slot.Attachment == entry.Attachment) { + Attachment attachment = GetAttachment(slotIndex, entry.Name); if (attachment != null) slot.Attachment = attachment; } } } - public struct AttachmentKeyTuple { - public readonly int slotIndex; - public readonly string name; - internal readonly int nameHashCode; + /// Stores an entry in the skin consisting of the slot index, name, and attachment. + public struct SkinEntry { + private readonly int slotIndex; + private readonly string name; + private readonly Attachment attachment; + internal readonly int hashCode; - public AttachmentKeyTuple (int slotIndex, string name) { + public SkinEntry (int slotIndex, string name, Attachment attachment) { this.slotIndex = slotIndex; this.name = name; - nameHashCode = this.name.GetHashCode(); + this.attachment = attachment; + this.hashCode = this.name.GetHashCode() + this.slotIndex * 37; + } + + public int SlotIndex { + get { + return slotIndex; + } + } + + /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. + 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(); - // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); - - bool IEqualityComparer.Equals (AttachmentKeyTuple o1, AttachmentKeyTuple o2) { - return o1.slotIndex == o2.slotIndex && o1.nameHashCode == o2.nameHashCode && string.Equals(o1.name, o2.name, StringComparison.Ordinal); + 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.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; + int IEqualityComparer.GetHashCode (object o) { + var e = (SkinEntry)o; + return e.Name.GetHashCode() + e.SlotIndex * 37; } } } diff --git a/spine-csharp/src/SlotData.cs b/spine-csharp/src/SlotData.cs index 4819fefd2..489b9b9b8 100644 --- a/spine-csharp/src/SlotData.cs +++ b/spine-csharp/src/SlotData.cs @@ -40,8 +40,11 @@ namespace Spine { internal string attachmentName; internal BlendMode blendMode; + /// The index of the slot in . public int Index { get { return index; } } + /// The name of the slot, which is unique across all slots in the skeleton. public string Name { get { return name; } } + /// The bone this slot belongs to. public BoneData BoneData { get { return boneData; } } public float R { get { return r; } set { r = value; } } public float G { get { return g; } set { g = value; } } @@ -53,8 +56,9 @@ namespace Spine { public float B2 { get { return b2; } set { b2 = value; } } public bool HasSecondColor { get { return hasSecondColor; } set { hasSecondColor = value; } } - /// May be null. + /// The name of the attachment that is visible for this slot in the setup pose, or null if no attachment is visible. public String AttachmentName { get { return attachmentName; } set { attachmentName = value; } } + /// The blend mode for drawing the slot's attachment. public BlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } public SlotData (int index, String name, BoneData boneData) { diff --git a/spine-csharp/src/TransformConstraint.cs b/spine-csharp/src/TransformConstraint.cs index a2d7d184f..9ab64cdf0 100644 --- a/spine-csharp/src/TransformConstraint.cs +++ b/spine-csharp/src/TransformConstraint.cs @@ -37,7 +37,7 @@ namespace Spine { /// /// See Transform constraints in the Spine User Guide. /// - public class TransformConstraint : IConstraint { + public class TransformConstraint : IUpdatable { internal TransformConstraintData data; internal ExposedList bones; internal Bone target; @@ -288,7 +288,6 @@ namespace Spine { } } - public int Order { get { return data.order; } } /// The bones that will be modified by this transform constraint. public ExposedList Bones { get { return bones; } } /// The target bone whose world transform will be copied to the constrained bones. diff --git a/spine-csharp/src/TransformConstraintData.cs b/spine-csharp/src/TransformConstraintData.cs index 1adc43dc4..0793c6355 100644 --- a/spine-csharp/src/TransformConstraintData.cs +++ b/spine-csharp/src/TransformConstraintData.cs @@ -30,17 +30,13 @@ using System; namespace Spine { - public class TransformConstraintData { - internal string name; - internal int order; + public class TransformConstraintData : ConstraintData { internal ExposedList bones = new ExposedList(); internal BoneData target; internal float rotateMix, translateMix, scaleMix, shearMix; internal float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY; internal bool relative, local; - public string Name { get { return name; } } - public int Order { get { return order; } set { order = value; } } public ExposedList Bones { get { return bones; } } public BoneData Target { get { return target; } set { target = value; } } public float RotateMix { get { return rotateMix; } set { rotateMix = value; } } @@ -58,13 +54,7 @@ namespace Spine { public bool Relative { get { return relative; } set { relative = value; } } public bool Local { get { return local; } set { local = value; } } - public TransformConstraintData (string name) { - if (name == null) throw new ArgumentNullException("name", "name cannot be null."); - this.name = name; - } - - override public string ToString () { - return name; + public TransformConstraintData (string name) : base(name) { } } } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs b/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs index 5f9fd361b..79d054c83 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Mix and Match Character Customize/EquipsVisualsComponentExample.cs @@ -60,7 +60,7 @@ namespace Spine.Unity.Examples { } public void Equip (int slotIndex, string attachmentName, Attachment attachment) { - equipsSkin.AddAttachment(slotIndex, attachmentName, attachment); + equipsSkin.SetAttachment(slotIndex, attachmentName, attachment); skeletonAnimation.Skeleton.SetSkin(equipsSkin); RefreshSkeletonAttachments(); } diff --git a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs index db01e1ff9..eed1f0661 100644 --- a/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs +++ b/spine-unity/Assets/Spine Examples/Scripts/Sample Components/Legacy/SpriteAttacher.cs @@ -170,7 +170,7 @@ namespace Spine.Unity.Examples { if (skinName != "") skin = skeletonData.FindSkin(skinName); - skin.AddAttachment(slotIndex, att.Name, att); + skin.SetAttachment(slotIndex, att.Name, att); return att; } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/PointFollowerEditor.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/PointFollowerEditor.cs index 3e605951d..6d08bf6d3 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/PointFollowerEditor.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/PointFollowerEditor.cs @@ -27,6 +27,7 @@ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +using System.Collections; using UnityEditor; using UnityEngine; @@ -110,11 +111,11 @@ namespace Spine.Unity.Editor { } static void DrawPointsInSkin (Skin skin, Skeleton skeleton, Transform transform) { - foreach (var skinEntry in skin.Attachments) { + foreach (DictionaryEntry skinEntry in skin.Attachments) { var attachment = skinEntry.Value as PointAttachment; if (attachment != null) { - var skinKey = skinEntry.Key; - var slot = skeleton.Slots.Items[skinKey.slotIndex]; + var skinKey = (Skin.SkinEntry)skinEntry.Key; + var slot = skeleton.Slots.Items[skinKey.SlotIndex]; DrawPointAttachmentWithLabel(attachment, slot.Bone, transform); } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonBaker.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonBaker.cs index 3a7245eb5..c20032981 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonBaker.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonBaker.cs @@ -228,11 +228,11 @@ namespace Spine.Unity.Editor { List attachmentNames = new List(); for (int i = 0; i < skinCount; i++) { var skin = skins.Items[i]; - List temp = new List(); - skin.FindNamesForSlot(s, temp); - foreach (string str in temp) { - if (!attachmentNames.Contains(str)) - attachmentNames.Add(str); + var skinEntries = new List(); + skin.GetAttachments(s, skinEntries); + foreach (var entry in skinEntries) { + if (!attachmentNames.Contains(entry.Name)) + attachmentNames.Add(entry.Name); } } slotLookup.Add(s, attachmentNames); @@ -333,8 +333,8 @@ namespace Spine.Unity.Editor { } //create slots and attachments - for (int i = 0; i < skeletonData.Slots.Count; i++) { - var slotData = skeletonData.Slots.Items[i]; + for (int slotIndex = 0; slotIndex < skeletonData.Slots.Count; slotIndex++) { + var slotData = skeletonData.Slots.Items[slotIndex]; Transform slotTransform = SpineEditorUtilities.EditorInstantiation.NewGameObject(slotData.Name).transform; slotTransform.parent = prefabRoot.transform; slotTable.Add(slotData.Name, slotTransform); @@ -342,12 +342,20 @@ namespace Spine.Unity.Editor { List attachments = new List(); List attachmentNames = new List(); - skin.FindAttachmentsForSlot(i, attachments); - skin.FindNamesForSlot(i, attachmentNames); + var skinEntries = new List(); + skin.GetAttachments(slotIndex, skinEntries); + foreach (var entry in skinEntries) { + attachments.Add(entry.Attachment); + attachmentNames.Add(entry.Name); + } if (skin != skeletonData.DefaultSkin) { - skeletonData.DefaultSkin.FindAttachmentsForSlot(i, attachments); - skeletonData.DefaultSkin.FindNamesForSlot(i, attachmentNames); + skinEntries.Clear(); + skeletonData.DefaultSkin.GetAttachments(slotIndex, skinEntries); + foreach (var entry in skinEntries) { + attachments.Add(entry.Attachment); + attachmentNames.Add(entry.Name); + } } for (int a = 0; a < attachments.Count; a++) { @@ -380,7 +388,7 @@ namespace Spine.Unity.Editor { rotation = 0; if (isWeightedMesh) - mesh = ExtractWeightedMeshAttachment(attachmentMeshName, meshAttachment, i, skeletonData, boneList, mesh); + mesh = ExtractWeightedMeshAttachment(attachmentMeshName, meshAttachment, slotIndex, skeletonData, boneList, mesh); else mesh = ExtractMeshAttachment(attachmentMeshName, meshAttachment, mesh); @@ -410,7 +418,7 @@ namespace Spine.Unity.Editor { } attachmentTransform.GetComponent().sharedMaterial = material; - attachmentTransform.GetComponent().sortingOrder = i; + attachmentTransform.GetComponent().sortingOrder = slotIndex; if (attachmentName != slotData.AttachmentName) attachmentTransform.gameObject.SetActive(false); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDataAssetInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDataAssetInspector.cs index 4641eb68d..52f3cf611 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDataAssetInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDataAssetInspector.cs @@ -498,15 +498,28 @@ namespace Spine.Unity.Editor { using (new SpineInspectorUtility.IndentScope()) { { - skin.FindNamesForSlot(i, slotAttachmentNames); - skin.FindAttachmentsForSlot(i, slotAttachments); + var skinEntries = new List(); + skin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { + slotAttachments.Add(entry.Attachment); + slotAttachmentNames.Add(entry.Name); + } if (skin != defaultSkin) { - defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames); - defaultSkin.FindNamesForSlot(i, slotAttachmentNames); - defaultSkin.FindAttachmentsForSlot(i, slotAttachments); + skinEntries.Clear(); + defaultSkin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { + slotAttachments.Add(entry.Attachment); + slotAttachmentNames.Add(entry.Name); + defaultSkinAttachmentNames.Add(entry.Name); + } + } else { - defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames); + skinEntries.Clear(); + defaultSkin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { + defaultSkinAttachmentNames.Add(entry.Name); + } } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDebugWindow.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDebugWindow.cs index 3a68bc2b7..17bab0cd3 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDebugWindow.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDebugWindow.cs @@ -574,8 +574,19 @@ namespace Spine.Unity.Editor { for (int i = skeleton.Slots.Count - 1; i >= 0; i--) { var attachments = new List(); 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.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; diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs index 567d44611..add04fb85 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs @@ -163,6 +163,7 @@ namespace Spine.Unity.Editor { override public void OnInspectorGUI () { bool multi = serializedObject.isEditingMultipleObjects; DrawInspectorGUI(multi); + HandleSkinChange(); if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current) || AreAnyMaskMaterialsMissing()) { if (!Application.isPlaying) { @@ -174,21 +175,6 @@ namespace Spine.Unity.Editor { SceneView.RepaintAll(); } } - - if (!Application.isPlaying && Event.current.type == EventType.Layout) { - bool mismatchDetected = false; - if (multi) { - foreach (var o in targets) - mismatchDetected |= UpdateIfSkinMismatch((SkeletonRenderer)o); - } else { - mismatchDetected |= UpdateIfSkinMismatch(target as SkeletonRenderer); - } - - if (mismatchDetected) { - mismatchDetected = false; - SceneView.RepaintAll(); - } - } } protected virtual void DrawInspectorGUI (bool multi) { @@ -493,19 +479,42 @@ namespace Spine.Unity.Editor { } } - static bool UpdateIfSkinMismatch (SkeletonRenderer skeletonRenderer) { + void HandleSkinChange() { + if (!Application.isPlaying && Event.current.type == EventType.Layout && !initialSkinName.hasMultipleDifferentValues) { + bool mismatchDetected = false; + string newSkinName = initialSkinName.stringValue; + foreach (var o in targets) { + mismatchDetected |= UpdateIfSkinMismatch((SkeletonRenderer)o, newSkinName); + } + + if (mismatchDetected) { + mismatchDetected = false; + SceneView.RepaintAll(); + } + } + } + + static bool UpdateIfSkinMismatch (SkeletonRenderer skeletonRenderer, string componentSkinName) { if (!skeletonRenderer.valid) return false; var skin = skeletonRenderer.Skeleton.Skin; string skeletonSkinName = skin != null ? skin.Name : null; - string componentSkinName = skeletonRenderer.initialSkinName; bool defaultCase = skin == null && string.IsNullOrEmpty(componentSkinName); bool fieldMatchesSkin = defaultCase || string.Equals(componentSkinName, skeletonSkinName, System.StringComparison.Ordinal); if (!fieldMatchesSkin) { Skin skinToSet = string.IsNullOrEmpty(componentSkinName) ? null : skeletonRenderer.Skeleton.Data.FindSkin(componentSkinName); - skeletonRenderer.Skeleton.Skin = skinToSet; + skeletonRenderer.Skeleton.SetSkin(skinToSet); skeletonRenderer.Skeleton.SetSlotsToSetupPose(); + + // Note: the UpdateIfSkinMismatch concept shall be replaced with e.g. an OnValidate based + // solution or in a separate commit. The current solution does not repaint the Game view because + // it is first applying values and in the next editor pass is calling this skin-changing method. + if (skeletonRenderer is SkeletonAnimation) + ((SkeletonAnimation) skeletonRenderer).Update(0f); + else if (skeletonRenderer is SkeletonMecanim) + ((SkeletonMecanim) skeletonRenderer).Update(); + skeletonRenderer.LateUpdate(); return true; } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs index 78ee7a3de..73c5c830f 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs @@ -177,19 +177,19 @@ namespace Spine.Unity.Editor { if (TargetAttribute.includeNone) menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property)); - for (int i = 0; i < data.Slots.Count; i++) { - string name = data.Slots.Items[i].Name; + for (int slotIndex = 0; slotIndex < data.Slots.Count; slotIndex++) { + string name = data.Slots.Items[slotIndex].Name; if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) { if (targetAttribute.containsBoundingBoxes) { - int slotIndex = i; - var attachments = new List(); - foreach (var skin in data.Skins) - skin.FindAttachmentsForSlot(slotIndex, attachments); + var skinEntries = new List(); + foreach (var skin in data.Skins) { + skin.GetAttachments(slotIndex, skinEntries); + } bool hasBoundingBox = false; - foreach (var attachment in attachments) { - var bbAttachment = attachment as BoundingBoxAttachment; + foreach (var entry in skinEntries) { + var bbAttachment = entry.Attachment as BoundingBoxAttachment; if (bbAttachment != null) { string menuLabel = bbAttachment.IsWeighted() ? name + " (!)" : name; menu.AddItem(new GUIContent(menuLabel), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property)); @@ -470,10 +470,21 @@ namespace Spine.Unity.Editor { attachmentNames.Clear(); placeholderNames.Clear(); - skin.FindNamesForSlot(i, attachmentNames); + var skinEntries = new List(); + skin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { + attachmentNames.Add(entry.Name); + } + if (skin != defaultSkin) { - defaultSkin.FindNamesForSlot(i, attachmentNames); - skin.FindNamesForSlot(i, placeholderNames); + foreach (var entry in skinEntries) { + placeholderNames.Add(entry.Name); + } + skinEntries.Clear(); + defaultSkin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { + attachmentNames.Add(entry.Name); + } } for (int a = 0; a < attachmentNames.Count; a++) { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs index 46e161cbf..29e69aa7c 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineEditorUtilities.cs @@ -707,9 +707,10 @@ namespace Spine.Unity.Editor { if (root == null || !root.ContainsKey("skins")) return requiredPaths; - foreach (var skin in (Dictionary)root["skins"]) { - foreach (var slot in (Dictionary)skin.Value) { - + foreach (Dictionary skinMap in (List)root["skins"]) { + if (!skinMap.ContainsKey("attachments")) + continue; + foreach (var slot in (Dictionary)skinMap["attachments"]) { foreach (var attachment in ((Dictionary)slot.Value)) { var data = ((Dictionary)attachment.Value); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs index 9877985fa..2a52f9e58 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/SkeletonUtility/Editor/SkeletonUtilityBoneInspector.cs @@ -81,7 +81,13 @@ namespace Spine.Unity.Editor { Slot slot = skeletonUtility.skeletonRenderer.skeleton.Slots.Items[i]; if (slot.Bone == utilityBone.bone) { var slotAttachments = new List(); - skin.FindAttachmentsForSlot(skeleton.FindSlotIndex(slot.Data.Name), slotAttachments); + var skinEntries = new List(); + int slotIndex = skeleton.FindSlotIndex(slot.Data.Name); + skin.GetAttachments(slotIndex, skinEntries); + foreach (var entry in skinEntries) { + slotAttachments.Add(entry.Attachment); + } + var boundingBoxes = new List(); foreach (var att in slotAttachments) { var boundingBoxAttachment = att as BoundingBoxAttachment; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs index 51b641e5f..e3bdd9944 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/BlendModeMaterialsAsset.cs @@ -52,16 +52,16 @@ namespace Spine.Unity { if (skeletonData == null) throw new ArgumentNullException("skeletonData"); using (var materialCache = new AtlasMaterialCache()) { - var attachmentBuffer = new List(); + var entryBuffer = new List(); var slotsItems = skeletonData.Slots.Items; - for (int i = 0, slotCount = skeletonData.Slots.Count; i < slotCount; i++) { - var slot = slotsItems[i]; + for (int slotIndex = 0, slotCount = skeletonData.Slots.Count; slotIndex < slotCount; slotIndex++) { + var slot = slotsItems[slotIndex]; if (slot.blendMode == BlendMode.Normal) continue; if (!includeAdditiveSlots && slot.blendMode == BlendMode.Additive) continue; - attachmentBuffer.Clear(); + entryBuffer.Clear(); foreach (var skin in skeletonData.Skins) - skin.FindAttachmentsForSlot(i, attachmentBuffer); + skin.GetAttachments(slotIndex, entryBuffer); Material templateMaterial = null; switch (slot.blendMode) { @@ -77,8 +77,8 @@ namespace Spine.Unity { } if (templateMaterial == null) continue; - foreach (var attachment in attachmentBuffer) { - var renderableAttachment = attachment as IHasRendererObject; + foreach (var entry in entryBuffer) { + var renderableAttachment = entry.Attachment as IHasRendererObject; if (renderableAttachment != null) { renderableAttachment.RendererObject = materialCache.CloneAtlasRegionWithMaterial((AtlasRegion)renderableAttachment.RendererObject, templateMaterial); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs index 4912fd83c..231a60817 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/AttachmentTools/AttachmentTools.cs @@ -29,6 +29,7 @@ using UnityEngine; using System.Collections.Generic; +using System.Collections; namespace Spine.Unity.Modules.AttachmentTools { public static class AttachmentRegionExtensions { @@ -503,9 +504,10 @@ namespace Spine.Unity.Modules.AttachmentTools { var texturesToPack = new List(); var originalRegions = new List(); int newRegionIndex = 0; - foreach (var skinEntry in skinAttachments) { - var originalKey = skinEntry.Key; - var originalAttachment = skinEntry.Value; + + foreach (DictionaryEntry skinEntry in skinAttachments) { + var originalKey = (Skin.SkinEntry)skinEntry.Key; + var originalAttachment = (Attachment)skinEntry.Value; Attachment newAttachment; if (IsRenderable(originalAttachment)) { @@ -523,9 +525,9 @@ namespace Spine.Unity.Modules.AttachmentTools { } repackedAttachments.Add(newAttachment); - newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, newAttachment); + newSkin.SetAttachment(originalKey.SlotIndex, originalKey.Name, newAttachment); } else { - newSkin.AddAttachment(originalKey.slotIndex, originalKey.name, useOriginalNonrenderables ? originalAttachment : originalAttachment.GetClone(true)); + newSkin.SetAttachment(originalKey.SlotIndex, originalKey.Name, useOriginalNonrenderables ? originalAttachment : originalAttachment.GetClone(true)); } } @@ -793,7 +795,7 @@ namespace Spine.Unity.Modules.AttachmentTools { var newSkin = new Skin(original.name + " clone"); var newSkinAttachments = newSkin.Attachments; - foreach (var a in original.Attachments) + foreach (DictionaryEntry a in original.Attachments) newSkinAttachments[a.Key] = a.Value; return newSkin; @@ -804,7 +806,7 @@ namespace Spine.Unity.Modules.AttachmentTools { int slotIndex = skeleton.FindSlotIndex(slotName); if (skeleton == null) throw new System.ArgumentNullException("skeleton", "skeleton cannot be null."); if (slotIndex == -1) throw new System.ArgumentException(string.Format("Slot '{0}' does not exist in skeleton.", slotName), "slotName"); - skin.AddAttachment(slotIndex, keyName, attachment); + skin.SetAttachment(slotIndex, keyName, attachment); } /// Adds skin items from another skin. For items that already exist, the previous values are replaced. @@ -823,7 +825,7 @@ namespace Spine.Unity.Modules.AttachmentTools { /// 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. public static void SetAttachment (this Skin skin, int slotIndex, string keyName, Attachment attachment) { - skin.AddAttachment(slotIndex, keyName, attachment); + skin.SetAttachment(slotIndex, keyName, attachment); } public static void RemoveAttachment (this Skin skin, string slotName, string keyName, SkeletonData skeletonData) { @@ -848,21 +850,21 @@ namespace Spine.Unity.Modules.AttachmentTools { if (cloneAttachments) { if (overwrite) { - foreach (var e in sourceAttachments) - destinationAttachments[e.Key] = e.Value.GetClone(cloneMeshesAsLinked); + foreach (DictionaryEntry e in sourceAttachments) + destinationAttachments[e.Key] = ((Attachment)e.Value).GetClone(cloneMeshesAsLinked); } else { - foreach (var e in sourceAttachments) { - if (destinationAttachments.ContainsKey(e.Key)) continue; - destinationAttachments.Add(e.Key, e.Value.GetClone(cloneMeshesAsLinked)); + foreach (DictionaryEntry e in sourceAttachments) { + if (destinationAttachments.Contains(e.Key)) continue; + destinationAttachments.Add(e.Key, ((Attachment)e.Value).GetClone(cloneMeshesAsLinked)); } } } else { if (overwrite) { - foreach (var e in sourceAttachments) + foreach (DictionaryEntry e in sourceAttachments) destinationAttachments[e.Key] = e.Value; } else { - foreach (var e in sourceAttachments) { - if (destinationAttachments.ContainsKey(e.Key)) continue; + foreach (DictionaryEntry e in sourceAttachments) { + if (destinationAttachments.Contains(e.Key)) continue; destinationAttachments.Add(e.Key, e.Value); } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/BoundingBoxFollower/BoundingBoxFollower.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/BoundingBoxFollower/BoundingBoxFollower.cs index d5e720d98..ffad1da27 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/BoundingBoxFollower/BoundingBoxFollower.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/BoundingBoxFollower/BoundingBoxFollower.cs @@ -139,11 +139,11 @@ namespace Spine.Unity { void AddSkin (Skin skin, int slotIndex) { if (skin == null) return; - var attachmentNames = new List(); - skin.FindNamesForSlot(slotIndex, attachmentNames); - - foreach (var skinKey in attachmentNames) { - var attachment = skin.GetAttachment(slotIndex, skinKey); + var skinEntries = new List(); + skin.GetAttachments(slotIndex, skinEntries); + + foreach (var entry in skinEntries) { + var attachment = skin.GetAttachment(slotIndex, entry.Name); var boundingBoxAttachment = attachment as BoundingBoxAttachment; if (BoundingBoxFollower.DebugMessages && attachment != null && boundingBoxAttachment == null) @@ -157,7 +157,7 @@ namespace Spine.Unity { bbCollider.hideFlags = HideFlags.NotEditable; bbCollider.isTrigger = IsTrigger; colliderTable.Add(boundingBoxAttachment, bbCollider); - nameTable.Add(boundingBoxAttachment, skinKey); + nameTable.Add(boundingBoxAttachment, entry.Name); } } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs index 6d2047e7c..2336273f3 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll.cs @@ -365,14 +365,15 @@ namespace Spine.Unity.Modules { GameObject go = t.gameObject; var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin; - var attachments = new List(); + var skinEntries = new List(); foreach (Slot s in skeleton.Slots) { if (s.Bone == b) { - skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments); - foreach (var a in attachments) { - var bbAttachment = a as BoundingBoxAttachment; + skin.GetAttachments(skeleton.Slots.IndexOf(s), skinEntries); + + foreach (var entry in skinEntries) { + var bbAttachment = entry.Attachment as BoundingBoxAttachment; if (bbAttachment != null) { - if (!a.Name.ToLower().Contains(AttachmentNameMarker)) + if (!entry.Name.ToLower().Contains(AttachmentNameMarker)) continue; var bbCollider = go.AddComponent(); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs index afd440070..88949f7ed 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Ragdoll/SkeletonRagdoll2D.cs @@ -360,16 +360,16 @@ namespace Spine.Unity.Modules { var colliders = new List(); var skin = skeleton.Skin ?? skeleton.Data.DefaultSkin; - var attachments = new List(); + var skinEntries = new List(); foreach (Slot slot in skeleton.Slots) { if (slot.bone == b) { - skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(slot), attachments); + skin.GetAttachments(skeleton.Slots.IndexOf(slot), skinEntries); bool bbAttachmentAdded = false; - foreach (var a in attachments) { - var bbAttachment = a as BoundingBoxAttachment; + foreach (var entry in skinEntries) { + var bbAttachment = entry.Attachment as BoundingBoxAttachment; if (bbAttachment != null) { - if (!a.Name.ToLower().Contains(AttachmentNameMarker)) + if (!entry.Name.ToLower().Contains(AttachmentNameMarker)) continue; bbAttachmentAdded = true; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/README.md b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/README.md index 94d4c610b..e611aa20c 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/README.md +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/README.md @@ -5,31 +5,37 @@ If this documentation contains mistakes or doesn't cover some questions, please ![](add-menu.png) -## Spine Animation Track -Controls the skeleton pose a given Spine component with animations. +## Spine AnimationState Track +![](animationstate-clip-inspector.png) +Sets animations on the target SkeletonAnimation's AnimationState. **Status:** -- Currently only SkeletonAnimation (via SkeletonAnimationPlayableHandle) -- Mixing has some significant bugs. It should work fine if you don't include mixing, or if all your animations have perfectly matching dopesheet properties. +- Currently only SkeletonAnimation (directly) **To use:** 1. Add `SkeletonAnimationPlayableHandle` component to your SkeletonAnimation GameObject. -2. With an existing Unity Playable Direction, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Animation Track**. -3. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine Skeleton Flip Track. -4. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Animation Clip Clip**. +2. With an existing Unity Playable Director, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Animation State Track**. +3. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine AnimationState Track. +4. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Animation State Clip Clip**. 5. Adjust the start and end times of the new clip, name it appropriately at the top of the Inspector. 6. Click on the clip inspector's SkeletonDataAsset field and choose your target skeleton's SkeletonDataAsset. This will enable the animation name dropdown to appear. 7. Choose the appropriate animation name, loop, and mix settings. -8. For easier readability, rename your clip to the animation name or something descriptive. +- For easier readability, rename your clip to the animation name or something descriptive. +- To avoid having to do steps 4-6 repeatedly, use the Duplicate function (`CTRL`/`CMD` + `D`) **Track Behavior** -- Currently buggy -- - +- `AnimationState.SetAnimation` will be called at the beginning of every clip based on the animationName. +- Clip durations don't matter. Animations won't be cleared where there is no active clip at certain slices of time. +- **EMPTY ANIMATION**: If a clip has no name specified, it will call SetEmptyAnimation instead. +- **ERROR HANDLING**: If the animation with the provided animationName is not found, it will do nothing (the previous animation will continue playing normally). +- Animations playing before the timeline starts playing will not be interrupted until the first clip starts playing. +- At the end of the last clip and at the end of the timeline, nothing happens. This means the effect of the last clip's SetAnimation call will persist until you give other commands to that AnimationState. +- If "custom duration" is unchecked, it will do a normal lookup of the AnimationState data's specified transition-pair mix setting, or the default mix. +- Edit mode preview mixing may look different from Play Mode mixing. Please check in actual Play Mode to see the real results. ## Spine Skeleton Flip Track ![](skeleton-flip-clip-inspector.png) -Controls skeleton flip a given Spine component. +Controls skeleton flip at a given Spine component. **Status:** - Currently only SkeletonAnimation (via SkeletonAnimationPlayableHandle) @@ -45,33 +51,6 @@ Controls skeleton flip a given Spine component. - The specified skeleton flip values will be applied for every frame within the duration of each track. - At the end of the timeline, the track will revert the skeleton flip to the flip values it captures when it starts playing that timeline. -## Spine AnimationState Track -![](animationstate-clip-inspector.png) -Sets Animations on the target SkeletonAnimation's AnimationState (via SetAnimation). - -**Status:** -- Currently only SkeletonAnimation (directly) - -**To use:** -1. With an existing Unity Playable Director, and in the Unity Timeline window, right-click on an empty space on the left and choose **Spine.Unity.Playables** > **Spine Animation State Track**. -2. Drag the SkeletonAnimation GameObject onto the empty reference property of the new Spine AnimationState Track. -3. Right-click on the row in an empty space in the Timeline dopesheet and choose **Add Spine Animation State Clip Clip**. -4. Adjust the start and end times of the new clip, name it appropriately at the top of the Inspector. -5. Click on the clip inspector's SkeletonDataAsset field and choose your target skeleton's SkeletonDataAsset. This will enable the animation name dropdown to appear. -6. Choose the appropriate animation name, loop, and mix settings. -- For easier readability, rename your clip to the animation name or something descriptive. -- To avoid having to do steps 4-6 repeatedly, use the Duplicate function (`CTRL`/`CMD` + `D`) - -**Track Behavior** -- `AnimationState.SetAnimation` will be called at the beginning of every clip based on the animationName. -- Clip durations don't matter. Animations won't be cleared where there is no active clip at certain slices of time. -- **EMPTY ANIMATION**: If a clip has no name specified, it will call SetEmptyAnimation instead. -- **ERROR HANDLING**: If the animation with the provided animationName is not found, it will do nothing (the previous animation will continue playing normally). -- Animations playing before the timeline starts playing will not be interrupted until the first clip starts playing. -- At the end of the last clip and at the end of the timeline, nothing happens. This means the effect of the last clip's SetAnimation call will persist until you give other commands to that AnimationState. -- If "custom duration" is unchecked, it will do a normal lookup of the AnimationState data's specified transition-pair mix setting, or the default mix. -- Edit mode preview mixing may look different from Play Mode mixing. Please check in actual Play Mode to see the real results. - ## Known Issues Spine Timeline support is currently experimental and has some known issues and inconveniences. - The Console logs an incorrect/harmless error `DrivenPropertyManager has failed to register property "m_Script" of object "Spine GameObject (spineboy-pro)" with driver "" because the property doesn't exist.`. This is a known issue on Unity's end. See more here: https://forum.unity.com/threads/default-playables-text-switcher-track-error.502903/ diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/add-menu.png b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/add-menu.png index b8e383f3c..f2277f2fb 100644 Binary files a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/add-menu.png and b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/add-menu.png differ diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png index f5b5702c4..c13b9d4f8 100644 Binary files a/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png and b/spine-unity/Assets/Spine/Runtime/spine-unity/Modules/Timeline/Documentation/animationstate-clip-inspector.png differ diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs index 1c0ac4f5c..004f14291 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/SkeletonExtensions.cs @@ -608,21 +608,6 @@ namespace Spine { public static void AllowImmediateQueue (this TrackEntry trackEntry) { if (trackEntry.nextTrackLast < 0) trackEntry.nextTrackLast = 0; } - - #endregion - - #region Skins - /// - public static void FindNamesForSlot (this Skin skin, string slotName, SkeletonData skeletonData, List results) { - int slotIndex = skeletonData.FindSlotIndex(slotName); - skin.FindNamesForSlot(slotIndex, results); - } - - /// - public static void FindAttachmentsForSlot (this Skin skin, string slotName, SkeletonData skeletonData, List results) { - int slotIndex = skeletonData.FindSlotIndex(slotName); - skin.FindAttachmentsForSlot(slotIndex, results); - } #endregion } }