From a009c35b82f7a53ffac8afd1cf217d40d91fd5b7 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Thu, 16 May 2019 18:52:20 +0200 Subject: [PATCH 1/5] [csharp] Ported skin API changes, see #841. --- spine-csharp/src/Attachments/Attachment.cs | 3 + .../src/Attachments/BoundingBoxAttachment.cs | 6 + .../src/Attachments/ClippingAttachment.cs | 9 +- .../src/Attachments/MeshAttachment.cs | 37 ++++++ .../src/Attachments/PathAttachment.cs | 12 +- .../src/Attachments/PointAttachment.cs | 8 ++ .../src/Attachments/RegionAttachment.cs | 21 ++++ .../src/Attachments/VertexAttachment.cs | 24 +++- spine-csharp/src/Skeleton.cs | 4 +- spine-csharp/src/SkeletonBinary.cs | 2 +- spine-csharp/src/SkeletonJson.cs | 2 +- spine-csharp/src/Skin.cs | 110 +++++++++++------- .../EquipsVisualsComponentExample.cs | 2 +- .../Legacy/SpriteAttacher.cs | 2 +- .../spine-unity/Editor/PointFollowerEditor.cs | 2 +- .../spine-unity/Editor/SkeletonBaker.cs | 18 ++- .../Editor/SkeletonDataAssetInspector.cs | 22 +++- .../spine-unity/Editor/SkeletonDebugWindow.cs | 13 ++- .../Editor/SpineAttributeDrawers.cs | 23 +++- .../Editor/SkeletonUtilityBoneInspector.cs | 6 +- .../Asset Types/BlendModeMaterialsAsset.cs | 11 +- .../AttachmentTools/AttachmentTools.cs | 8 +- .../BoundingBoxFollower.cs | 4 +- .../Modules/Ragdoll/SkeletonRagdoll.cs | 5 +- .../Modules/Ragdoll/SkeletonRagdoll2D.cs | 4 +- .../Runtime/spine-unity/SkeletonExtensions.cs | 15 --- 26 files changed, 278 insertions(+), 95 deletions(-) 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..f591de110 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, uvs.Length); + Array.Copy(offset, 0, copy.offset, 0, offset.Length); + return copy; + } } } diff --git a/spine-csharp/src/Attachments/VertexAttachment.cs b/spine-csharp/src/Attachments/VertexAttachment.cs index 48a9d1962..d951248b8 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,26 @@ 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; - } + } + + ///Internal method used by VertexAttachment subclasses to copy basic data. Does not copy id (generated) and 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/Skeleton.cs b/spine-csharp/src/Skeleton.cs index 8e0491b03..cd6fedef4 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -235,8 +235,8 @@ 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 entry in skin.Attachments.Keys) + if (entry.SlotIndex == slotIndex) SortPathConstraintAttachment(entry.Attachment, slotBone); } private void SortPathConstraintAttachment (Attachment attachment, Bone slotBone) { diff --git a/spine-csharp/src/SkeletonBinary.cs b/spine-csharp/src/SkeletonBinary.cs index 84c635c0c..126149461 100644 --- a/spine-csharp/src/SkeletonBinary.cs +++ b/spine-csharp/src/SkeletonBinary.cs @@ -321,7 +321,7 @@ namespace Spine { 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..01a132995 100644 --- a/spine-csharp/src/SkeletonJson.cs +++ b/spine-csharp/src/SkeletonJson.cs @@ -266,7 +266,7 @@ namespace Spine { 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); + if (attachment != null) skin.SetAttachment(slotIndex, entry.Key, attachment); } catch (Exception e) { throw new Exception("Error reading attachment: " + entry.Key + ", skin: " + skin, e); } diff --git a/spine-csharp/src/Skin.cs b/spine-csharp/src/Skin.cs index cf8680c4a..2eb6fa149 100644 --- a/spine-csharp/src/Skin.cs +++ b/spine-csharp/src/Skin.cs @@ -37,53 +37,62 @@ namespace Spine { /// public class Skin { internal string name; - private Dictionary attachments = - new Dictionary(AttachmentKeyTupleComparer.Instance); - + private Dictionary attachments = + new Dictionary(SkinEntryComparer.Instance); + public string Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } + public Dictionary Attachments { get { return attachments; } } 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 from the specified skin to this skin. + public void AddSkin (Skin skin) { + foreach (SkinEntry entry in skin.attachments.Keys) + SetAttachment(entry.SlotIndex, entry.Name, entry.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); + var lookup = new SkinEntry(slotIndex, name, null); + attachments.TryGetValue(lookup, out attachment); return attachment; } /// 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 instances for the given slot contained in this skin. /// 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 List GetEntries (int slotIndex) { + List entries = new List(); + foreach (SkinEntry entry in this.attachments.Keys) + if (entry.SlotIndex == slotIndex) entries.Add(entry); + return entries; } override public string ToString () { @@ -92,38 +101,61 @@ 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; + } + } + + public String Name { + get { + return name; + } + } + + public Attachment Attachment { + get { + return attachment; + } } } - + // Avoids boxing in the dictionary. - class AttachmentKeyTupleComparer : IEqualityComparer { - internal static readonly AttachmentKeyTupleComparer Instance = new AttachmentKeyTupleComparer(); + class SkinEntryComparer : IEqualityComparer { + internal static readonly SkinEntryComparer Instance = new SkinEntryComparer(); - 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 (SkinEntry o1, SkinEntry o2) { + if (o1.SlotIndex != o2.SlotIndex) return false; + if (!string.Equals(o1.Name, o2.Name, StringComparison.Ordinal)) return false; + return true; } - int IEqualityComparer.GetHashCode (AttachmentKeyTuple o) { - return o.slotIndex; + int IEqualityComparer.GetHashCode (SkinEntry o) { + return o.Name.GetHashCode() + o.SlotIndex * 37; } } } 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..8cde89ae2 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/PointFollowerEditor.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/PointFollowerEditor.cs @@ -114,7 +114,7 @@ namespace Spine.Unity.Editor { var attachment = skinEntry.Value as PointAttachment; if (attachment != null) { var skinKey = skinEntry.Key; - var slot = skeleton.Slots.Items[skinKey.slotIndex]; + 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..c33a065db 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonBaker.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonBaker.cs @@ -229,7 +229,9 @@ namespace Spine.Unity.Editor { for (int i = 0; i < skinCount; i++) { var skin = skins.Items[i]; List temp = new List(); - skin.FindNamesForSlot(s, temp); + var entries = skin.GetEntries(s); + foreach (var entry in entries) + temp.Add(entry.Name); foreach (string str in temp) { if (!attachmentNames.Contains(str)) attachmentNames.Add(str); @@ -342,12 +344,18 @@ namespace Spine.Unity.Editor { List attachments = new List(); List attachmentNames = new List(); - skin.FindAttachmentsForSlot(i, attachments); - skin.FindNamesForSlot(i, attachmentNames); + var entries = skin.GetEntries(i); + foreach (var entry in entries) { + attachments.Add(entry.Attachment); + attachmentNames.Add(entry.Name); + } if (skin != skeletonData.DefaultSkin) { - skeletonData.DefaultSkin.FindAttachmentsForSlot(i, attachments); - skeletonData.DefaultSkin.FindNamesForSlot(i, attachmentNames); + entries = skeletonData.DefaultSkin.GetEntries(i); + foreach (var entry in entries) { + attachments.Add(entry.Attachment); + attachmentNames.Add(entry.Name); + } } for (int a = 0; a < attachments.Count; a++) { 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..ef26a2007 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,25 @@ namespace Spine.Unity.Editor { using (new SpineInspectorUtility.IndentScope()) { { - skin.FindNamesForSlot(i, slotAttachmentNames); - skin.FindAttachmentsForSlot(i, slotAttachments); + var entries = skin.GetEntries(i); + foreach (var entry in entries) { + slotAttachments.Add(entry.Attachment); + slotAttachmentNames.Add(entry.Name); + } if (skin != defaultSkin) { - defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames); - defaultSkin.FindNamesForSlot(i, slotAttachmentNames); - defaultSkin.FindAttachmentsForSlot(i, slotAttachments); + entries = defaultSkin.GetEntries(i); + foreach (var entry in entries) { + slotAttachments.Add(entry.Attachment); + slotAttachmentNames.Add(entry.Name); + defaultSkinAttachmentNames.Add(entry.Name); + } + } else { - defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames); + entries = defaultSkin.GetEntries(i); + foreach (var entry in entries) { + 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..444c048b8 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,17 @@ 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 entries = skin.GetEntries(i); + foreach (var entry in entries) { + attachments.Add(entry.Attachment); + } + if (notDefaultSkin) { // Add default skin attachments. + entries = defaultSkin.GetEntries(i); + foreach (var entry in entries) { + attachments.Add(entry.Attachment); + } + } } activeSkin = skeleton.Skin; 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..5e331684d 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs @@ -184,8 +184,12 @@ namespace Spine.Unity.Editor { if (targetAttribute.containsBoundingBoxes) { int slotIndex = i; var attachments = new List(); - foreach (var skin in data.Skins) - skin.FindAttachmentsForSlot(slotIndex, attachments); + foreach (var skin in data.Skins) { + var entries = skin.GetEntries(slotIndex); + foreach (var entry in entries) { + attachments.Add(entry.Attachment); + } + } bool hasBoundingBox = false; foreach (var attachment in attachments) { @@ -470,10 +474,19 @@ namespace Spine.Unity.Editor { attachmentNames.Clear(); placeholderNames.Clear(); - skin.FindNamesForSlot(i, attachmentNames); + var entries = skin.GetEntries(i); + foreach (var entry in entries) { + attachmentNames.Add(entry.Name); + } + if (skin != defaultSkin) { - defaultSkin.FindNamesForSlot(i, attachmentNames); - skin.FindNamesForSlot(i, placeholderNames); + foreach (var entry in entries) { + placeholderNames.Add(entry.Name); + } + entries = defaultSkin.GetEntries(i); + foreach (var entry in entries) { + attachmentNames.Add(entry.Name); + } } for (int a = 0; a < attachmentNames.Count; a++) { 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..1caccbb56 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,11 @@ 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 entries = skin.GetEntries(skeleton.FindSlotIndex(slot.Data.Name)); + foreach (var entry in entries) { + 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..ea79d9b6f 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 @@ -54,14 +54,17 @@ namespace Spine.Unity { using (var materialCache = new AtlasMaterialCache()) { var attachmentBuffer = 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(); - foreach (var skin in skeletonData.Skins) - skin.FindAttachmentsForSlot(i, attachmentBuffer); + foreach (var skin in skeletonData.Skins) { + var entries = skin.GetEntries(slotIndex); + foreach (var entry in entries) + attachmentBuffer.Add(entry.Attachment); + } Material templateMaterial = null; switch (slot.blendMode) { 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..5dedc2491 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 @@ -523,9 +523,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)); } } @@ -804,7 +804,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 +823,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) { 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..daaa4de1d 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 @@ -140,7 +140,9 @@ namespace Spine.Unity { void AddSkin (Skin skin, int slotIndex) { if (skin == null) return; var attachmentNames = new List(); - skin.FindNamesForSlot(slotIndex, attachmentNames); + var entries = skin.GetEntries(slotIndex); + foreach (var entry in entries) + attachmentNames.Add(entry.Name); foreach (var skinKey in attachmentNames) { var attachment = skin.GetAttachment(slotIndex, skinKey); 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..ed1676687 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 @@ -368,7 +368,10 @@ namespace Spine.Unity.Modules { var attachments = new List(); foreach (Slot s in skeleton.Slots) { if (s.Bone == b) { - skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(s), attachments); + var entries = skin.GetEntries(skeleton.Slots.IndexOf(s)); + foreach (var entry in entries) + attachments.Add(entry.Attachment); + foreach (var a in attachments) { var bbAttachment = a as BoundingBoxAttachment; if (bbAttachment != null) { 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..35f067093 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 @@ -363,7 +363,9 @@ namespace Spine.Unity.Modules { var attachments = new List(); foreach (Slot slot in skeleton.Slots) { if (slot.bone == b) { - skin.FindAttachmentsForSlot(skeleton.Slots.IndexOf(slot), attachments); + var entries = skin.GetEntries(skeleton.Slots.IndexOf(slot)); + foreach (var entry in entries) + attachments.Add(entry.Attachment); bool bbAttachmentAdded = false; foreach (var a in attachments) { 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 } } From 0791df8c4c8dd799b06d23cfa9711a5069230a81 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Mon, 20 May 2019 11:43:00 +0200 Subject: [PATCH 2/5] [csharp] Ported skin API changes part2, see #841. Port of changes of commit ff5b854860f639af1c111596bc407cbe12124d1e. --- spine-csharp/src/Skeleton.cs | 4 +- spine-csharp/src/Skin.cs | 53 ++++++++++++------- .../spine-unity/Editor/PointFollowerEditor.cs | 5 +- .../spine-unity/Editor/SkeletonBaker.cs | 30 +++++------ .../Editor/SkeletonDataAssetInspector.cs | 15 +++--- .../spine-unity/Editor/SkeletonDebugWindow.cs | 10 ++-- .../Editor/SpineAttributeDrawers.cs | 28 +++++----- .../Editor/SkeletonUtilityBoneInspector.cs | 6 ++- .../Asset Types/BlendModeMaterialsAsset.cs | 15 +++--- .../AttachmentTools/AttachmentTools.cs | 26 ++++----- .../BoundingBoxFollower.cs | 14 +++-- .../Modules/Ragdoll/SkeletonRagdoll.cs | 14 +++-- .../Modules/Ragdoll/SkeletonRagdoll2D.cs | 12 ++--- 13 files changed, 123 insertions(+), 109 deletions(-) diff --git a/spine-csharp/src/Skeleton.cs b/spine-csharp/src/Skeleton.cs index cd6fedef4..6385fd64b 100644 --- a/spine-csharp/src/Skeleton.cs +++ b/spine-csharp/src/Skeleton.cs @@ -235,8 +235,10 @@ namespace Spine { } private void SortPathConstraintAttachment (Skin skin, int slotIndex, Bone slotBone) { - foreach (var entry in skin.Attachments.Keys) + 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) { diff --git a/spine-csharp/src/Skin.cs b/spine-csharp/src/Skin.cs index 2eb6fa149..4771061c1 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,12 +39,11 @@ namespace Spine { /// public class Skin { internal string name; - private Dictionary attachments = - new Dictionary(SkinEntryComparer.Instance); + private OrderedDictionary attachments = new OrderedDictionary(SkinEntryComparer.Instance); // contains public string Name { get { return name; } } - public Dictionary Attachments { get { return attachments; } } - + public OrderedDictionary Attachments { get { return attachments; } } + public Skin (string name) { if (name == null) throw new ArgumentNullException("name", "name cannot be null."); this.name = name; @@ -56,19 +57,30 @@ namespace Spine { attachments[new SkinEntry(slotIndex, name, attachment)] = attachment; } - ///Adds all attachments from the specified skin to this skin. + ///Adds all attachments, bones, and constraints from the specified skin to this skin. public void AddSkin (Skin skin) { 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) { + // note: bones and constraints are added in a separate commit. + + 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; var lookup = new SkinEntry(slotIndex, name, null); - attachments.TryGetValue(lookup, out attachment); - return attachment; + 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. @@ -86,13 +98,11 @@ namespace Spine { return entries; } - /// Returns all instances for the given slot contained in this skin. + /// Returns all attachments for the given slot in this skin. /// The target slotIndex. To find the slot index, use or - public List GetEntries (int slotIndex) { - List entries = new List(); + public void GetAttachments (int slotIndex, List attachments) { foreach (SkinEntry entry in this.attachments.Keys) - if (entry.SlotIndex == slotIndex) entries.Add(entry); - return entries; + if (entry.SlotIndex == slotIndex) attachments.Add(entry); } override public string ToString () { @@ -144,18 +154,21 @@ namespace Spine { } } - // Avoids boxing in the dictionary. - class SkinEntryComparer : IEqualityComparer { + // 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 (SkinEntry o1, SkinEntry o2) { - if (o1.SlotIndex != o2.SlotIndex) return false; - if (!string.Equals(o1.Name, o2.Name, StringComparison.Ordinal)) return false; + 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 (SkinEntry o) { - return o.Name.GetHashCode() + o.SlotIndex * 37; + int IEqualityComparer.GetHashCode (object o) { + var e = (SkinEntry)o; + return e.Name.GetHashCode() + e.SlotIndex * 37; } } } 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 8cde89ae2..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,10 +111,10 @@ 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 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 c33a065db..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,13 +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(); - var entries = skin.GetEntries(s); - foreach (var entry in entries) - temp.Add(entry.Name); - 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); @@ -335,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); @@ -344,15 +342,17 @@ namespace Spine.Unity.Editor { List attachments = new List(); List attachmentNames = new List(); - var entries = skin.GetEntries(i); - foreach (var entry in entries) { + 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) { - entries = skeletonData.DefaultSkin.GetEntries(i); - foreach (var entry in entries) { + skinEntries.Clear(); + skeletonData.DefaultSkin.GetAttachments(slotIndex, skinEntries); + foreach (var entry in skinEntries) { attachments.Add(entry.Attachment); attachmentNames.Add(entry.Name); } @@ -388,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); @@ -418,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 ef26a2007..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,23 +498,26 @@ namespace Spine.Unity.Editor { using (new SpineInspectorUtility.IndentScope()) { { - var entries = skin.GetEntries(i); - foreach (var entry in entries) { + var skinEntries = new List(); + skin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { slotAttachments.Add(entry.Attachment); slotAttachmentNames.Add(entry.Name); } if (skin != defaultSkin) { - entries = defaultSkin.GetEntries(i); - foreach (var entry in entries) { + skinEntries.Clear(); + defaultSkin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { slotAttachments.Add(entry.Attachment); slotAttachmentNames.Add(entry.Name); defaultSkinAttachmentNames.Add(entry.Name); } } else { - entries = defaultSkin.GetEntries(i); - foreach (var entry in entries) { + 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 444c048b8..17bab0cd3 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDebugWindow.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonDebugWindow.cs @@ -575,13 +575,15 @@ namespace Spine.Unity.Editor { var attachments = new List(); attachmentTable.Add(skeleton.Slots.Items[i], attachments); // Add skin attachments. - var entries = skin.GetEntries(i); - foreach (var entry in entries) { + var skinEntries = new List(); + skin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { attachments.Add(entry.Attachment); } if (notDefaultSkin) { // Add default skin attachments. - entries = defaultSkin.GetEntries(i); - foreach (var entry in entries) { + skinEntries.Clear(); + defaultSkin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { attachments.Add(entry.Attachment); } } 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 5e331684d..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,23 +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(); + var skinEntries = new List(); foreach (var skin in data.Skins) { - var entries = skin.GetEntries(slotIndex); - foreach (var entry in entries) { - attachments.Add(entry.Attachment); - } + 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)); @@ -474,17 +470,19 @@ namespace Spine.Unity.Editor { attachmentNames.Clear(); placeholderNames.Clear(); - var entries = skin.GetEntries(i); - foreach (var entry in entries) { + var skinEntries = new List(); + skin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { attachmentNames.Add(entry.Name); } if (skin != defaultSkin) { - foreach (var entry in entries) { + foreach (var entry in skinEntries) { placeholderNames.Add(entry.Name); } - entries = defaultSkin.GetEntries(i); - foreach (var entry in entries) { + skinEntries.Clear(); + defaultSkin.GetAttachments(i, skinEntries); + foreach (var entry in skinEntries) { attachmentNames.Add(entry.Name); } } 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 1caccbb56..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,8 +81,10 @@ namespace Spine.Unity.Editor { Slot slot = skeletonUtility.skeletonRenderer.skeleton.Slots.Items[i]; if (slot.Bone == utilityBone.bone) { var slotAttachments = new List(); - var entries = skin.GetEntries(skeleton.FindSlotIndex(slot.Data.Name)); - foreach (var entry in entries) { + var skinEntries = new List(); + int slotIndex = skeleton.FindSlotIndex(slot.Data.Name); + skin.GetAttachments(slotIndex, skinEntries); + foreach (var entry in skinEntries) { slotAttachments.Add(entry.Attachment); } 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 ea79d9b6f..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,19 +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 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(); - foreach (var skin in skeletonData.Skins) { - var entries = skin.GetEntries(slotIndex); - foreach (var entry in entries) - attachmentBuffer.Add(entry.Attachment); - } + entryBuffer.Clear(); + foreach (var skin in skeletonData.Skins) + skin.GetAttachments(slotIndex, entryBuffer); Material templateMaterial = null; switch (slot.blendMode) { @@ -80,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 5dedc2491..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)) { @@ -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; @@ -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 daaa4de1d..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,13 +139,11 @@ namespace Spine.Unity { void AddSkin (Skin skin, int slotIndex) { if (skin == null) return; - var attachmentNames = new List(); - var entries = skin.GetEntries(slotIndex); - foreach (var entry in entries) - attachmentNames.Add(entry.Name); - - 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) @@ -159,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 ed1676687..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,17 +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) { - var entries = skin.GetEntries(skeleton.Slots.IndexOf(s)); - foreach (var entry in entries) - attachments.Add(entry.Attachment); - - 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 35f067093..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,18 +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) { - var entries = skin.GetEntries(skeleton.Slots.IndexOf(slot)); - foreach (var entry in entries) - attachments.Add(entry.Attachment); + 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; From 1eea0c24c98c1f2191045cb9fddf902239cd30db Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Mon, 20 May 2019 15:43:19 +0200 Subject: [PATCH 3/5] [unity] Corrected Timeline features documentation file, contained outdated removed features. --- .../Modules/Timeline/Documentation/README.md | 57 ++++++------------ .../Timeline/Documentation/add-menu.png | Bin 4158 -> 6225 bytes .../animationstate-clip-inspector.png | Bin 13951 -> 26539 bytes 3 files changed, 18 insertions(+), 39 deletions(-) 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 b8e383f3c75afec4bbdfdf712ff7aaf2011b7326..f2277f2fbde581ceba213a1a85aa313dcbc22735 100644 GIT binary patch literal 6225 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 YxJ=%yt?@7rc4q@*BoxJK#f(4yA5M$AxBvhE literal 4158 zcma)QZ!Tp2^T4%m#P$jfJl?x zgOq?My@e`-*MQ-}>HK?~k0Eea=36_ROC7%}mrSQzITOVJ-jw zc&-`in*#s~1H8B7JP2Mp5jvLO9}CRf=qga!e`*07us^zDas>b?V!3zzJOGXlc^lt{ z0l<+j`#+WruMbWD02#iff5jpIN=DheIA?UaV^fwcZ+JM>~iO=MgAx-<6*d zN2f6Zi~AT)nfYvNKS=xp6I6vev9h#DDe{Uc^L~qDr(EM~Vh^rs zwdZ4V-YcxHN8V)?{VaXEfGb^C`4pM9Sh&qBTqs^xda^}zzT2@>)?7LnylLB) zC}U;$vX2{>7pea$8oYLUede`UA&rDBx{)kbAP5b((kLI|m#z*oJUjYC4%+x(USgDo zCzX4F&U=5(Mt1`=j8k)v7$ZQ8aja7WDChODWdnw;KJ|GZ1_usuHkYyhOpZzpK%WiA z7AGK+$XvtjL8W!z z4i3f}Qqk;AkWav0%Phv+^$+uh3&!#I;i1~k$&IosI5q`cP}WdrDGr4?UDMD|6yDt2 z48gGhi@)dBqS4i>uHj}yjP)eunqo~wMLaEKJYn4XA!(%VZw;`ywV+_9EoM#-46!4{_K!Op)ecjpMVaS=TdNF8Y% zE^mhg6dS`!aqVbJMd~+x4D<&rGPa)nS?yauhZ2qVHkss6uoQdbL$gnBJJ!3ut<_5; zJ4{ejKDI;IQpsqPfol!U*3V=T>^VCL7$HMA8pk?iobpUVWf z>oq%b=wcDKw#BzU5*3TMAbHsHWajDE{wpi^e(4?$QeO{;We;0ykX0~z zC+wcl+Kml;X4;N0 z5t;aCyIwPA?W>M&;;3OFya5krn-(6A@G0BW72b=lSh}^P>`a9J4E)@6-r4f3B-GC` zU!DK{U0QVv;aiLrpM~W*nnzTW5V$(M5P29MLpnUei6%K7?savUq!LFtBnMOonN2yS z!OXO6rJ#b0`OIY)HPPL-#^v2;USq;qTVgUMvE1<9lkEqa&s3n*MNtI5Q|9VOA%*G} zS=cuIkC#Qwarp*t*Pzcr0ekUuF(9eM+c`7Ks@A%*DTR(!DWl=HhNf8)-+gREMhurp zBFJjJb#X%EOoN?JH_?jz6S105MWb>|ro2Kv4=rymrZO#jfulYzdOm8hTRWStL|&}P zZN^t+nyWiXk6bdMhPE@=nPNm#D#>hK*1GBpZgpM+)Cz6(76?|rs8BQx&?w0ZT zb)aEe^-0M!13PqtC@t=G^%L~O(sYCGws=-&OH9R6TcjYgqunYnAns|{cv?yyELFTO zjW(zoFbhfQscL4V1x(~yRgS&4-$OK#VKhC*yi#7&St&CM#QmP@FTFUsq#M8Rc7Inm zlK(iTpo7Tt*AX^THaUw6MO^F=m%Q@Lz2fxrbn#UIPaf$r% z?u>DGQ5oj0#~#(p;d}kCJTB7Zm;msFrE;wJujO?CO@hRH2snu|fk|rKF1f-bx(`?} zxJh?qEq>lM2Fb5J^xVm;O#ELjfVtN045;35gfK|$dmvcn!g|2*F>NP^t~H41-Y9wa zGadtaoEG4^&t9p78a*;MmnXswNK>*P$O~i7=<6H!y<1~;$f9RcEs?E41+4qIM(PaX z`E$0K+S+_SfB%}*+S=N605Fb<4O*+WSy(Tto!oAyr?ktzR5U-Zzduv`jmXGIjiZM$ zE$MHBLPqG{Ml-v7J)tX@O~kHg0p62w{eZM#;eMiOi|X8+pEu@4B9V4d>|zGmh(4aW z@#y)o`bF#YG{P=4dF7+y8(Rwa=Vv(rMUCpp&^yFp9PO@6QJ3|T*%l?6uK8(UmcL>~yiU(2F4qjC z_*7DJ=oj$(SBW2jP)mgi_v~BhJk$K+DYA?SF?fBFgwc&&2{`bTB>}_FtkTiWkNpnA zs*V(uHRXkyt@!(86s|qscqRe2cHNk2z%DSBTx(qS-i-U0b+B^xt2hz-_YZxhw4rJ4 zMaH|$Ow12%>kZ^qkKL$=YH-&=l^hsl+j+7cOLMBU6D5Ys5<5gTmSEORc9lwk_KlN? z1L*bNG>3_QZ4+#7Y1izx>|0yC1>)T6|KR*=$Bg$2%ffsQC}o%}>!# zq@+jg7yMg|+o3U2Z|S~u9y^Vw=xn@Vg$7d%aagDJWOGVpcTiaS{P9%xjAUZ0xN_6q z13PXxO}-Ebbjb;?8kC3QVh`-T8EAr}UM%v-m9n2^nTT7=nTf!Ck!Bm}TNf(opDGP> zmsyi)9i1PIzCByKyHf(k2+NO;wt8tEDM;5jEM(m}K$_;V$$%7G98ef5X&-xv;E`=V zKg-vjwka9~VO`?buXXZjE3xMdmU=?UrUGoE^+VrJ-GWONvEo7Qt_JJjP6!gNx!^m> z)hh86EO(AYkR_uKlbJ~8JnyV9Lyk4QI(pL@A_MDM)+ z7q#@tqbVY>n*^rI7;Bo_0?mB;+YZz_y8{+F`T7RgvMB z2+u%r0Ph!)m-XnE0Q-+%P=&aFstmFIZ>0$0MJ!brv{Lzhcq!cZ|JRPcEb<#p!24en zAXu03^8CivzsVWa#0`(72@u@APBR8kWxBSk4!hs%#^C17lgZTaijx=Y9%yz1@e@3y z+LIX7Y|RG)MShzbBXATfbYG*;tl3$JV`4zlZ!iORJ>N=(z8) zs2zXHPKCMKa1-}nxPzR`SY^jV+o@umUm8NYpoE=V@(&v>CF?-OgH9WvEylGv{*~4t z8UF?z* z=it6N%?G<1jmjNvmo6;z)@?ab7{H-@{Y{#k%-)!LJ13v6D)N+Vi)&0#SR<{lusrgu z{C0J#psb)s1lwb96M56b6y7$8Kf%m=n# z{-J{-%@j=(79Xw-Ef#!hwPD$pT7Neu4mrauBUEJq^(wAM$#`#_%6-~P8G!4(z4O^i zer6bQX2XW2@na%jue&kS`X5sz2Dek3&b?B!lWy;U9y1kEMM~zx%aS~V&wVxYX}bp( zXXNpt36nCKB;5*X{kS^s9RCQ&c+i6qmmj@;8PF~3h2DnEw1nT0Ix8Qc?}0cT$!^om z+4lH#a;-F;|2 zQD87f91ev13OiT=_+9mW&(yo`w8H!TU$^haY~VR6sM>(TU9g$@ci;Z52K-9KfIx(& z$nb&%d3pImNr`MtU0s2foIS#`nduj8(Gf`h5jhIOYDsup`|zKwHGvUm)602 z)|ey#?l5F-YD$+(CKr7FZVN(aa`I~tr-F_@A-LACka&5QPF!pgwBuWfIM^5N6ZC=( zyD}zWo9To4xv~P503iWj$F}bFm*=_ar|(LxmJ8*ZqK1rp(;sC78e+EQ(j2}IysL|g zfgDB3&DZBFy)L~|4+Rz1RrVpiJIPrhH2;uQfV8$SEL;#n+o55L{3ipAicbnF3l34l z&-5K2f4V=oFU4iK@5@!49X0{tE@Nd3T%TJ|?gK=duKTaK)7Wri^!2ePoS?}7jp^RE zO5lnx%Cv!je1WISfQ@lSWalN)K5ar`LY9etE#Ck&2aJJEZIRBV{c{Up|8E~V1;U=( bXNvw|u#lKt>Fy2o{J=E>Q~lDb_M!g+?Ri8I 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 f5b5702c496fac8dd23e079ab1dd6a600ca221e5..c13b9d4f8270bb7e52ed5b445938ec5bb21ed963 100644 GIT binary patch literal 26539 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 literal 13951 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 From c63bc7b88f7202a0c7718b6a724040a4e314c28f Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 22 May 2019 18:29:54 +0200 Subject: [PATCH 4/5] [csharp] See #1346: Port bone/constraint association with skins. Also contains second (and final) partial port of commit ff5b854. Adapted spine-unity for skin changes. Fixed a bug in Skin setter property. --- spine-csharp/src/Animation.cs | 2 +- .../src/Attachments/RegionAttachment.cs | 4 +- .../src/Attachments/VertexAttachment.cs | 3 +- spine-csharp/src/Bone.cs | 2 +- spine-csharp/src/BoneData.cs | 8 +- .../src/{IConstraint.cs => ConstraintData.cs} | 37 +++- spine-csharp/src/EventData.cs | 2 +- spine-csharp/src/IkConstraint.cs | 7 +- spine-csharp/src/IkConstraintData.cs | 22 +-- spine-csharp/src/PathConstraint.cs | 7 +- spine-csharp/src/PathConstraintData.cs | 18 +- spine-csharp/src/Skeleton.cs | 49 +++-- spine-csharp/src/SkeletonBinary.cs | 26 ++- spine-csharp/src/SkeletonJson.cs | 178 +++++++++++------- spine-csharp/src/Skin.cs | 28 ++- spine-csharp/src/SlotData.cs | 6 +- spine-csharp/src/TransformConstraint.cs | 3 +- spine-csharp/src/TransformConstraintData.cs | 14 +- .../Editor/SkeletonRendererInspector.cs | 11 +- .../Editor/SpineEditorUtilities.cs | 7 +- 20 files changed, 260 insertions(+), 174 deletions(-) rename spine-csharp/src/{IConstraint.cs => ConstraintData.cs} (59%) 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/RegionAttachment.cs b/spine-csharp/src/Attachments/RegionAttachment.cs index f591de110..021069298 100644 --- a/spine-csharp/src/Attachments/RegionAttachment.cs +++ b/spine-csharp/src/Attachments/RegionAttachment.cs @@ -195,8 +195,8 @@ namespace Spine { copy.rotation = rotation; copy.width = width; copy.height = height; - Array.Copy(uvs, 0, copy.uvs, 0, uvs.Length); - Array.Copy(offset, 0, copy.offset, 0, offset.Length); + 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 d951248b8..71bb42ea0 100644 --- a/spine-csharp/src/Attachments/VertexAttachment.cs +++ b/spine-csharp/src/Attachments/VertexAttachment.cs @@ -133,8 +133,7 @@ namespace Spine { return this == sourceAttachment; } - ///Internal method used by VertexAttachment subclasses to copy basic data. Does not copy id (generated) and name (set on - /// construction). + ///Does not copy id (generated) or name (set on construction). internal void CopyTo (VertexAttachment attachment) { if (bones != null) { attachment.bones = new int[bones.Length]; 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 6385fd64b..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; @@ -269,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; } @@ -295,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 @@ -315,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; @@ -447,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 @@ -456,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); @@ -472,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. @@ -528,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; } @@ -539,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 126149461..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,11 +316,19 @@ 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); diff --git a/spine-csharp/src/SkeletonJson.cs b/spine-csharp/src/SkeletonJson.cs index 01a132995..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.SetAttachment(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 4771061c1..e57b3d33d 100644 --- a/spine-csharp/src/Skin.cs +++ b/spine-csharp/src/Skin.cs @@ -40,9 +40,13 @@ namespace Spine { public class Skin { internal string name; 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 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."); @@ -59,13 +63,23 @@ namespace Spine { ///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) { - // note: bones and constraints are added in a separate commit. + 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(); @@ -98,13 +112,20 @@ namespace Spine { return entries; } - /// Returns all attachments for the given slot in this skin. + /// Returns all attachments in this skin for the specified slot index. /// The target slotIndex. To find the slot index, use or 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 () { return name; } @@ -141,6 +162,7 @@ namespace Spine { } } + /// The name the attachment is associated with, equivalent to the skin placeholder name in the Spine editor. public String Name { get { return name; 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/Editor/spine-unity/Editor/SkeletonRendererInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs index 567d44611..025e4aaa4 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs @@ -504,8 +504,17 @@ namespace Spine.Unity.Editor { 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/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); From b23ef5ef1af4599f52e5cf13fe5888834dec3623 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 24 May 2019 11:47:14 +0200 Subject: [PATCH 5/5] [unity] Fixed game and scene view updates on skin change for SkeletonRenderer. Closes #1361 --- .../Editor/SkeletonRendererInspector.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) 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 025e4aaa4..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,12 +479,26 @@ 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);