Browse Source

[unity] Split the file `AttachmentTools` into 4 new files for each contained class.

Harald Csaszar 6 years ago
parent
commit
dad97ecc06

+ 1 - 0
CHANGELOG.md

@@ -183,6 +183,7 @@
   * Moved `Modules/AnimationMatchModifier` directory to `Spine Examples/Scripts/MecanimAnimationMatchModifier`.
   * Moved `SkeletonRagdoll` and `SkeletonRagdoll2D` components from `Modules/Ragdoll` directory to `Spine Examples/Scripts/Sample Components/SkeletonUtility Modules`.
   * Moved `AttachmentTools.cs` to `Utility` directory.
+  * Split the file `AttachmentTools` into 4 new files for each contained class. No namespace or other API changes performed.
   * Moved `SkeletonExtensions.cs` to `Utility` directory.
   * Moved `Modules/YieldInstructions` directory to `Utility/YieldInstructions`.
   * Moved corresponding editor scripts of the above components to restructured directories as well.

+ 1 - 381
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentTools.cs → spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs

@@ -32,179 +32,7 @@ using System.Collections.Generic;
 using System.Collections;
 
 namespace Spine.Unity.AttachmentTools {
-	public static class AttachmentRegionExtensions {
-		#region GetRegion
-		/// <summary>
-		/// Tries to get the region (image) of a renderable attachment. If the attachment is not renderable, it returns null.</summary>
-		public static AtlasRegion GetRegion (this Attachment attachment) {
-			var renderableAttachment = attachment as IHasRendererObject;
-			if (renderableAttachment != null)
-				return renderableAttachment.RendererObject as AtlasRegion;
-
-			return null;
-		}
-
-		/// <summary>Gets the region (image) of a RegionAttachment</summary>
-		public static AtlasRegion GetRegion (this RegionAttachment regionAttachment) {
-			return regionAttachment.RendererObject as AtlasRegion;
-		}
-
-		/// <summary>Gets the region (image) of a MeshAttachment</summary>
-		public static AtlasRegion GetRegion (this MeshAttachment meshAttachment) {
-			return meshAttachment.RendererObject as AtlasRegion;
-		}
-		#endregion
-		#region SetRegion
-		/// <summary>
-		/// Tries to set the region (image) of a renderable attachment. If the attachment is not renderable, nothing is applied.</summary>
-		public static void SetRegion (this Attachment attachment, AtlasRegion region, bool updateOffset = true) {
-			var regionAttachment = attachment as RegionAttachment;
-			if (regionAttachment != null)
-				regionAttachment.SetRegion(region, updateOffset);
-
-			var meshAttachment = attachment as MeshAttachment;
-			if (meshAttachment != null)
-				meshAttachment.SetRegion(region, updateOffset);
-		}
-
-		/// <summary>Sets the region (image) of a RegionAttachment</summary>
-		public static void SetRegion (this RegionAttachment attachment, AtlasRegion region, bool updateOffset = true) {
-			if (region == null) throw new System.ArgumentNullException("region"); 
-
-			// (AtlasAttachmentLoader.cs)
-			attachment.RendererObject = region;
-			attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
-			attachment.regionOffsetX = region.offsetX;
-			attachment.regionOffsetY = region.offsetY;
-			attachment.regionWidth = region.width;
-			attachment.regionHeight = region.height;
-			attachment.regionOriginalWidth = region.originalWidth;
-			attachment.regionOriginalHeight = region.originalHeight;
-
-			if (updateOffset) attachment.UpdateOffset();
-		}
-
-		/// <summary>Sets the region (image) of a MeshAttachment</summary>
-		public static void SetRegion (this MeshAttachment attachment, AtlasRegion region, bool updateUVs = true) {
-			if (region == null) throw new System.ArgumentNullException("region"); 
-
-			// (AtlasAttachmentLoader.cs)
-			attachment.RendererObject = region;
-			attachment.RegionU = region.u;
-			attachment.RegionV = region.v;
-			attachment.RegionU2 = region.u2;
-			attachment.RegionV2 = region.v2;
-			attachment.RegionRotate = region.rotate;
-			attachment.regionOffsetX = region.offsetX;
-			attachment.regionOffsetY = region.offsetY;
-			attachment.regionWidth = region.width;
-			attachment.regionHeight = region.height;
-			attachment.regionOriginalWidth = region.originalWidth;
-			attachment.regionOriginalHeight = region.originalHeight;
-
-			if (updateUVs) attachment.UpdateUVs();
-		}
-		#endregion
-
-		#region Runtime RegionAttachments
-		/// <summary>
-		/// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses a new AtlasPage with the Material provided./// </summary>
-		public static RegionAttachment ToRegionAttachment (this Sprite sprite, Material material, float rotation = 0f) {
-			return sprite.ToRegionAttachment(material.ToSpineAtlasPage(), rotation);
-		}
-
-		/// <summary>
-		/// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses the AtlasPage provided./// </summary>
-		public static RegionAttachment ToRegionAttachment (this Sprite sprite, AtlasPage page, float rotation = 0f) {
-			if (sprite == null) throw new System.ArgumentNullException("sprite");
-			if (page == null) throw new System.ArgumentNullException("page");
-			var region = sprite.ToAtlasRegion(page);
-			var unitsPerPixel = 1f / sprite.pixelsPerUnit;
-			return region.ToRegionAttachment(sprite.name, unitsPerPixel, rotation);
-		}
-
-		/// <summary>
-		/// Creates a Spine.AtlasRegion that uses a premultiplied alpha duplicate texture of the Sprite's texture data. Returns a RegionAttachment that uses it. Use this if you plan to use a premultiply alpha shader such as "Spine/Skeleton"</summary>
-		public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Shader shader, TextureFormat textureFormat = AtlasUtilities.SpineTextureFormat, bool mipmaps = AtlasUtilities.UseMipMaps, Material materialPropertySource = null, float rotation = 0f) {
-			if (sprite == null) throw new System.ArgumentNullException("sprite");
-			if (shader == null) throw new System.ArgumentNullException("shader");
-			var region = sprite.ToAtlasRegionPMAClone(shader, textureFormat, mipmaps, materialPropertySource);
-			var unitsPerPixel = 1f / sprite.pixelsPerUnit;
-			return region.ToRegionAttachment(sprite.name, unitsPerPixel, rotation);
-		}
-
-		public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Material materialPropertySource, TextureFormat textureFormat = AtlasUtilities.SpineTextureFormat, bool mipmaps = AtlasUtilities.UseMipMaps, float rotation = 0f) {
-			return sprite.ToRegionAttachmentPMAClone(materialPropertySource.shader, textureFormat, mipmaps, materialPropertySource, rotation);
-		}
-
-		/// <summary>
-		/// Creates a new RegionAttachment from a given AtlasRegion.</summary>
-		public static RegionAttachment ToRegionAttachment (this AtlasRegion region, string attachmentName, float scale = 0.01f, float rotation = 0f) {
-			if (string.IsNullOrEmpty(attachmentName)) throw new System.ArgumentException("attachmentName can't be null or empty.", "attachmentName");
-			if (region == null) throw new System.ArgumentNullException("region");
-
-			// (AtlasAttachmentLoader.cs)
-			var attachment = new RegionAttachment(attachmentName);
-
-			attachment.RendererObject = region;
-			attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
-			attachment.regionOffsetX = region.offsetX;
-			attachment.regionOffsetY = region.offsetY;
-			attachment.regionWidth = region.width;
-			attachment.regionHeight = region.height;
-			attachment.regionOriginalWidth = region.originalWidth;
-			attachment.regionOriginalHeight = region.originalHeight;
-
-			attachment.Path = region.name;
-			attachment.scaleX = 1;
-			attachment.scaleY = 1;
-			attachment.rotation = rotation;
-
-			attachment.r = 1;
-			attachment.g = 1;
-			attachment.b = 1;
-			attachment.a = 1;
-
-			// pass OriginalWidth and OriginalHeight because UpdateOffset uses it in its calculation.
-			attachment.width = attachment.regionOriginalWidth * scale;
-			attachment.height = attachment.regionOriginalHeight * scale;
-
-			attachment.SetColor(Color.white);
-			attachment.UpdateOffset();
-			return attachment;
-		}
-
-		/// <summary> Sets the scale. Call regionAttachment.UpdateOffset to apply the change.</summary>
-		public static void SetScale (this RegionAttachment regionAttachment, Vector2 scale) {
-			regionAttachment.scaleX = scale.x;
-			regionAttachment.scaleY = scale.y;
-		}
-
-		/// <summary> Sets the scale. Call regionAttachment.UpdateOffset to apply the change.</summary>
-		public static void SetScale (this RegionAttachment regionAttachment, float x, float y) {
-			regionAttachment.scaleX = x;
-			regionAttachment.scaleY = y;
-		}
-
-		/// <summary> Sets the position offset. Call regionAttachment.UpdateOffset to apply the change.</summary>
-		public static void SetPositionOffset (this RegionAttachment regionAttachment, Vector2 offset) {
-			regionAttachment.x = offset.x;
-			regionAttachment.y = offset.y;
-		}
-
-		/// <summary> Sets the position offset. Call regionAttachment.UpdateOffset to apply the change.</summary>
-		public static void SetPositionOffset (this RegionAttachment regionAttachment, float x, float y) {
-			regionAttachment.x = x;
-			regionAttachment.y = y;
-		}
-
-		/// <summary> Sets the rotation. Call regionAttachment.UpdateOffset to apply the change.</summary>
-		public static void SetRotation (this RegionAttachment regionAttachment, float rotation) {
-			regionAttachment.rotation = rotation;
-		}
-		#endregion
-	}
-
+	
 	public static class AtlasUtilities {
 		internal const TextureFormat SpineTextureFormat = TextureFormat.RGBA32;
 		internal const float DefaultMipmapBias = -0.5f;
@@ -753,212 +581,4 @@ namespace Spine.Unity.AttachmentTools {
 			return (value - a) / (b - a);
 		}
 	}
-
-	public static class SkinUtilities {
-
-		#region Skeleton Skin Extensions
-		/// <summary>
-		/// Convenience method for duplicating a skeleton's current active skin so changes to it will not affect other skeleton instances. .</summary>
-		public static Skin UnshareSkin (this Skeleton skeleton, bool includeDefaultSkin, bool unshareAttachments, AnimationState state = null) {
-			// 1. Copy the current skin and set the skeleton's skin to the new one.
-			var newSkin = skeleton.GetClonedSkin("cloned skin", includeDefaultSkin, unshareAttachments, true);
-			skeleton.SetSkin(newSkin);
-
-			// 2. Apply correct attachments: skeleton.SetToSetupPose + animationState.Apply
-			if (state != null) {
-				skeleton.SetToSetupPose();
-				state.Apply(skeleton);
-			}
-
-			// 3. Return unshared skin.
-			return newSkin;
-		}
-
-		public static Skin GetClonedSkin (this Skeleton skeleton, string newSkinName, bool includeDefaultSkin = false, bool cloneAttachments = false, bool cloneMeshesAsLinked = true) {
-			var newSkin = new Skin(newSkinName); // may have null name. Harmless.
-			var defaultSkin = skeleton.data.DefaultSkin;
-			var activeSkin = skeleton.skin;
-
-			if (includeDefaultSkin)
-				defaultSkin.CopyTo(newSkin, true, cloneAttachments, cloneMeshesAsLinked);
-
-			if (activeSkin != null)
-				activeSkin.CopyTo(newSkin, true, cloneAttachments, cloneMeshesAsLinked);
-
-			return newSkin;
-		}
-		#endregion
-
-		/// <summary>
-		/// Gets a shallow copy of the skin. The cloned skin's attachments are shared with the original skin.</summary>
-		public static Skin GetClone (this Skin original) {
-			var newSkin = new Skin(original.name + " clone");
-			var newSkinAttachments = newSkin.Attachments;
-
-			foreach (DictionaryEntry a in original.Attachments)
-				newSkinAttachments[a.Key] = a.Value;
-
-			return newSkin;
-		}
-
-		/// <summary>Adds an attachment to the skin for the specified slot index and name. If the name already exists for the slot, the previous value is replaced.</summary>
-		public static void SetAttachment (this Skin skin, string slotName, string keyName, Attachment attachment, Skeleton skeleton) {
-			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.SetAttachment(slotIndex, keyName, attachment);
-		}
-
-		/// <summary>Adds skin items from another skin. For items that already exist, the previous values are replaced.</summary>
-		public static void AddAttachments (this Skin skin, Skin otherSkin) {
-			if (otherSkin == null) return;
-			otherSkin.CopyTo(skin, true, false);
-		}
-
-		/// <summary>Gets an attachment from the skin for the specified slot index and name.</summary>
-		public static Attachment GetAttachment (this Skin skin, string slotName, string keyName, Skeleton skeleton) {
-			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");
-			return skin.GetAttachment(slotIndex, keyName);
-		}
-
-		/// <summary>Adds an attachment to the skin for the specified slot index and name. If the name already exists for the slot, the previous value is replaced.</summary>
-		public static void SetAttachment (this Skin skin, int slotIndex, string keyName, Attachment attachment) {
-			skin.SetAttachment(slotIndex, keyName, attachment);
-		}
-		
-		public static void RemoveAttachment (this Skin skin, string slotName, string keyName, SkeletonData skeletonData) {
-			int slotIndex = skeletonData.FindSlotIndex(slotName);
-			if (skeletonData == null) throw new System.ArgumentNullException("skeletonData", "skeletonData cannot be null.");
-			if (slotIndex == -1) throw new System.ArgumentException(string.Format("Slot '{0}' does not exist in skeleton.", slotName), "slotName");
-			skin.RemoveAttachment(slotIndex, keyName);
-		}
-
-		public static void Clear (this Skin skin) {
-			skin.Attachments.Clear();
-		}
-
-		//[System.Obsolete]
-		public static void Append (this Skin destination, Skin source) {
-			source.CopyTo(destination, true, false);
-		}
-
-		public static void CopyTo (this Skin source, Skin destination, bool overwrite, bool cloneAttachments, bool cloneMeshesAsLinked = true) {
-			var sourceAttachments = source.Attachments;
-			var destinationAttachments = destination.Attachments;
-
-			if (cloneAttachments) {
-				if (overwrite) {
-					foreach (DictionaryEntry e in sourceAttachments)
-						destinationAttachments[e.Key] = ((Attachment)e.Value).GetCopy(cloneMeshesAsLinked);
-				} else {
-					foreach (DictionaryEntry e in sourceAttachments) {
-						if (destinationAttachments.Contains(e.Key)) continue;
-						destinationAttachments.Add(e.Key, ((Attachment)e.Value).GetCopy(cloneMeshesAsLinked));
-					}
-				}
-			} else {
-				if (overwrite) {
-					foreach (DictionaryEntry e in sourceAttachments)
-						destinationAttachments[e.Key] = e.Value;
-				} else {
-					foreach (DictionaryEntry e in sourceAttachments) {
-						if (destinationAttachments.Contains(e.Key)) continue;
-						destinationAttachments.Add(e.Key, e.Value);
-					}
-				}
-			}
-		}
-
-
-	}
-
-	public static class AttachmentCloneExtensions {
-		/// <summary>
-		/// Clones the attachment.</summary>
-		public static Attachment GetCopy (this Attachment o, bool cloneMeshesAsLinked) {
-			var meshAttachment = o as MeshAttachment;
-			if (meshAttachment != null && cloneMeshesAsLinked)
-				return meshAttachment.NewLinkedMesh();
-			return o.Copy();
-		}
-
-		#region Runtime Linked MeshAttachments
-		/// <summary>
-		/// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to the AtlasRegion provided.</summary>
-		public static MeshAttachment GetLinkedMesh (this MeshAttachment o, string newLinkedMeshName, AtlasRegion region) {
-			if (region == null) throw new System.ArgumentNullException("region");
-			MeshAttachment mesh = o.NewLinkedMesh();
-			mesh.SetRegion(region, false);
-			return mesh;
-		}
-
-		/// <summary>
-		/// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to an AtlasRegion generated from a Sprite. The AtlasRegion will be mapped to a new Material based on the shader.
-		/// For better caching and batching, use GetLinkedMesh(string, AtlasRegion, bool)</summary>
-		public static MeshAttachment GetLinkedMesh (this MeshAttachment o, Sprite sprite, Shader shader, Material materialPropertySource = null) {
-			var m = new Material(shader);
-			if (materialPropertySource != null) {
-				m.CopyPropertiesFromMaterial(materialPropertySource);
-				m.shaderKeywords = materialPropertySource.shaderKeywords;
-			}
-			return o.GetLinkedMesh(sprite.name, sprite.ToAtlasRegion());
-		}
-
-		/// <summary>
-		/// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to an AtlasRegion generated from a Sprite. The AtlasRegion will be mapped to a new Material based on the shader.
-		/// For better caching and batching, use GetLinkedMesh(string, AtlasRegion, bool)</summary>
-		public static MeshAttachment GetLinkedMesh (this MeshAttachment o, Sprite sprite, Material materialPropertySource) {
-			return o.GetLinkedMesh(sprite, materialPropertySource.shader, materialPropertySource);
-		}
-		#endregion
-
-		#region RemappedClone Convenience Methods
-		/// <summary>
-		/// Gets a clone of the attachment remapped with a sprite image.</summary>
-		/// <returns>The remapped clone.</returns>
-		/// <param name="o">The original attachment.</param>
-		/// <param name="sprite">The sprite whose texture to use.</param>
-		/// <param name="sourceMaterial">The source material used to copy the shader and material properties from.</param>
-		/// <param name="premultiplyAlpha">If <c>true</c>, a premultiply alpha clone of the original texture will be created.</param>
-		/// <param name="cloneMeshAsLinked">If <c>true</c> MeshAttachments will be cloned as linked meshes and will inherit animation from the original attachment.</param>
-		/// <param name="useOriginalRegionSize">If <c>true</c> the size of the original attachment will be followed, instead of using the Sprite size.</param>
-		public static Attachment GetRemappedClone (this Attachment o, Sprite sprite, Material sourceMaterial, bool premultiplyAlpha = true, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false) {
-			var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(new Material(sourceMaterial) { mainTexture = sprite.texture } );
-			return o.GetRemappedClone(atlasRegion, cloneMeshAsLinked, useOriginalRegionSize, 1f/sprite.pixelsPerUnit);
-		}
-
-		/// <summary>
-		/// Gets a clone of the attachment remapped with an atlasRegion image.</summary>
-		/// <returns>The remapped clone.</returns>
-		/// <param name="o">The original attachment.</param>
-		/// <param name="atlasRegion">Atlas region.</param>
-		/// <param name="cloneMeshAsLinked">If <c>true</c> MeshAttachments will be cloned as linked meshes and will inherit animation from the original attachment.</param>
-		/// <param name="useOriginalRegionSize">If <c>true</c> the size of the original attachment will be followed, instead of using the Sprite size.</param>
-		/// <param name="scale">Unity units per pixel scale used to scale the atlas region size when not using the original region size.</param>
-		public static Attachment GetRemappedClone (this Attachment o, AtlasRegion atlasRegion, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false, float scale = 0.01f) {
-			var regionAttachment = o as RegionAttachment;
-			if (regionAttachment != null) {
-				RegionAttachment newAttachment = (RegionAttachment)regionAttachment.Copy();
-				newAttachment.SetRegion(atlasRegion, false);
-				if (!useOriginalRegionSize) {
-					newAttachment.width = atlasRegion.width * scale;
-					newAttachment.height = atlasRegion.height * scale;
-				}
-				newAttachment.UpdateOffset();
-				return newAttachment;
-			} else {
-				var meshAttachment = o as MeshAttachment;
-				if (meshAttachment != null) {
-					MeshAttachment newAttachment = cloneMeshAsLinked ? meshAttachment.NewLinkedMesh() : (MeshAttachment)meshAttachment.Copy();
-					newAttachment.SetRegion(atlasRegion);
-					return newAttachment;
-				}
-			}
-
-			return o.GetCopy(true); // Non-renderable Attachments will return as normal cloned attachments.
-		}
-		#endregion
-	}
 }

+ 2 - 2
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentTools.cs.meta → spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AtlasUtilities.cs.meta

@@ -1,6 +1,6 @@
 fileFormatVersion: 2
-guid: 8dd46dbf979fcb7459246cd37aad09ef
-timeCreated: 1478437807
+guid: 25ceef568a3dad448bf8a14fcc326964
+timeCreated: 1563321428
 licenseType: Free
 MonoImporter:
   serializedVersion: 2

+ 123 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentCloneExtensions.cs

@@ -0,0 +1,123 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace Spine.Unity.AttachmentTools {
+	
+	public static class AttachmentCloneExtensions {
+		/// <summary>
+		/// Clones the attachment.</summary>
+		public static Attachment GetCopy (this Attachment o, bool cloneMeshesAsLinked) {
+			var meshAttachment = o as MeshAttachment;
+			if (meshAttachment != null && cloneMeshesAsLinked)
+				return meshAttachment.NewLinkedMesh();
+			return o.Copy();
+		}
+
+		#region Runtime Linked MeshAttachments
+		/// <summary>
+		/// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to the AtlasRegion provided.</summary>
+		public static MeshAttachment GetLinkedMesh (this MeshAttachment o, string newLinkedMeshName, AtlasRegion region) {
+			if (region == null) throw new System.ArgumentNullException("region");
+			MeshAttachment mesh = o.NewLinkedMesh();
+			mesh.SetRegion(region, false);
+			return mesh;
+		}
+
+		/// <summary>
+		/// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to an AtlasRegion generated from a Sprite. The AtlasRegion will be mapped to a new Material based on the shader.
+		/// For better caching and batching, use GetLinkedMesh(string, AtlasRegion, bool)</summary>
+		public static MeshAttachment GetLinkedMesh (this MeshAttachment o, Sprite sprite, Shader shader, Material materialPropertySource = null) {
+			var m = new Material(shader);
+			if (materialPropertySource != null) {
+				m.CopyPropertiesFromMaterial(materialPropertySource);
+				m.shaderKeywords = materialPropertySource.shaderKeywords;
+			}
+			return o.GetLinkedMesh(sprite.name, sprite.ToAtlasRegion());
+		}
+
+		/// <summary>
+		/// Returns a new linked mesh linked to this MeshAttachment. It will be mapped to an AtlasRegion generated from a Sprite. The AtlasRegion will be mapped to a new Material based on the shader.
+		/// For better caching and batching, use GetLinkedMesh(string, AtlasRegion, bool)</summary>
+		public static MeshAttachment GetLinkedMesh (this MeshAttachment o, Sprite sprite, Material materialPropertySource) {
+			return o.GetLinkedMesh(sprite, materialPropertySource.shader, materialPropertySource);
+		}
+		#endregion
+
+		#region RemappedClone Convenience Methods
+		/// <summary>
+		/// Gets a clone of the attachment remapped with a sprite image.</summary>
+		/// <returns>The remapped clone.</returns>
+		/// <param name="o">The original attachment.</param>
+		/// <param name="sprite">The sprite whose texture to use.</param>
+		/// <param name="sourceMaterial">The source material used to copy the shader and material properties from.</param>
+		/// <param name="premultiplyAlpha">If <c>true</c>, a premultiply alpha clone of the original texture will be created.</param>
+		/// <param name="cloneMeshAsLinked">If <c>true</c> MeshAttachments will be cloned as linked meshes and will inherit animation from the original attachment.</param>
+		/// <param name="useOriginalRegionSize">If <c>true</c> the size of the original attachment will be followed, instead of using the Sprite size.</param>
+		public static Attachment GetRemappedClone (this Attachment o, Sprite sprite, Material sourceMaterial, bool premultiplyAlpha = true, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false) {
+			var atlasRegion = premultiplyAlpha ? sprite.ToAtlasRegionPMAClone(sourceMaterial) : sprite.ToAtlasRegion(new Material(sourceMaterial) { mainTexture = sprite.texture } );
+			return o.GetRemappedClone(atlasRegion, cloneMeshAsLinked, useOriginalRegionSize, 1f/sprite.pixelsPerUnit);
+		}
+
+		/// <summary>
+		/// Gets a clone of the attachment remapped with an atlasRegion image.</summary>
+		/// <returns>The remapped clone.</returns>
+		/// <param name="o">The original attachment.</param>
+		/// <param name="atlasRegion">Atlas region.</param>
+		/// <param name="cloneMeshAsLinked">If <c>true</c> MeshAttachments will be cloned as linked meshes and will inherit animation from the original attachment.</param>
+		/// <param name="useOriginalRegionSize">If <c>true</c> the size of the original attachment will be followed, instead of using the Sprite size.</param>
+		/// <param name="scale">Unity units per pixel scale used to scale the atlas region size when not using the original region size.</param>
+		public static Attachment GetRemappedClone (this Attachment o, AtlasRegion atlasRegion, bool cloneMeshAsLinked = true, bool useOriginalRegionSize = false, float scale = 0.01f) {
+			var regionAttachment = o as RegionAttachment;
+			if (regionAttachment != null) {
+				RegionAttachment newAttachment = (RegionAttachment)regionAttachment.Copy();
+				newAttachment.SetRegion(atlasRegion, false);
+				if (!useOriginalRegionSize) {
+					newAttachment.width = atlasRegion.width * scale;
+					newAttachment.height = atlasRegion.height * scale;
+				}
+				newAttachment.UpdateOffset();
+				return newAttachment;
+			} else {
+				var meshAttachment = o as MeshAttachment;
+				if (meshAttachment != null) {
+					MeshAttachment newAttachment = cloneMeshAsLinked ? meshAttachment.NewLinkedMesh() : (MeshAttachment)meshAttachment.Copy();
+					newAttachment.SetRegion(atlasRegion);
+					return newAttachment;
+				}
+			}
+
+			return o.GetCopy(true); // Non-renderable Attachments will return as normal cloned attachments.
+		}
+		#endregion
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentCloneExtensions.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 3431ed563b2c62f4c8c974a99365ba52
+timeCreated: 1563321428
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 207 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentRegionExtensions.cs

@@ -0,0 +1,207 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace Spine.Unity.AttachmentTools {
+	public static class AttachmentRegionExtensions {
+		#region GetRegion
+		/// <summary>
+		/// Tries to get the region (image) of a renderable attachment. If the attachment is not renderable, it returns null.</summary>
+		public static AtlasRegion GetRegion (this Attachment attachment) {
+			var renderableAttachment = attachment as IHasRendererObject;
+			if (renderableAttachment != null)
+				return renderableAttachment.RendererObject as AtlasRegion;
+
+			return null;
+		}
+
+		/// <summary>Gets the region (image) of a RegionAttachment</summary>
+		public static AtlasRegion GetRegion (this RegionAttachment regionAttachment) {
+			return regionAttachment.RendererObject as AtlasRegion;
+		}
+
+		/// <summary>Gets the region (image) of a MeshAttachment</summary>
+		public static AtlasRegion GetRegion (this MeshAttachment meshAttachment) {
+			return meshAttachment.RendererObject as AtlasRegion;
+		}
+		#endregion
+		#region SetRegion
+		/// <summary>
+		/// Tries to set the region (image) of a renderable attachment. If the attachment is not renderable, nothing is applied.</summary>
+		public static void SetRegion (this Attachment attachment, AtlasRegion region, bool updateOffset = true) {
+			var regionAttachment = attachment as RegionAttachment;
+			if (regionAttachment != null)
+				regionAttachment.SetRegion(region, updateOffset);
+
+			var meshAttachment = attachment as MeshAttachment;
+			if (meshAttachment != null)
+				meshAttachment.SetRegion(region, updateOffset);
+		}
+
+		/// <summary>Sets the region (image) of a RegionAttachment</summary>
+		public static void SetRegion (this RegionAttachment attachment, AtlasRegion region, bool updateOffset = true) {
+			if (region == null) throw new System.ArgumentNullException("region"); 
+
+			// (AtlasAttachmentLoader.cs)
+			attachment.RendererObject = region;
+			attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
+			attachment.regionOffsetX = region.offsetX;
+			attachment.regionOffsetY = region.offsetY;
+			attachment.regionWidth = region.width;
+			attachment.regionHeight = region.height;
+			attachment.regionOriginalWidth = region.originalWidth;
+			attachment.regionOriginalHeight = region.originalHeight;
+
+			if (updateOffset) attachment.UpdateOffset();
+		}
+
+		/// <summary>Sets the region (image) of a MeshAttachment</summary>
+		public static void SetRegion (this MeshAttachment attachment, AtlasRegion region, bool updateUVs = true) {
+			if (region == null) throw new System.ArgumentNullException("region"); 
+
+			// (AtlasAttachmentLoader.cs)
+			attachment.RendererObject = region;
+			attachment.RegionU = region.u;
+			attachment.RegionV = region.v;
+			attachment.RegionU2 = region.u2;
+			attachment.RegionV2 = region.v2;
+			attachment.RegionRotate = region.rotate;
+			attachment.regionOffsetX = region.offsetX;
+			attachment.regionOffsetY = region.offsetY;
+			attachment.regionWidth = region.width;
+			attachment.regionHeight = region.height;
+			attachment.regionOriginalWidth = region.originalWidth;
+			attachment.regionOriginalHeight = region.originalHeight;
+
+			if (updateUVs) attachment.UpdateUVs();
+		}
+		#endregion
+
+		#region Runtime RegionAttachments
+		/// <summary>
+		/// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses a new AtlasPage with the Material provided./// </summary>
+		public static RegionAttachment ToRegionAttachment (this Sprite sprite, Material material, float rotation = 0f) {
+			return sprite.ToRegionAttachment(material.ToSpineAtlasPage(), rotation);
+		}
+
+		/// <summary>
+		/// Creates a RegionAttachment based on a sprite. This method creates a real, usable AtlasRegion. That AtlasRegion uses the AtlasPage provided./// </summary>
+		public static RegionAttachment ToRegionAttachment (this Sprite sprite, AtlasPage page, float rotation = 0f) {
+			if (sprite == null) throw new System.ArgumentNullException("sprite");
+			if (page == null) throw new System.ArgumentNullException("page");
+			var region = sprite.ToAtlasRegion(page);
+			var unitsPerPixel = 1f / sprite.pixelsPerUnit;
+			return region.ToRegionAttachment(sprite.name, unitsPerPixel, rotation);
+		}
+
+		/// <summary>
+		/// Creates a Spine.AtlasRegion that uses a premultiplied alpha duplicate texture of the Sprite's texture data. Returns a RegionAttachment that uses it. Use this if you plan to use a premultiply alpha shader such as "Spine/Skeleton"</summary>
+		public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Shader shader, TextureFormat textureFormat = AtlasUtilities.SpineTextureFormat, bool mipmaps = AtlasUtilities.UseMipMaps, Material materialPropertySource = null, float rotation = 0f) {
+			if (sprite == null) throw new System.ArgumentNullException("sprite");
+			if (shader == null) throw new System.ArgumentNullException("shader");
+			var region = sprite.ToAtlasRegionPMAClone(shader, textureFormat, mipmaps, materialPropertySource);
+			var unitsPerPixel = 1f / sprite.pixelsPerUnit;
+			return region.ToRegionAttachment(sprite.name, unitsPerPixel, rotation);
+		}
+
+		public static RegionAttachment ToRegionAttachmentPMAClone (this Sprite sprite, Material materialPropertySource, TextureFormat textureFormat = AtlasUtilities.SpineTextureFormat, bool mipmaps = AtlasUtilities.UseMipMaps, float rotation = 0f) {
+			return sprite.ToRegionAttachmentPMAClone(materialPropertySource.shader, textureFormat, mipmaps, materialPropertySource, rotation);
+		}
+
+		/// <summary>
+		/// Creates a new RegionAttachment from a given AtlasRegion.</summary>
+		public static RegionAttachment ToRegionAttachment (this AtlasRegion region, string attachmentName, float scale = 0.01f, float rotation = 0f) {
+			if (string.IsNullOrEmpty(attachmentName)) throw new System.ArgumentException("attachmentName can't be null or empty.", "attachmentName");
+			if (region == null) throw new System.ArgumentNullException("region");
+
+			// (AtlasAttachmentLoader.cs)
+			var attachment = new RegionAttachment(attachmentName);
+
+			attachment.RendererObject = region;
+			attachment.SetUVs(region.u, region.v, region.u2, region.v2, region.rotate);
+			attachment.regionOffsetX = region.offsetX;
+			attachment.regionOffsetY = region.offsetY;
+			attachment.regionWidth = region.width;
+			attachment.regionHeight = region.height;
+			attachment.regionOriginalWidth = region.originalWidth;
+			attachment.regionOriginalHeight = region.originalHeight;
+
+			attachment.Path = region.name;
+			attachment.scaleX = 1;
+			attachment.scaleY = 1;
+			attachment.rotation = rotation;
+
+			attachment.r = 1;
+			attachment.g = 1;
+			attachment.b = 1;
+			attachment.a = 1;
+
+			// pass OriginalWidth and OriginalHeight because UpdateOffset uses it in its calculation.
+			attachment.width = attachment.regionOriginalWidth * scale;
+			attachment.height = attachment.regionOriginalHeight * scale;
+
+			attachment.SetColor(Color.white);
+			attachment.UpdateOffset();
+			return attachment;
+		}
+
+		/// <summary> Sets the scale. Call regionAttachment.UpdateOffset to apply the change.</summary>
+		public static void SetScale (this RegionAttachment regionAttachment, Vector2 scale) {
+			regionAttachment.scaleX = scale.x;
+			regionAttachment.scaleY = scale.y;
+		}
+
+		/// <summary> Sets the scale. Call regionAttachment.UpdateOffset to apply the change.</summary>
+		public static void SetScale (this RegionAttachment regionAttachment, float x, float y) {
+			regionAttachment.scaleX = x;
+			regionAttachment.scaleY = y;
+		}
+
+		/// <summary> Sets the position offset. Call regionAttachment.UpdateOffset to apply the change.</summary>
+		public static void SetPositionOffset (this RegionAttachment regionAttachment, Vector2 offset) {
+			regionAttachment.x = offset.x;
+			regionAttachment.y = offset.y;
+		}
+
+		/// <summary> Sets the position offset. Call regionAttachment.UpdateOffset to apply the change.</summary>
+		public static void SetPositionOffset (this RegionAttachment regionAttachment, float x, float y) {
+			regionAttachment.x = x;
+			regionAttachment.y = y;
+		}
+
+		/// <summary> Sets the rotation. Call regionAttachment.UpdateOffset to apply the change.</summary>
+		public static void SetRotation (this RegionAttachment regionAttachment, float rotation) {
+			regionAttachment.rotation = rotation;
+		}
+		#endregion
+	}
+}

+ 12 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/AttachmentRegionExtensions.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 7e7eac783deea004e9bc403eca68a7dc
+timeCreated: 1563321428
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 156 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkinUtilities.cs

@@ -0,0 +1,156 @@
+/******************************************************************************
+ * Spine Runtimes License Agreement
+ * Last updated May 1, 2019. Replaces all prior versions.
+ *
+ * Copyright (c) 2013-2019, Esoteric Software LLC
+ *
+ * Integration of the Spine Runtimes into software or otherwise creating
+ * derivative works of the Spine Runtimes is permitted under the terms and
+ * conditions of Section 2 of the Spine Editor License Agreement:
+ * http://esotericsoftware.com/spine-editor-license
+ *
+ * Otherwise, it is permitted to integrate the Spine Runtimes into software
+ * or otherwise create derivative works of the Spine Runtimes (collectively,
+ * "Products"), provided that each user of the Products must obtain their own
+ * Spine Editor license and redistribution of the Products in any form must
+ * include this license and copyright notice.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS
+ * INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *****************************************************************************/
+
+using UnityEngine;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace Spine.Unity.AttachmentTools {
+	
+	public static class SkinUtilities {
+
+		#region Skeleton Skin Extensions
+		/// <summary>
+		/// Convenience method for duplicating a skeleton's current active skin so changes to it will not affect other skeleton instances. .</summary>
+		public static Skin UnshareSkin (this Skeleton skeleton, bool includeDefaultSkin, bool unshareAttachments, AnimationState state = null) {
+			// 1. Copy the current skin and set the skeleton's skin to the new one.
+			var newSkin = skeleton.GetClonedSkin("cloned skin", includeDefaultSkin, unshareAttachments, true);
+			skeleton.SetSkin(newSkin);
+
+			// 2. Apply correct attachments: skeleton.SetToSetupPose + animationState.Apply
+			if (state != null) {
+				skeleton.SetToSetupPose();
+				state.Apply(skeleton);
+			}
+
+			// 3. Return unshared skin.
+			return newSkin;
+		}
+
+		public static Skin GetClonedSkin (this Skeleton skeleton, string newSkinName, bool includeDefaultSkin = false, bool cloneAttachments = false, bool cloneMeshesAsLinked = true) {
+			var newSkin = new Skin(newSkinName); // may have null name. Harmless.
+			var defaultSkin = skeleton.data.DefaultSkin;
+			var activeSkin = skeleton.skin;
+
+			if (includeDefaultSkin)
+				defaultSkin.CopyTo(newSkin, true, cloneAttachments, cloneMeshesAsLinked);
+
+			if (activeSkin != null)
+				activeSkin.CopyTo(newSkin, true, cloneAttachments, cloneMeshesAsLinked);
+
+			return newSkin;
+		}
+		#endregion
+
+		/// <summary>
+		/// Gets a shallow copy of the skin. The cloned skin's attachments are shared with the original skin.</summary>
+		public static Skin GetClone (this Skin original) {
+			var newSkin = new Skin(original.name + " clone");
+			var newSkinAttachments = newSkin.Attachments;
+
+			foreach (DictionaryEntry a in original.Attachments)
+				newSkinAttachments[a.Key] = a.Value;
+
+			return newSkin;
+		}
+
+		/// <summary>Adds an attachment to the skin for the specified slot index and name. If the name already exists for the slot, the previous value is replaced.</summary>
+		public static void SetAttachment (this Skin skin, string slotName, string keyName, Attachment attachment, Skeleton skeleton) {
+			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.SetAttachment(slotIndex, keyName, attachment);
+		}
+
+		/// <summary>Adds skin items from another skin. For items that already exist, the previous values are replaced.</summary>
+		public static void AddAttachments (this Skin skin, Skin otherSkin) {
+			if (otherSkin == null) return;
+			otherSkin.CopyTo(skin, true, false);
+		}
+
+		/// <summary>Gets an attachment from the skin for the specified slot index and name.</summary>
+		public static Attachment GetAttachment (this Skin skin, string slotName, string keyName, Skeleton skeleton) {
+			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");
+			return skin.GetAttachment(slotIndex, keyName);
+		}
+
+		/// <summary>Adds an attachment to the skin for the specified slot index and name. If the name already exists for the slot, the previous value is replaced.</summary>
+		public static void SetAttachment (this Skin skin, int slotIndex, string keyName, Attachment attachment) {
+			skin.SetAttachment(slotIndex, keyName, attachment);
+		}
+		
+		public static void RemoveAttachment (this Skin skin, string slotName, string keyName, SkeletonData skeletonData) {
+			int slotIndex = skeletonData.FindSlotIndex(slotName);
+			if (skeletonData == null) throw new System.ArgumentNullException("skeletonData", "skeletonData cannot be null.");
+			if (slotIndex == -1) throw new System.ArgumentException(string.Format("Slot '{0}' does not exist in skeleton.", slotName), "slotName");
+			skin.RemoveAttachment(slotIndex, keyName);
+		}
+
+		public static void Clear (this Skin skin) {
+			skin.Attachments.Clear();
+		}
+
+		//[System.Obsolete]
+		public static void Append (this Skin destination, Skin source) {
+			source.CopyTo(destination, true, false);
+		}
+
+		public static void CopyTo (this Skin source, Skin destination, bool overwrite, bool cloneAttachments, bool cloneMeshesAsLinked = true) {
+			var sourceAttachments = source.Attachments;
+			var destinationAttachments = destination.Attachments;
+
+			if (cloneAttachments) {
+				if (overwrite) {
+					foreach (DictionaryEntry e in sourceAttachments)
+						destinationAttachments[e.Key] = ((Attachment)e.Value).GetCopy(cloneMeshesAsLinked);
+				} else {
+					foreach (DictionaryEntry e in sourceAttachments) {
+						if (destinationAttachments.Contains(e.Key)) continue;
+						destinationAttachments.Add(e.Key, ((Attachment)e.Value).GetCopy(cloneMeshesAsLinked));
+					}
+				}
+			} else {
+				if (overwrite) {
+					foreach (DictionaryEntry e in sourceAttachments)
+						destinationAttachments[e.Key] = e.Value;
+				} else {
+					foreach (DictionaryEntry e in sourceAttachments) {
+						if (destinationAttachments.Contains(e.Key)) continue;
+						destinationAttachments.Add(e.Key, e.Value);
+					}
+				}
+			}
+		}
+
+
+	}
+
+}

+ 12 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Utility/SkinUtilities.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: f4692b9527684d048862210ba3f9834e
+timeCreated: 1563321428
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: