Przeglądaj źródła

Merge pull request #348 from Fenrisul/master

Spine Unity Runtime Update
Fenrisul 10 lat temu
rodzic
commit
47452a0c54
73 zmienionych plików z 4239 dodań i 426 usunięć
  1. 19 7
      spine-csharp/src/Attachments/AtlasAttachmentLoader.cs
  2. 2 2
      spine-csharp/src/SkeletonJson.cs
  3. BIN
      spine-unity/Assets/Examples/Scenes/Attributes and AtlasRegions.unity
  4. 4 0
      spine-unity/Assets/Examples/Scenes/Attributes and AtlasRegions.unity.meta
  5. BIN
      spine-unity/Assets/Examples/Scenes/Mix and Match.unity
  6. 4 0
      spine-unity/Assets/Examples/Scenes/Mix and Match.unity.meta
  7. BIN
      spine-unity/Assets/Examples/Scenes/Spineboy Movement.unity
  8. 6 0
      spine-unity/Assets/Examples/Scripts/BasicPlatformerController.cs
  9. 51 0
      spine-unity/Assets/Examples/Scripts/Chimera.cs
  10. 8 0
      spine-unity/Assets/Examples/Scripts/Chimera.cs.meta
  11. 66 0
      spine-unity/Assets/Examples/Scripts/DynamicSpineBone.cs
  12. 8 0
      spine-unity/Assets/Examples/Scripts/DynamicSpineBone.cs.meta
  13. 85 0
      spine-unity/Assets/Examples/Scripts/FootSoldierExample.cs
  14. 8 0
      spine-unity/Assets/Examples/Scripts/FootSoldierExample.cs.meta
  15. 35 1
      spine-unity/Assets/Examples/Scripts/SpineboyController.cs
  16. BIN
      spine-unity/Assets/Examples/Spine/Dragon/dragon_SkeletonData.asset
  17. BIN
      spine-unity/Assets/Examples/Spine/Eyes/eyes_SkeletonData.asset
  18. 5 0
      spine-unity/Assets/Examples/Spine/FootSoldier.meta
  19. 5 0
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment.meta
  20. 34 0
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.atlas.txt
  21. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.atlas.txt.meta
  22. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.png
  23. 47 0
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.png.meta
  24. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Atlas.asset
  25. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Atlas.asset.meta
  26. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Material.mat
  27. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Material.mat.meta
  28. 90 0
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.atlas.txt
  29. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.atlas.txt.meta
  30. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.png
  31. 47 0
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.png.meta
  32. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Atlas.asset
  33. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Atlas.asset.meta
  34. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Material.mat
  35. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Material.mat.meta
  36. 1754 0
      spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier.json
  37. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier.json.meta
  38. BIN
      spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier_SkeletonData.asset
  39. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier_SkeletonData.asset.meta
  40. 5 0
      spine-unity/Assets/Examples/Spine/FootSoldier/license.txt
  41. 4 0
      spine-unity/Assets/Examples/Spine/FootSoldier/license.txt.meta
  42. BIN
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Material.mat
  43. BIN
      spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_SkeletonData.asset
  44. BIN
      spine-unity/Assets/Examples/Spine/Hero.prefab
  45. BIN
      spine-unity/Assets/Examples/Spine/Hero/Hero_SkeletonData.asset
  46. 5 0
      spine-unity/Assets/Examples/Spine/Hero/license.txt
  47. 4 0
      spine-unity/Assets/Examples/Spine/Hero/license.txt.meta
  48. BIN
      spine-unity/Assets/Examples/Spine/Raptor/raptor_Material.mat
  49. BIN
      spine-unity/Assets/Examples/Spine/Raptor/raptor_SkeletonData.asset
  50. BIN
      spine-unity/Assets/Examples/Spine/Spineboy/spineboy_Atlas.asset
  51. BIN
      spine-unity/Assets/Examples/Spine/Spineboy/spineboy_SkeletonData.asset
  52. BIN
      spine-unity/Assets/Examples/Spine/dragon.prefab
  53. 49 0
      spine-unity/Assets/spine-unity/AtlasRegionAttacher.cs
  54. 8 0
      spine-unity/Assets/spine-unity/AtlasRegionAttacher.cs.meta
  55. 36 0
      spine-unity/Assets/spine-unity/CustomSkin.cs
  56. 8 0
      spine-unity/Assets/spine-unity/CustomSkin.cs.meta
  57. 40 1
      spine-unity/Assets/spine-unity/Editor/AtlasAssetInspector.cs
  58. BIN
      spine-unity/Assets/spine-unity/Editor/GUI/icon-slotRoot.png
  59. 47 0
      spine-unity/Assets/spine-unity/Editor/GUI/icon-slotRoot.png.meta
  60. 7 7
      spine-unity/Assets/spine-unity/Editor/GUI/icon-subMeshRenderer.png.meta
  61. BIN
      spine-unity/Assets/spine-unity/Editor/GUI/icon-weights.png
  62. 47 0
      spine-unity/Assets/spine-unity/Editor/GUI/icon-weights.png.meta
  63. 432 226
      spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs
  64. 9 3
      spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs
  65. 585 0
      spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs
  66. 8 0
      spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs.meta
  67. 369 157
      spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs
  68. 26 8
      spine-unity/Assets/spine-unity/SkeletonDataAsset.cs
  69. 12 12
      spine-unity/Assets/spine-unity/SkeletonExtensions.cs
  70. 17 1
      spine-unity/Assets/spine-unity/SkeletonRenderer.cs
  71. 5 1
      spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityKinematicShadow.cs
  72. 198 0
      spine-unity/Assets/spine-unity/SpineAttributes.cs
  73. 8 0
      spine-unity/Assets/spine-unity/SpineAttributes.cs.meta

+ 19 - 7
spine-csharp/src/Attachments/AtlasAttachmentLoader.cs

@@ -32,15 +32,15 @@ using System;
 
 namespace Spine {
 	public class AtlasAttachmentLoader : AttachmentLoader {
-		private Atlas atlas;
+		private Atlas[] atlasArray;
 
-		public AtlasAttachmentLoader (Atlas atlas) {
-			if (atlas == null) throw new ArgumentNullException("atlas cannot be null.");
-			this.atlas = atlas;
+		public AtlasAttachmentLoader (params Atlas[] atlasArray) {
+			if (atlasArray == null) throw new ArgumentNullException("atlas array cannot be null.");
+			this.atlasArray = atlasArray;
 		}
 
 		public RegionAttachment NewRegionAttachment (Skin skin, String name, String path) {
-			AtlasRegion region = atlas.FindRegion(path);
+			AtlasRegion region = FindRegion(path);
 			if (region == null) throw new Exception("Region not found in atlas: " + path + " (region attachment: " + name + ")");
 			RegionAttachment attachment = new RegionAttachment(name);
 			attachment.RendererObject = region;
@@ -55,7 +55,7 @@ namespace Spine {
 		}
 
 		public MeshAttachment NewMeshAttachment (Skin skin, String name, String path) {
-			AtlasRegion region = atlas.FindRegion(path);
+			AtlasRegion region = FindRegion(path);
 			if (region == null) throw new Exception("Region not found in atlas: " + path + " (mesh attachment: " + name + ")");
 			MeshAttachment attachment = new MeshAttachment(name);
 			attachment.RendererObject = region;
@@ -74,7 +74,7 @@ namespace Spine {
 		}
 
 		public SkinnedMeshAttachment NewSkinnedMeshAttachment (Skin skin, String name, String path) {
-			AtlasRegion region = atlas.FindRegion(path);
+			AtlasRegion region = FindRegion(path);
 			if (region == null) throw new Exception("Region not found in atlas: " + path + " (skinned mesh attachment: " + name + ")");
 			SkinnedMeshAttachment attachment = new SkinnedMeshAttachment(name);
 			attachment.RendererObject = region;
@@ -95,5 +95,17 @@ namespace Spine {
 		public BoundingBoxAttachment NewBoundingBoxAttachment (Skin skin, String name) {
 			return new BoundingBoxAttachment(name);
 		}
+
+		public AtlasRegion FindRegion(string name) {
+			AtlasRegion region;
+
+			for (int i = 0; i < atlasArray.Length; i++) {
+				region = atlasArray[i].FindRegion(name);
+				if (region != null)
+					return region;
+			}
+
+			return null;
+		}
 	}
 }

+ 2 - 2
spine-csharp/src/SkeletonJson.cs

@@ -42,8 +42,8 @@ namespace Spine {
 		private AttachmentLoader attachmentLoader;
 		public float Scale { get; set; }
 
-		public SkeletonJson (Atlas atlas)
-			: this(new AtlasAttachmentLoader(atlas)) {
+		public SkeletonJson (params Atlas[] atlasArray)
+			: this(new AtlasAttachmentLoader(atlasArray)) {
 		}
 
 		public SkeletonJson (AttachmentLoader attachmentLoader) {

BIN
spine-unity/Assets/Examples/Scenes/Attributes and AtlasRegions.unity


+ 4 - 0
spine-unity/Assets/Examples/Scenes/Attributes and AtlasRegions.unity.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: 63212ccaf5776bd489cba58fb67a2233
+DefaultImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Scenes/Mix and Match.unity


+ 4 - 0
spine-unity/Assets/Examples/Scenes/Mix and Match.unity.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: c5673b83016f67a4c99772dfb7b3c437
+DefaultImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Scenes/Spineboy Movement.unity


+ 6 - 0
spine-unity/Assets/Examples/Scripts/BasicPlatformerController.cs

@@ -71,11 +71,17 @@ public class BasicPlatformerController : MonoBehaviour {
 #if UNITY_4_5
 	[Header("Animation")]
 #endif
+	[SpineAnimation(dataField: "skeletonAnimation")]
 	public string walkName = "Walk";
+	[SpineAnimation(dataField: "skeletonAnimation")]
 	public string runName = "Run";
+	[SpineAnimation(dataField: "skeletonAnimation")]
 	public string idleName = "Idle";
+	[SpineAnimation(dataField: "skeletonAnimation")]
 	public string jumpName = "Jump";
+	[SpineAnimation(dataField: "skeletonAnimation")]
 	public string fallName = "Fall";
+	[SpineAnimation(dataField: "skeletonAnimation")]
 	public string crouchName = "Crouch";
 
 #if UNITY_4_5

+ 51 - 0
spine-unity/Assets/Examples/Scripts/Chimera.cs

@@ -0,0 +1,51 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.1
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software (typically granted by licensing Spine), you
+ * may not (a) modify, translate, adapt or otherwise create derivative works,
+ * improvements of the Software or develop new applications using the Software
+ * or (b) remove, delete, alter or obscure any trademarks or any copyright,
+ * trademark, patent or other intellectual property or proprietary rights
+ * notices on or in the Software, including any copy thereof. Redistributions
+ * in binary or source form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Basic Platformer Controller created by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+using UnityEngine;
+using System.Collections;
+
+public class Chimera : MonoBehaviour {
+
+	public SkeletonDataAsset skeletonDataSource;
+
+	[SpineAttachment(currentSkinOnly: false, returnAttachmentPath: true, dataField: "skeletonDataSource")]
+	public string attachmentPath;
+
+	[SpineSlot]
+	public string targetSlot;
+
+	void Start() {
+		GetComponent<SkeletonRenderer>().skeleton.FindSlot(targetSlot).Attachment = SpineAttachment.GetAttachment(attachmentPath, skeletonDataSource);
+	}
+}

+ 8 - 0
spine-unity/Assets/Examples/Scripts/Chimera.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5053fe97a7657b5418b0c307b7338b0c
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 66 - 0
spine-unity/Assets/Examples/Scripts/DynamicSpineBone.cs

@@ -0,0 +1,66 @@
+using UnityEngine;
+using System.Collections;
+
+public class DynamicSpineBone : MonoBehaviour {
+
+	public Transform speedReference;
+
+	[SpineBone]
+	public string boneName;
+
+	[Range(-90, 90)]
+	public float minRotation = -45;
+	[Range(-90, 90)]
+	public float maxRotation = 45;
+
+	[Range(-2000, 2000)]
+	public float rotationFactor = 300;
+
+	[Range(5, 30)]
+	public float returnSpeed = 10;
+
+	[Range(100, 1000)]
+	public float boneSpeed = 300;
+
+	public float returnThreshhold = 0.01f;
+
+	public bool useAcceleration;
+
+
+	SkeletonAnimation skeletonAnimation;
+	float goalRotation;
+	Spine.Bone bone;
+	Vector3 velocity;
+	Vector3 acceleration;
+	Vector3 lastPosition;
+
+	void Start() {
+		if (speedReference == null)
+			speedReference = transform;
+
+		skeletonAnimation = GetComponent<SkeletonAnimation>();
+		bone = SpineBone.GetBone(boneName, skeletonAnimation);
+		skeletonAnimation.UpdateLocal += UpdateLocal;
+		lastPosition = speedReference.position;
+	}
+
+	void FixedUpdate() {
+		acceleration = (speedReference.position - lastPosition) - velocity;
+		velocity = speedReference.position - lastPosition;
+		lastPosition = speedReference.position;
+	}
+
+	void UpdateLocal(SkeletonAnimation animation) {
+		Vector3 vec = useAcceleration ? acceleration : velocity;
+
+		if (Mathf.Abs(vec.x) < returnThreshhold)
+			goalRotation = Mathf.Lerp(goalRotation, 0, returnSpeed * Time.deltaTime);
+		else
+			goalRotation += vec.x * rotationFactor * Time.deltaTime * (bone.WorldFlipX ? -1 : 1);
+
+		goalRotation = Mathf.Clamp(goalRotation, minRotation, maxRotation);
+
+		bone.Rotation = Mathf.Lerp(bone.Rotation, bone.Rotation + goalRotation, boneSpeed * Time.deltaTime);
+
+	}
+}

+ 8 - 0
spine-unity/Assets/Examples/Scripts/DynamicSpineBone.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7dae3f4db9a24bf4abe2059526bfd689
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 85 - 0
spine-unity/Assets/Examples/Scripts/FootSoldierExample.cs

@@ -0,0 +1,85 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.1
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software (typically granted by licensing Spine), you
+ * may not (a) modify, translate, adapt or otherwise create derivative works,
+ * improvements of the Software or develop new applications using the Software
+ * or (b) remove, delete, alter or obscure any trademarks or any copyright,
+ * trademark, patent or other intellectual property or proprietary rights
+ * notices on or in the Software, including any copy thereof. Redistributions
+ * in binary or source form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * FootSoldierExample created by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+using UnityEngine;
+using System.Collections;
+
+public class FootSoldierExample : MonoBehaviour {
+	[SpineAnimation("Idle")]
+	public string idleAnimation;
+
+	[SpineAnimation]
+	public string attackAnimation;
+
+	[SpineSlot]
+	public string eyesSlot;
+
+	[SpineAttachment(currentSkinOnly: true, slotField: "eyesSlot")]
+	public string eyesOpenAttachment;
+
+	[SpineAttachment(currentSkinOnly: true, slotField: "eyesSlot")]
+	public string blinkAttachment;
+
+	[Range(0, 0.2f)]
+	public float blinkDuration = 0.05f;
+
+	private SkeletonAnimation skeletonAnimation;
+
+	void Awake() {
+		skeletonAnimation = GetComponent<SkeletonAnimation>();
+	}
+
+	void Start() {
+		skeletonAnimation.state.SetAnimation(0, idleAnimation, true);
+		StartCoroutine("Blink");
+	}
+
+	void Update() {
+		if (Input.GetKey(KeyCode.Space)) {
+			if (skeletonAnimation.state.GetCurrent(0).Animation.Name != attackAnimation) {
+				skeletonAnimation.state.SetAnimation(0, attackAnimation, false);
+				skeletonAnimation.state.AddAnimation(0, idleAnimation, true, 0);
+			}
+		}
+	}
+
+	IEnumerator Blink() {
+		while (true) {
+			yield return new WaitForSeconds(Random.Range(0.25f, 3f));
+			skeletonAnimation.skeleton.SetAttachment(eyesSlot, blinkAttachment);
+			yield return new WaitForSeconds(blinkDuration);
+			skeletonAnimation.skeleton.SetAttachment(eyesSlot, eyesOpenAttachment);
+		}
+	}
+}

+ 8 - 0
spine-unity/Assets/Examples/Scripts/FootSoldierExample.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3c826b50b0cfee343be3bdbbf59d0f7c
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 35 - 1
spine-unity/Assets/Examples/Scripts/SpineboyController.cs

@@ -1,4 +1,38 @@
-using UnityEngine;
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.1
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software (typically granted by licensing Spine), you
+ * may not (a) modify, translate, adapt or otherwise create derivative works,
+ * improvements of the Software or develop new applications using the Software
+ * or (b) remove, delete, alter or obscure any trademarks or any copyright,
+ * trademark, patent or other intellectual property or proprietary rights
+ * notices on or in the Software, including any copy thereof. Redistributions
+ * in binary or source form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * SpineboyController created by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+using UnityEngine;
 using System.Collections;
 
 [RequireComponent(typeof(SkeletonAnimation), typeof(Rigidbody2D))]

BIN
spine-unity/Assets/Examples/Spine/Dragon/dragon_SkeletonData.asset


BIN
spine-unity/Assets/Examples/Spine/Eyes/eyes_SkeletonData.asset


+ 5 - 0
spine-unity/Assets/Examples/Spine/FootSoldier.meta

@@ -0,0 +1,5 @@
+fileFormatVersion: 2
+guid: 7a709e690449c1b40b198bb86f707c41
+folderAsset: yes
+DefaultImporter:
+  userData: 

+ 5 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment.meta

@@ -0,0 +1,5 @@
+fileFormatVersion: 2
+guid: e90f1603e5c99c745a28d42e61afe5b2
+folderAsset: yes
+DefaultImporter:
+  userData: 

+ 34 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.atlas.txt

@@ -0,0 +1,34 @@
+
+Equipment.png
+size: 512,128
+format: RGBA8888
+filter: Linear,Linear
+repeat: none
+Equipment/shield1
+  rotate: true
+  xy: 220, 33
+  size: 71, 118
+  orig: 256, 256
+  offset: 92, 69
+  index: -1
+Equipment/shield2
+  rotate: true
+  xy: 340, 22
+  size: 82, 111
+  orig: 256, 256
+  offset: 87, 72
+  index: -1
+Equipment/sword1
+  rotate: false
+  xy: 2, 2
+  size: 161, 31
+  orig: 512, 256
+  offset: 217, 112
+  index: -1
+Equipment/sword4
+  rotate: false
+  xy: 2, 35
+  size: 216, 69
+  orig: 512, 256
+  offset: 200, 94
+  index: -1

+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.atlas.txt.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: 4f0639ff8bc42314d8d62ee0f7ba541f
+TextScriptImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.png


+ 47 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment.png.meta

@@ -0,0 +1,47 @@
+fileFormatVersion: 2
+guid: ddb89f63d0296cf4f8572b0448bb6b30
+TextureImporter:
+  fileIDToRecycleName: {}
+  serializedVersion: 2
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    linearTexture: 0
+    correctGamma: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: .25
+    normalMapFilter: 0
+  isReadable: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 0
+  seamlessCubemap: 0
+  textureFormat: -3
+  maxTextureSize: 2048
+  textureSettings:
+    filterMode: -1
+    aniso: -1
+    mipBias: -1
+    wrapMode: -1
+  nPOTScale: 1
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 0
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: .5, y: .5}
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spritePixelsToUnits: 100
+  alphaIsTransparency: 0
+  textureType: -1
+  buildTargetSettings: []
+  spriteSheet:
+    sprites: []
+  spritePackingTag: 
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Atlas.asset


+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Atlas.asset.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: c574489dd067c2b4cb4dc165a4c410cc
+NativeFormatImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Material.mat


+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/Equipment/Equipment_Material.mat.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: e6e388faa521c96449984cfa4b60d74e
+NativeFormatImporter:
+  userData: 

+ 90 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.atlas.txt

@@ -0,0 +1,90 @@
+
+FS_White.png
+size: 256,256
+format: RGBA8888
+filter: Linear,Linear
+repeat: none
+White/arm
+  rotate: false
+  xy: 143, 156
+  size: 111, 98
+  orig: 111, 98
+  offset: 0, 0
+  index: -1
+White/arm 2
+  rotate: false
+  xy: 95, 36
+  size: 46, 79
+  orig: 46, 79
+  offset: 0, 0
+  index: -1
+White/body
+  rotate: false
+  xy: 2, 12
+  size: 91, 103
+  orig: 91, 103
+  offset: 0, 0
+  index: -1
+White/eyes
+  rotate: true
+  xy: 195, 87
+  size: 67, 31
+  orig: 67, 31
+  offset: 0, 0
+  index: -1
+White/eyes blink
+  rotate: true
+  xy: 228, 87
+  size: 67, 22
+  orig: 67, 22
+  offset: 0, 0
+  index: -1
+White/feet
+  rotate: false
+  xy: 95, 2
+  size: 50, 32
+  orig: 50, 32
+  offset: 0, 0
+  index: -1
+White/feet 2
+  rotate: false
+  xy: 193, 58
+  size: 55, 27
+  orig: 55, 27
+  offset: 0, 0
+  index: -1
+White/hand
+  rotate: false
+  xy: 147, 9
+  size: 32, 28
+  orig: 32, 28
+  offset: 0, 0
+  index: -1
+White/head 1
+  rotate: false
+  xy: 2, 117
+  size: 139, 137
+  orig: 139, 137
+  offset: 0, 0
+  index: -1
+White/leg
+  rotate: false
+  xy: 143, 39
+  size: 48, 55
+  orig: 48, 55
+  offset: 0, 0
+  index: -1
+White/leg 2
+  rotate: false
+  xy: 143, 96
+  size: 50, 58
+  orig: 50, 58
+  offset: 0, 0
+  index: -1
+White/mouth
+  rotate: false
+  xy: 193, 35
+  size: 28, 21
+  orig: 28, 21
+  offset: 0, 0
+  index: -1

+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.atlas.txt.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: 5ca7c05342912804eb0a2fd5bbe85b58
+TextScriptImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.png


+ 47 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White.png.meta

@@ -0,0 +1,47 @@
+fileFormatVersion: 2
+guid: 57b57f94df266f94ea0981915a4472e1
+TextureImporter:
+  fileIDToRecycleName: {}
+  serializedVersion: 2
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    linearTexture: 0
+    correctGamma: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: .25
+    normalMapFilter: 0
+  isReadable: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 0
+  seamlessCubemap: 0
+  textureFormat: -3
+  maxTextureSize: 2048
+  textureSettings:
+    filterMode: -1
+    aniso: -1
+    mipBias: -1
+    wrapMode: -1
+  nPOTScale: 1
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 0
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: .5, y: .5}
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spritePixelsToUnits: 100
+  alphaIsTransparency: 0
+  textureType: -1
+  buildTargetSettings: []
+  spriteSheet:
+    sprites: []
+  spritePackingTag: 
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Atlas.asset


+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Atlas.asset.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: e3c7e74834cd8424f8735ba05e94a688
+NativeFormatImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Material.mat


+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/FS_White_Material.mat.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: 5a3598dafa118754db95756064347da7
+NativeFormatImporter:
+  userData: 

+ 1754 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier.json

@@ -0,0 +1,1754 @@
+{
+"skeleton": { "hash": "uZfvh80BNvvngM3EGMfAkHebg00", "spine": "2.1.08", "width": 147.68, "height": 268.92, "images": "./images/" },
+"bones": [
+	{ "name": "Root" },
+	{ "name": "Hip", "parent": "Root", "x": -0.93, "y": 73.4 },
+	{ "name": "Body", "parent": "Hip", "length": 60.98, "x": 2.46, "y": -7.69, "rotation": 89.52 },
+	{ "name": "Leg", "parent": "Hip", "length": 31.39, "x": -20.32, "y": -13.85, "rotation": -105.82 },
+	{ "name": "Leg2", "parent": "Hip", "length": 31.09, "x": 22.48, "y": -12.01, "rotation": -74.17 },
+	{ "name": "Arm", "parent": "Body", "length": 51.63, "x": 49.91, "y": 37.35, "rotation": 166.67 },
+	{ "name": "Arm2", "parent": "Body", "length": 52.61, "x": 53.81, "y": -28.52, "rotation": -157.17 },
+	{ "name": "Feet", "parent": "Leg", "length": 15.4, "x": 39.56, "y": 1.59, "rotation": 14.56 },
+	{ "name": "Feet2", "parent": "Leg2", "length": 12.32, "x": 41.33, "y": 0.12, "rotation": -17.19 },
+	{ "name": "Head", "parent": "Body", "length": 65.29, "x": 73.6, "y": 1.09, "rotation": -88.23 },
+	{ "name": "Shield", "parent": "Arm", "x": 45.01, "y": -2.1, "rotation": 123.56 },
+	{ "name": "Weapon", "parent": "Arm2", "length": 137.65, "x": 48.2, "y": 12.78, "rotation": 92.5 }
+],
+"slots": [
+	{ "name": "Arm2", "bone": "Arm2", "attachment": "Arm2" },
+	{ "name": "Weapon", "bone": "Weapon" },
+	{ "name": "Hand", "bone": "Arm2", "attachment": "Hand" },
+	{ "name": "Leg2", "bone": "Leg2", "attachment": "Leg2" },
+	{ "name": "Feet2", "bone": "Feet2", "attachment": "Feet2" },
+	{ "name": "Leg", "bone": "Leg", "attachment": "Leg" },
+	{ "name": "Feet", "bone": "Feet", "attachment": "Feet" },
+	{ "name": "Body", "bone": "Body", "attachment": "body" },
+	{ "name": "Arm", "bone": "Arm", "attachment": "Arm" },
+	{ "name": "Head", "bone": "Head", "attachment": "Head" },
+	{ "name": "Eyes", "bone": "Head", "attachment": "Open" },
+	{ "name": "Shield", "bone": "Shield" },
+	{ "name": "Mouth", "bone": "Head", "attachment": "Closed" }
+],
+"skins": {
+	"default": {},
+	"White": {
+		"Arm": {
+			"Arm": { "name": "arm", "path": "White/arm", "x": 21.18, "y": 21.04, "rotation": 109.17, "width": 111, "height": 98 }
+		},
+		"Arm2": {
+			"Arm2": { "name": "arm 2", "path": "White/arm 2", "x": 23.03, "y": -1.29, "rotation": 78.03, "width": 46, "height": 79 }
+		},
+		"Body": {
+			"body": { "path": "White/body", "x": 23.73, "y": 7.21, "rotation": -89.52, "width": 91, "height": 103 }
+		},
+		"Eyes": {
+			"Blink": { "path": "White/eyes blink", "x": 10.41, "y": 31.16, "rotation": -1.13, "width": 67, "height": 22 },
+			"Open": { "path": "White/eyes", "x": 11.07, "y": 26.12, "rotation": -4.55, "width": 67, "height": 31 }
+		},
+		"Feet": {
+			"Feet": { "name": "feet", "path": "White/feet", "x": 6.08, "y": -1.36, "rotation": 91.26, "width": 50, "height": 32 }
+		},
+		"Feet2": {
+			"Feet2": { "name": "feet 2", "path": "White/feet 2", "x": 4.26, "y": -4.34, "rotation": 91.37, "width": 55, "height": 27 }
+		},
+		"Hand": {
+			"Hand": { "name": "hand", "path": "White/hand", "x": 49.06, "y": 2.06, "rotation": 67.64, "width": 32, "height": 28 }
+		},
+		"Head": {
+			"Head": { "name": "head 1", "path": "White/head 1", "x": -3.5, "y": 58.44, "rotation": -2.76, "width": 139, "height": 137 }
+		},
+		"Leg": {
+			"Leg": { "name": "leg", "path": "White/leg", "x": 16.86, "y": -4.3, "rotation": 104.82, "width": 48, "height": 55 }
+		},
+		"Leg2": {
+			"Leg2": { "name": "leg 2", "path": "White/leg 2", "x": 16.44, "y": -2.11, "rotation": 74.17, "width": 50, "height": 58 }
+		},
+		"Mouth": {
+			"Closed": { "path": "White/mouth", "x": 10.96, "y": 3.69, "rotation": -1.29, "width": 28, "height": 21 }
+		}
+	}
+},
+"events": {
+	"Hit": {}
+},
+"animations": {
+	"Attack": {
+		"bones": {
+			"Arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -10.02,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "angle": -50.06 },
+					{ "time": 0.4, "angle": -15.89 },
+					{ "time": 0.4666, "angle": -29.45, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"angle": -29.45,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "angle": -10.02 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "x": 2.53, "y": -1.01 },
+					{ "time": 0.4666, "x": -5.05, "y": -2.57, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"x": -5.05,
+						"y": -2.57,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Root": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.3333, "angle": 0, "curve": "stepped" },
+					{ "time": 0.8333, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.3333, "x": 0, "y": 0 },
+					{ "time": 0.4666, "x": 9.25, "y": -5.94, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"x": 9.25,
+						"y": -5.94,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Body": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "angle": 8.25 },
+					{ "time": 0.4666, "angle": -15.92, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"angle": -15.92,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "x": -1.07, "y": 3.56 },
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Leg": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.3333, "angle": 0 },
+					{ "time": 0.4666, "angle": -18.61, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"angle": -18.61,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.3333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Leg2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "angle": 15.02 },
+					{ "time": 0.4666, "angle": 4.71, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"angle": 4.71,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "x": -2.61, "y": 4.37 },
+					{ "time": 0.4666, "x": 5.74, "y": 4.37, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"x": 5.74,
+						"y": 4.37,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Arm2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "angle": 148.07 },
+					{ "time": 0.4666, "angle": 335.98, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"angle": 335.98,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "x": 4.5, "y": -0.72 },
+					{ "time": 0.4666, "x": -1.04, "y": -2.35, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"x": -1.04,
+						"y": -2.35,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Feet": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.3333, "angle": 0 },
+					{ "time": 0.4666, "angle": 18.36, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"angle": 18.36,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.3333, "x": 0, "y": 0 },
+					{ "time": 0.4666, "x": -1.43, "y": 2.2, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"x": -1.43,
+						"y": 2.2,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Feet2": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.3333, "angle": 0 },
+					{ "time": 0.4666, "angle": -2.99, "curve": "stepped" },
+					{
+						"time": 0.6,
+						"angle": -2.99,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8333, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.3333, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Head": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "angle": 19.17 },
+					{ "time": 0.4666, "angle": -2.33 },
+					{ "time": 0.8333, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.3333, "x": -1.36, "y": -0.46 },
+					{ "time": 0.8333, "x": 0, "y": 0 }
+				]
+			},
+			"Weapon": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			}
+		},
+		"events": [
+			{ "time": 0.4333, "name": "Hit" }
+		]
+	},
+	"DeathBackward": {
+		"bones": {
+			"Root": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.3333, "angle": 87.75 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.2, "x": -50.02, "y": -6.59 },
+					{ "time": 0.3333, "x": -83.36, "y": -37.67 }
+				]
+			},
+			"Body": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.3333, "x": 5.06, "y": -0.04 }
+				]
+			},
+			"Leg": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.3333, "angle": 33.36 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Leg2": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.3333, "angle": -48.11 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Arm": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.3333, "angle": -346.22 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.3333, "x": -4.06, "y": -12.11 }
+				]
+			},
+			"Arm2": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.3333, "angle": 278.31 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Feet": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.3333, "angle": -46.46 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Feet2": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Head": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.3333, "angle": 17.02 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.3333, "x": -3.67, "y": -21.86 }
+				]
+			},
+			"Weapon": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.3333, "angle": -38.52 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			}
+		}
+	},
+	"DeathForward": {
+		"bones": {
+			"Root": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Hip": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": -87.3 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.243, 0, 0.638, 0.47 ]
+					},
+					{
+						"time": 0.2,
+						"x": 36.63,
+						"y": 0.08,
+						"curve": [ 0.386, 0.35, 0.748, 0.73 ]
+					},
+					{ "time": 0.3333, "x": 74.83, "y": -43.12 }
+				]
+			},
+			"Body": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Leg": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": 52.39 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "x": -4.47, "y": 3.29 }
+				]
+			},
+			"Leg2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": -34.64 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": -306.5 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.3333, "x": -2.42, "y": -20.41 }
+				]
+			},
+			"Arm2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": 160 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.3333, "x": -0.79, "y": 16.15 }
+				]
+			},
+			"Feet": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": -19.93 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "x": -0.19, "y": -2.03 }
+				]
+			},
+			"Feet2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": 30.89 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Head": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": 47.13 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Weapon": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 0,
+						"curve": [ 0.25, 0, 0.758, 0.67 ]
+					},
+					{ "time": 0.3333, "angle": 43.72 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			}
+		}
+	},
+	"Idle": {
+		"bones": {
+			"Body": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5,
+						"x": 0,
+						"y": -3.51,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 1, "x": 0, "y": 0 }
+				]
+			},
+			"Head": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -1.03,
+						"y": 0,
+						"curve": [ 0.212, 0.29, 0.75, 1 ]
+					},
+					{
+						"time": 0.1333,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.6333,
+						"x": -1.75,
+						"y": -0.01,
+						"curve": [ 0.25, 0, 0.755, 0.68 ]
+					},
+					{ "time": 1, "x": -1.03, "y": 0 }
+				]
+			},
+			"Arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -10.63,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"angle": -13.14,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8666, "angle": -10 },
+					{ "time": 1, "angle": -10.63 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.44,
+						"y": 0,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"x": -2.17,
+						"y": -0.01,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8666, "x": 0, "y": 0 },
+					{ "time": 1, "x": -0.44, "y": 0 }
+				]
+			},
+			"Arm2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -0.47,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"angle": -2.34,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8666, "angle": 0 },
+					{ "time": 1, "angle": -0.47 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.46,
+						"y": 0,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"x": -2.29,
+						"y": -0.01,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.8666,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 1, "x": -0.46, "y": 0 }
+				]
+			},
+			"Leg": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": -0.26,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{ "time": 0.1, "x": 0, "y": 0 },
+					{
+						"time": 0.6,
+						"x": 0,
+						"y": -2.03,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": 0, "y": -0.26 }
+				]
+			},
+			"Feet": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.25,
+						"y": -0.07,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{
+						"time": 0.1,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.6,
+						"x": -1.95,
+						"y": -0.55,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": -0.25, "y": -0.07 }
+				]
+			},
+			"Leg2": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": -0.33,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{ "time": 0.1, "x": 0, "y": 0 },
+					{
+						"time": 0.6,
+						"x": 0,
+						"y": -2.54,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": 0, "y": -0.33 }
+				]
+			},
+			"Feet2": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.31,
+						"y": 0.09,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{
+						"time": 0.1,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.6,
+						"x": -2.44,
+						"y": 0.69,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": -0.31, "y": 0.09 }
+				]
+			},
+			"Root": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Weapon": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			}
+		}
+	},
+	"Idle2": {
+		"bones": {
+			"Body": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5,
+						"x": 0,
+						"y": -3.51,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 1, "x": 0, "y": 0 }
+				]
+			},
+			"Head": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.1666, "angle": 22.27, "curve": "stepped" },
+					{ "time": 0.7333, "angle": 22.27 },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -1.03,
+						"y": 0,
+						"curve": [ 0.212, 0.29, 0.75, 1 ]
+					},
+					{
+						"time": 0.1333,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.4,
+						"x": -1.75,
+						"y": -0.01,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 1, "x": -1.03, "y": 0 }
+				]
+			},
+			"Arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -10.63,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"angle": -13.14,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8666, "angle": -10 },
+					{ "time": 1, "angle": -10.63 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.44,
+						"y": 0,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"x": -2.17,
+						"y": -0.01,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8666, "x": 0, "y": 0 },
+					{ "time": 1, "x": -0.44, "y": 0 }
+				]
+			},
+			"Arm2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -0.47,
+						"curve": [ 0.315, 0.27, 0.661, 0.65 ]
+					},
+					{ "time": 0.1666, "angle": 79.53, "curve": "stepped" },
+					{
+						"time": 0.7333,
+						"angle": 79.53,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{ "time": 0.9666, "angle": -0.47, "curve": "stepped" },
+					{ "time": 1, "angle": -0.47 }
+				],
+				"translate": [
+					{ "time": 0, "x": -0.46, "y": 0, "curve": "stepped" },
+					{ "time": 1, "x": -0.46, "y": 0 }
+				]
+			},
+			"Leg": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": -0.26,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{ "time": 0.1, "x": 0, "y": 0 },
+					{
+						"time": 0.6,
+						"x": 0,
+						"y": -2.03,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": 0, "y": -0.26 }
+				]
+			},
+			"Feet": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.25,
+						"y": -0.07,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{
+						"time": 0.1,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.6,
+						"x": -1.95,
+						"y": -0.55,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": -0.25, "y": -0.07 }
+				]
+			},
+			"Leg2": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": -0.33,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{ "time": 0.1, "x": 0, "y": 0 },
+					{
+						"time": 0.6,
+						"x": 0,
+						"y": -2.54,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": 0, "y": -0.33 }
+				]
+			},
+			"Feet2": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.31,
+						"y": 0.09,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{
+						"time": 0.1,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.6,
+						"x": -2.44,
+						"y": 0.69,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": -0.31, "y": 0.09 }
+				]
+			},
+			"Root": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Weapon": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.1666, "angle": -15.07, "curve": "stepped" },
+					{ "time": 0.7333, "angle": -15.07 },
+					{ "time": 0.9666, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			}
+		}
+	},
+	"Idle3": {
+		"bones": {
+			"Body": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5,
+						"x": 0,
+						"y": -3.51,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 1, "x": 0, "y": 0 }
+				]
+			},
+			"Head": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -1.03,
+						"y": 0,
+						"curve": [ 0.212, 0.29, 0.75, 1 ]
+					},
+					{
+						"time": 0.1333,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.6333,
+						"x": -1.75,
+						"y": -0.01,
+						"curve": [ 0.25, 0, 0.755, 0.68 ]
+					},
+					{ "time": 1, "x": -1.03, "y": 0 }
+				]
+			},
+			"Arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -10.63,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"angle": -13.14,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8666, "angle": -10 },
+					{ "time": 1, "angle": -10.63 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.44,
+						"y": 0,
+						"curve": [ 0.337, 0.34, 0.757, 1 ]
+					},
+					{
+						"time": 0.3666,
+						"x": -2.17,
+						"y": -0.01,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.8666, "x": 0, "y": 0 },
+					{ "time": 1, "x": -0.44, "y": 0 }
+				]
+			},
+			"Arm2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -0.47,
+						"curve": [ 0.322, 0.29, 0.658, 0.63 ]
+					},
+					{
+						"time": 0.1666,
+						"angle": 322.37,
+						"curve": [ 0.339, 0.35, 0.673, 0.68 ]
+					},
+					{
+						"time": 0.2666,
+						"angle": 335.29,
+						"curve": [ 0.336, 0.34, 0.67, 0.67 ]
+					},
+					{
+						"time": 0.3666,
+						"angle": 322.1,
+						"curve": [ 0.381, 0.58, 0.729, 1 ]
+					},
+					{
+						"time": 0.4666,
+						"angle": 331.22,
+						"curve": [ 0.333, 0.33, 0.667, 0.66 ]
+					},
+					{
+						"time": 0.5666,
+						"angle": 322.25,
+						"curve": [ 0.33, 0.32, 0.664, 0.66 ]
+					},
+					{
+						"time": 0.6666,
+						"angle": 322.37,
+						"curve": [ 0.386, 0, 0.755, 1 ]
+					},
+					{ "time": 1, "angle": -0.47 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.46,
+						"y": 0,
+						"curve": [ 0.322, 0.29, 0.658, 0.63 ]
+					},
+					{
+						"time": 0.1666,
+						"x": -12.22,
+						"y": -0.1,
+						"curve": [ 0.332, 0.33, 0.682, 0.71 ]
+					},
+					{
+						"time": 0.4333,
+						"x": -10.84,
+						"y": -0.08,
+						"curve": [ 0.381, 0.58, 0.729, 1 ]
+					},
+					{
+						"time": 0.6666,
+						"x": -12.22,
+						"y": -0.1,
+						"curve": [ 0.361, 0.43, 0.755, 1 ]
+					},
+					{ "time": 1, "x": -0.46, "y": 0 }
+				]
+			},
+			"Leg": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": -0.26,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{ "time": 0.1, "x": 0, "y": 0 },
+					{
+						"time": 0.6,
+						"x": 0,
+						"y": -2.03,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": 0, "y": -0.26 }
+				]
+			},
+			"Feet": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.25,
+						"y": -0.07,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{
+						"time": 0.1,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.6,
+						"x": -1.95,
+						"y": -0.55,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": -0.25, "y": -0.07 }
+				]
+			},
+			"Leg2": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": -0.33,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{ "time": 0.1, "x": 0, "y": 0 },
+					{
+						"time": 0.6,
+						"x": 0,
+						"y": -2.54,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": 0, "y": -0.33 }
+				]
+			},
+			"Feet2": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.1, "angle": 0, "curve": "stepped" },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.31,
+						"y": 0.09,
+						"curve": [ 0.374, 0.61, 0.715, 1 ]
+					},
+					{
+						"time": 0.1,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.6,
+						"x": -2.44,
+						"y": 0.69,
+						"curve": [ 0.242, 0, 0.679, 0.71 ]
+					},
+					{ "time": 1, "x": -0.31, "y": 0.09 }
+				]
+			},
+			"Root": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Weapon": {
+				"rotate": [
+					{ "time": 0, "angle": 0 },
+					{ "time": 0.1666, "angle": -7.41, "curve": "stepped" },
+					{ "time": 0.6666, "angle": -7.41 },
+					{ "time": 1, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 1, "x": 0, "y": 0 }
+				]
+			}
+		}
+	},
+	"Move": {
+		"bones": {
+			"Arm": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -345.33,
+						"curve": [ 0.235, 0.29, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"angle": -8.71,
+						"curve": [ 0.25, 0, 0.592, 1 ]
+					},
+					{
+						"time": 0.5333,
+						"angle": -338.66,
+						"curve": [ 0.25, 0, 0.715, 0.63 ]
+					},
+					{ "time": 0.6666, "angle": -345.33 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -2.81,
+						"y": -0.06,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"x": -0.69,
+						"y": -0.92,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.3333,
+						"x": -2.81,
+						"y": -0.06,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5333,
+						"x": 0.36,
+						"y": 0.38,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "x": -2.81, "y": -0.06 }
+				]
+			},
+			"Leg": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 26.56,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"angle": 52.94,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5333,
+						"angle": -1.83,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "angle": 26.56 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"x": 19.08,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "x": 0, "y": 0 }
+				]
+			},
+			"Leg2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -33.11,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"angle": -49.48,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5333,
+						"angle": -1.66,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "angle": -33.11 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": 0,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"x": -22.21,
+						"y": 0,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.4,
+						"x": -11.24,
+						"y": 6.26,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "x": 0, "y": 0 }
+				]
+			},
+			"Feet": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -23.86,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"angle": -51.25,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.3333,
+						"angle": -29.88,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.4333,
+						"angle": -14.06,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5333,
+						"angle": -18.03,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "angle": -23.48 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.84,
+						"y": -4.45,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.2, "x": -1.03, "y": -4.7, "curve": "stepped" },
+					{
+						"time": 0.5333,
+						"x": -1.03,
+						"y": -4.7,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "x": -0.84, "y": -4.45 }
+				]
+			},
+			"Feet2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": 33.53,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"angle": 9.39,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5333,
+						"angle": 14.85,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "angle": 33.53 }
+				],
+				"translate": [
+					{
+						"time": 0,
+						"x": -0.98,
+						"y": 3.98,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"x": 0.28,
+						"y": -0.42,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5333,
+						"x": 0.31,
+						"y": 2.54,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{ "time": 0.6666, "x": -0.98, "y": 3.98 }
+				]
+			},
+			"Hip": {
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.2, "x": 0, "y": 3.84 },
+					{ "time": 0.3333, "x": 0, "y": 0 },
+					{ "time": 0.5333, "x": 0, "y": 3.84 },
+					{ "time": 0.6666, "x": 0, "y": 0 }
+				]
+			},
+			"Head": {
+				"rotate": [
+					{ "time": 0, "angle": 6.58 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0.95, "y": 0 },
+					{ "time": 0.0666, "x": 0, "y": 0 },
+					{ "time": 0.2666, "x": 1.91, "y": 0.01 },
+					{ "time": 0.4, "x": 0, "y": 0 },
+					{ "time": 0.6, "x": 1.91, "y": 0.01 },
+					{ "time": 0.6666, "x": 0.95, "y": 0 }
+				]
+			},
+			"Arm2": {
+				"rotate": [
+					{
+						"time": 0,
+						"angle": -5.89,
+						"curve": [ 0.244, 0.31, 0.75, 1 ]
+					},
+					{
+						"time": 0.2,
+						"angle": 4.17,
+						"curve": [ 0.25, 0, 0.75, 1 ]
+					},
+					{
+						"time": 0.5333,
+						"angle": -10.86,
+						"curve": [ 0.25, 0, 0.781, 0.72 ]
+					},
+					{ "time": 0.6666, "angle": -5.89 }
+				]
+			},
+			"Body": {
+				"rotate": [
+					{ "time": 0, "angle": -5.26 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 2.51 },
+					{ "time": 0.0666, "x": 0, "y": 0 },
+					{ "time": 0.2666, "x": 0, "y": 3.02 },
+					{ "time": 0.4, "x": 0, "y": 0 },
+					{ "time": 0.6, "x": 0, "y": 5.03 },
+					{ "time": 0.6666, "x": 0, "y": 2.51 }
+				]
+			}
+		}
+	},
+	"Parried": {
+		"bones": {
+			"Arm": {
+				"rotate": [
+					{ "time": 0, "angle": -22.67 },
+					{ "time": 0.1, "angle": -46.87 },
+					{ "time": 0.2333, "angle": -5.32 },
+					{ "time": 0.4, "angle": -10.02 }
+				],
+				"translate": [
+					{ "time": 0, "x": -3.15, "y": -2.18 },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Root": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			},
+			"Hip": {
+				"rotate": [
+					{ "time": 0, "angle": 0, "curve": "stepped" },
+					{ "time": 0.4, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 6.94, "y": -4.46 },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Body": {
+				"rotate": [
+					{ "time": 0, "angle": -9.88 },
+					{ "time": 0.1, "angle": 7.82 },
+					{ "time": 0.4, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": -0.86, "y": 2.85 },
+					{ "time": 0.1, "x": -3.19, "y": 2.13 },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Leg": {
+				"rotate": [
+					{ "time": 0, "angle": -13.95 },
+					{ "time": 0.4, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0, "curve": "stepped" },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Leg2": {
+				"rotate": [
+					{ "time": 0, "angle": 7.29 },
+					{ "time": 0.1, "angle": 19.32 },
+					{ "time": 0.4, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 3.65, "y": 4.37 },
+					{ "time": 0.1, "x": -3.88, "y": 5.46 },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Arm2": {
+				"rotate": [
+					{ "time": 0, "angle": 19 },
+					{ "time": 0.1, "angle": 88.31 },
+					{ "time": 0.4, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0.34, "y": -1.94 },
+					{ "time": 0.1, "x": -1.61, "y": 3.91 },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Feet": {
+				"rotate": [
+					{ "time": 0, "angle": 13.77 },
+					{ "time": 0.4, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": -1.07, "y": 1.65 },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Feet2": {
+				"rotate": [
+					{ "time": 0, "angle": -2.24 },
+					{ "time": 0.1, "angle": 3.22 },
+					{ "time": 0.4, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 },
+					{ "time": 0.1, "x": -1.56, "y": 1.1 },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Head": {
+				"rotate": [
+					{ "time": 0, "angle": 3.04 },
+					{ "time": 0.1, "angle": 11.84 },
+					{ "time": 0.4, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": -1.08, "y": -0.37 },
+					{ "time": 0.4, "x": 0, "y": 0 }
+				]
+			},
+			"Weapon": {
+				"rotate": [
+					{ "time": 0, "angle": 0 }
+				],
+				"translate": [
+					{ "time": 0, "x": 0, "y": 0 }
+				]
+			}
+		}
+	}
+}
+}

+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier.json.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: b075ad1d14f5db64b941d266e53de57d
+TextScriptImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier_SkeletonData.asset


+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/FootSoldier_SkeletonData.asset.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: e57cdb51287d3924ebb2ececf816733b
+NativeFormatImporter:
+  userData: 

+ 5 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/license.txt

@@ -0,0 +1,5 @@
+Copyright (c) 2014, XDTech
+
+The project file and images in this "FootSoldier" project are provided for
+demonstration purposes only and may not be redistributed for any reason nor
+used as the basis for derivative work.

+ 4 - 0
spine-unity/Assets/Examples/Spine/FootSoldier/license.txt.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: 6fe8b327d2ab4bd438a63ffec150a911
+TextScriptImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_Material.mat


BIN
spine-unity/Assets/Examples/Spine/Goblins/goblins-mesh_SkeletonData.asset


BIN
spine-unity/Assets/Examples/Spine/Hero.prefab


BIN
spine-unity/Assets/Examples/Spine/Hero/Hero_SkeletonData.asset


+ 5 - 0
spine-unity/Assets/Examples/Spine/Hero/license.txt

@@ -0,0 +1,5 @@
+Copyright (c) 2014, XDTech
+
+The project file and images in this "Hero" project are provided for
+demonstration purposes only and may not be redistributed for any reason nor
+used as the basis for derivative work.

+ 4 - 0
spine-unity/Assets/Examples/Spine/Hero/license.txt.meta

@@ -0,0 +1,4 @@
+fileFormatVersion: 2
+guid: 0d9db1748e0cf9f408129df308299463
+TextScriptImporter:
+  userData: 

BIN
spine-unity/Assets/Examples/Spine/Raptor/raptor_Material.mat


BIN
spine-unity/Assets/Examples/Spine/Raptor/raptor_SkeletonData.asset


BIN
spine-unity/Assets/Examples/Spine/Spineboy/spineboy_Atlas.asset


BIN
spine-unity/Assets/Examples/Spine/Spineboy/spineboy_SkeletonData.asset


BIN
spine-unity/Assets/Examples/Spine/dragon.prefab


+ 49 - 0
spine-unity/Assets/spine-unity/AtlasRegionAttacher.cs

@@ -0,0 +1,49 @@
+using UnityEngine;
+using System.Collections;
+using Spine;
+
+
+public class AtlasRegionAttacher : MonoBehaviour {
+
+	[System.Serializable]
+	public class SlotRegionPair {
+		[SpineSlot]
+		public string slot;
+
+		[SpineAtlasRegion]
+		public string region;
+	}
+
+	public AtlasAsset atlasAsset;
+	public SlotRegionPair[] attachments;
+
+	[HideInInspector]
+	public SkeletonRenderer skeletonRenderer;
+
+
+	Atlas atlas;
+
+	void Start() {
+		atlas = atlasAsset.GetAtlas();
+		this.skeletonRenderer = GetComponent<SkeletonRenderer>();
+
+		AtlasAttachmentLoader loader = new AtlasAttachmentLoader(atlas);
+
+		float scaleMultiplier = skeletonRenderer.skeletonDataAsset.scale;
+
+		var enumerator = attachments.GetEnumerator();
+		while (enumerator.MoveNext()) {
+			var entry = (SlotRegionPair)enumerator.Current;
+			var regionAttachment = loader.NewRegionAttachment(null, entry.region, entry.region);
+			regionAttachment.Width = regionAttachment.RegionOriginalWidth * scaleMultiplier;
+			regionAttachment.Height = regionAttachment.RegionOriginalHeight * scaleMultiplier;
+
+			regionAttachment.SetColor(new Color(1, 1, 1, 1));
+			regionAttachment.UpdateOffset();
+
+			var slot = this.skeletonRenderer.skeleton.FindSlot(entry.slot);
+			slot.Attachment = regionAttachment;
+		}
+	}
+
+}

+ 8 - 0
spine-unity/Assets/spine-unity/AtlasRegionAttacher.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: afde57cc4fd39bb4dbd61b73403ae6a8
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 36 - 0
spine-unity/Assets/spine-unity/CustomSkin.cs

@@ -0,0 +1,36 @@
+using UnityEngine;
+using System.Collections;
+using Spine;
+
+public class CustomSkin : MonoBehaviour {
+
+
+	[System.Serializable]
+	public class SkinPair {
+		[SpineAttachment(currentSkinOnly: false, returnAttachmentPath: true, dataField: "skinSource")]
+		public string sourceAttachment;
+		[SpineSlot]
+		public string targetSlot;
+		[SpineAttachment(currentSkinOnly: true, placeholdersOnly: true)]
+		public string targetAttachment;
+	}
+
+	public SkeletonDataAsset skinSource;
+	public SkinPair[] skinning;
+	public Skin customSkin;
+
+	SkeletonRenderer skeletonRenderer;
+	void Start() {
+		skeletonRenderer = GetComponent<SkeletonRenderer>();
+		Skeleton skeleton = skeletonRenderer.skeleton;
+
+		customSkin = new Skin("CustomSkin");
+
+		foreach (var pair in skinning) {
+			var attachment = SpineAttachment.GetAttachment(pair.sourceAttachment, skinSource);
+			customSkin.AddAttachment(skeleton.FindSlotIndex(pair.targetSlot), pair.targetAttachment, attachment);
+		}
+
+		skeleton.SetSkin(customSkin);
+	}
+}

+ 8 - 0
spine-unity/Assets/spine-unity/CustomSkin.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6e55c8477eccddc4cb5c3551a3945ca7
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 40 - 1
spine-unity/Assets/spine-unity/Editor/AtlasAssetInspector.cs

@@ -28,14 +28,21 @@
  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *****************************************************************************/
 using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
 using UnityEditor;
 using UnityEngine;
+using Spine;
+
 
 [CustomEditor(typeof(AtlasAsset))]
 public class AtlasAssetInspector : Editor {
 	private SerializedProperty atlasFile, materials;
 
 	void OnEnable () {
+		SpineEditorUtilities.ConfirmInitialization();
 		atlasFile = serializedObject.FindProperty("atlasFile");
 		materials = serializedObject.FindProperty("materials");
 	}
@@ -44,11 +51,43 @@ public class AtlasAssetInspector : Editor {
 		serializedObject.Update();
 		AtlasAsset asset = (AtlasAsset)target;
 
+		EditorGUI.BeginChangeCheck();
 		EditorGUILayout.PropertyField(atlasFile);
 		EditorGUILayout.PropertyField(materials, true);
+		if (EditorGUI.EndChangeCheck())
+			serializedObject.ApplyModifiedProperties();
+
+		if (materials.arraySize == 0) {
+			EditorGUILayout.LabelField(new GUIContent("Error:  Missing materials", SpineEditorUtilities.Icons.warning));
+			return;
+		}
+
+		for (int i = 0; i < materials.arraySize; i++) {
+			SerializedProperty prop = materials.GetArrayElementAtIndex(i);
+			Material mat = (Material)prop.objectReferenceValue;
+			if (mat == null) {
+				EditorGUILayout.LabelField(new GUIContent("Error:  Materials cannot be null", SpineEditorUtilities.Icons.warning));
+				return;
+			}
+		}
+			
+		
+
+		if (atlasFile.objectReferenceValue != null) {
+			Atlas atlas = asset.GetAtlas();
+			FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
+			List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
+			EditorGUILayout.LabelField("Regions");
+			EditorGUI.indentLevel++;
+			for (int i = 0; i < regions.Count; i++) {
+				EditorGUILayout.LabelField(regions[i].name);
+			}
+			EditorGUI.indentLevel--;
+		}
+
 		
 		if (serializedObject.ApplyModifiedProperties() ||
-			(Event.current.type == EventType.ValidateCommand && Event.current.commandName == "UndoRedoPerformed")
+			(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
 		) {
 			asset.Reset();
 		}

BIN
spine-unity/Assets/spine-unity/Editor/GUI/icon-slotRoot.png


+ 47 - 0
spine-unity/Assets/spine-unity/Editor/GUI/icon-slotRoot.png.meta

@@ -0,0 +1,47 @@
+fileFormatVersion: 2
+guid: 4a1646cf39026224c85ecba92d7d6948
+TextureImporter:
+  fileIDToRecycleName: {}
+  serializedVersion: 2
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    linearTexture: 1
+    correctGamma: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: .25
+    normalMapFilter: 0
+  isReadable: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 0
+  seamlessCubemap: 0
+  textureFormat: -1
+  maxTextureSize: 1024
+  textureSettings:
+    filterMode: -1
+    aniso: 1
+    mipBias: -1
+    wrapMode: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 0
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: .5, y: .5}
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spritePixelsToUnits: 100
+  alphaIsTransparency: 1
+  textureType: 2
+  buildTargetSettings: []
+  spriteSheet:
+    sprites: []
+  spritePackingTag: 
+  userData: 

+ 7 - 7
spine-unity/Assets/spine-unity/Editor/GUI/icon-subMeshRenderer.png.meta

@@ -5,8 +5,8 @@ TextureImporter:
   serializedVersion: 2
   mipmaps:
     mipMapMode: 0
-    enableMipMap: 1
-    linearTexture: 0
+    enableMipMap: 0
+    linearTexture: 1
     correctGamma: 0
     fadeOut: 0
     borderMipMap: 0
@@ -25,10 +25,10 @@ TextureImporter:
   maxTextureSize: 1024
   textureSettings:
     filterMode: -1
-    aniso: -1
+    aniso: 1
     mipBias: -1
-    wrapMode: -1
-  nPOTScale: 1
+    wrapMode: 1
+  nPOTScale: 0
   lightmap: 0
   compressionQuality: 50
   spriteMode: 0
@@ -38,8 +38,8 @@ TextureImporter:
   spritePivot: {x: .5, y: .5}
   spriteBorder: {x: 0, y: 0, z: 0, w: 0}
   spritePixelsToUnits: 100
-  alphaIsTransparency: 0
-  textureType: -1
+  alphaIsTransparency: 1
+  textureType: 2
   buildTargetSettings: []
   spriteSheet:
     sprites: []

BIN
spine-unity/Assets/spine-unity/Editor/GUI/icon-weights.png


+ 47 - 0
spine-unity/Assets/spine-unity/Editor/GUI/icon-weights.png.meta

@@ -0,0 +1,47 @@
+fileFormatVersion: 2
+guid: 0b1bcb09fa228d049ba3c9ea6a57e1ee
+TextureImporter:
+  fileIDToRecycleName: {}
+  serializedVersion: 2
+  mipmaps:
+    mipMapMode: 0
+    enableMipMap: 0
+    linearTexture: 1
+    correctGamma: 0
+    fadeOut: 0
+    borderMipMap: 0
+    mipMapFadeDistanceStart: 1
+    mipMapFadeDistanceEnd: 3
+  bumpmap:
+    convertToNormalMap: 0
+    externalNormalMap: 0
+    heightScale: .25
+    normalMapFilter: 0
+  isReadable: 0
+  grayScaleToAlpha: 0
+  generateCubemap: 0
+  seamlessCubemap: 0
+  textureFormat: -3
+  maxTextureSize: 1024
+  textureSettings:
+    filterMode: -1
+    aniso: 1
+    mipBias: -1
+    wrapMode: 1
+  nPOTScale: 0
+  lightmap: 0
+  compressionQuality: 50
+  spriteMode: 0
+  spriteExtrude: 1
+  spriteMeshType: 1
+  alignment: 0
+  spritePivot: {x: .5, y: .5}
+  spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+  spritePixelsToUnits: 100
+  alphaIsTransparency: 1
+  textureType: 2
+  buildTargetSettings: []
+  spriteSheet:
+    sprites: []
+  spritePackingTag: 
+  userData: 

+ 432 - 226
spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs

@@ -44,42 +44,48 @@ using Spine;
 
 [CustomEditor(typeof(SkeletonDataAsset))]
 public class SkeletonDataAssetInspector : Editor {
-	private SerializedProperty atlasAsset, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
-	private bool showAnimationStateData = true;
-	
-	#if UNITY_4_3
-	private bool m_showAnimationList = true;
-	#else
-	private AnimBool m_showAnimationList = new AnimBool(true);
-	#endif
-	
+	static bool showAnimationStateData = true;
+	static bool showAnimationList = true;
+	static bool showSlotList = false;
+	static bool showAttachments = false;
+
+	private SerializedProperty atlasAssets, skeletonJSON, scale, fromAnimation, toAnimation, duration, defaultMix;
+
 	private bool m_initialized = false;
 	private SkeletonDataAsset m_skeletonDataAsset;
+	private SkeletonData m_skeletonData;
 	private string m_skeletonDataAssetGUID;
+
+	List<string> warnings = new List<string>();
+
+	void OnEnable() {
+
+	SpineEditorUtilities.ConfirmInitialization();
 	
-	void OnEnable () {
 		try {
-
-			atlasAsset = serializedObject.FindProperty("atlasAsset");
+			atlasAssets = serializedObject.FindProperty("atlasAssets");
 			skeletonJSON = serializedObject.FindProperty("skeletonJSON");
 			scale = serializedObject.FindProperty("scale");
 			fromAnimation = serializedObject.FindProperty("fromAnimation");
 			toAnimation = serializedObject.FindProperty("toAnimation");
 			duration = serializedObject.FindProperty("duration");
 			defaultMix = serializedObject.FindProperty("defaultMix");
-			
+
 			m_skeletonDataAsset = (SkeletonDataAsset)target;
 			m_skeletonDataAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_skeletonDataAsset));
-			
-			EditorApplication.update += Update;
 
+			EditorApplication.update += Update;
 		} catch {
 
 
 		}
+
+		m_skeletonData = m_skeletonDataAsset.GetSkeletonData(true);
+
+		RepopulateWarnings();
 	}
-	
-	void OnDestroy () {
+
+	void OnDestroy() {
 		m_initialized = false;
 		EditorApplication.update -= Update;
 		this.DestroyPreviewInstances();
@@ -88,156 +94,341 @@ public class SkeletonDataAssetInspector : Editor {
 			this.m_previewUtility = null;
 		}
 	}
-	
-	override public void OnInspectorGUI () {
+
+	override public void OnInspectorGUI() {
 		serializedObject.Update();
 		SkeletonDataAsset asset = (SkeletonDataAsset)target;
-		
+
 		EditorGUI.BeginChangeCheck();
-		EditorGUILayout.PropertyField(atlasAsset);
+		EditorGUILayout.PropertyField(atlasAssets, true);
 		EditorGUILayout.PropertyField(skeletonJSON);
 		EditorGUILayout.PropertyField(scale);
 		if (EditorGUI.EndChangeCheck()) {
+			if (serializedObject.ApplyModifiedProperties()) {
+
+				if (m_previewUtility != null) {
+					m_previewUtility.Cleanup();
+					m_previewUtility = null;
+				}
+
+				RepopulateWarnings();
+				OnEnable();
+				return;
+			}
+			
+		}
+
+		
+		if (m_skeletonData != null) {
+
+			DrawAnimationStateInfo();
+			DrawAnimationList();
+			DrawSlotList();
+			
+		} else {
+
+			DrawReimportButton();
+			//Show Warnings
+			foreach (var str in warnings)
+				EditorGUILayout.LabelField(new GUIContent(str, SpineEditorUtilities.Icons.warning));
+		}
+
+		if (!Application.isPlaying) {
+			if (serializedObject.ApplyModifiedProperties() ||
+				(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
+					) {
+				asset.Reset();
+			}
+		}
+	}
+
+	void DrawReimportButton() {
+		EditorGUI.BeginDisabledGroup(skeletonJSON.objectReferenceValue == null);
+		if (GUILayout.Button(new GUIContent("Attempt Reimport", SpineEditorUtilities.Icons.warning))) {
+			SpineEditorUtilities.ImportSpineContent(new string[] { AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue) }, true);
+
 			if (m_previewUtility != null) {
 				m_previewUtility.Cleanup();
 				m_previewUtility = null;
 			}
+
+			RepopulateWarnings();
+			OnEnable();
+			return;
+			
 		}
+		EditorGUI.EndDisabledGroup();
+	}
+
+	void DrawAnimationStateInfo() {
+		showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
+		if (!showAnimationStateData)
+			return;
+
+		EditorGUILayout.PropertyField(defaultMix);
+
+		// Animation names
+		String[] animations = new String[m_skeletonData.Animations.Count];
+		for (int i = 0; i < animations.Length; i++)
+			animations[i] = m_skeletonData.Animations[i].Name;
+
+		for (int i = 0; i < fromAnimation.arraySize; i++) {
+			SerializedProperty from = fromAnimation.GetArrayElementAtIndex(i);
+			SerializedProperty to = toAnimation.GetArrayElementAtIndex(i);
+			SerializedProperty durationProp = duration.GetArrayElementAtIndex(i);
+			EditorGUILayout.BeginHorizontal();
+			from.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, from.stringValue), 0), animations)];
+			to.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, to.stringValue), 0), animations)];
+			durationProp.floatValue = EditorGUILayout.FloatField(durationProp.floatValue);
+			if (GUILayout.Button("Delete")) {
+				duration.DeleteArrayElementAtIndex(i);
+				toAnimation.DeleteArrayElementAtIndex(i);
+				fromAnimation.DeleteArrayElementAtIndex(i);
+			}
+			EditorGUILayout.EndHorizontal();
+		}
+		EditorGUILayout.BeginHorizontal();
+		EditorGUILayout.Space();
+		if (GUILayout.Button("Add Mix")) {
+			duration.arraySize++;
+			toAnimation.arraySize++;
+			fromAnimation.arraySize++;
+		}
+		EditorGUILayout.Space();
+		EditorGUILayout.EndHorizontal();
 		
-		SkeletonData skeletonData = asset.GetSkeletonData(asset.atlasAsset == null || asset.skeletonJSON == null);
-		if (skeletonData != null) {
-			showAnimationStateData = EditorGUILayout.Foldout(showAnimationStateData, "Animation State Data");
-			if (showAnimationStateData) {
-				EditorGUILayout.PropertyField(defaultMix);
-				
-				// Animation names
-				String[] animations = new String[skeletonData.Animations.Count];
-				for (int i = 0; i < animations.Length; i++)
-					animations[i] = skeletonData.Animations[i].Name;
-				
-				for (int i = 0; i < fromAnimation.arraySize; i++) {
-					SerializedProperty from = fromAnimation.GetArrayElementAtIndex(i);
-					SerializedProperty to = toAnimation.GetArrayElementAtIndex(i);
-					SerializedProperty durationProp = duration.GetArrayElementAtIndex(i);
-					EditorGUILayout.BeginHorizontal();
-					from.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, from.stringValue), 0), animations)];
-					to.stringValue = animations[EditorGUILayout.Popup(Math.Max(Array.IndexOf(animations, to.stringValue), 0), animations)];
-					durationProp.floatValue = EditorGUILayout.FloatField(durationProp.floatValue);
-					if (GUILayout.Button("Delete")) {
-						duration.DeleteArrayElementAtIndex(i);
-						toAnimation.DeleteArrayElementAtIndex(i);
-						fromAnimation.DeleteArrayElementAtIndex(i);
+	}
+	void DrawAnimationList() {
+		showAnimationList = EditorGUILayout.Foldout(showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
+		if (!showAnimationList)
+			return;
+
+		if (GUILayout.Button(new GUIContent("Setup Pose", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(105), GUILayout.Height(18))) {
+			StopAnimation();
+			m_skeletonAnimation.skeleton.SetToSetupPose();
+			m_requireRefresh = true;
+		}
+
+		EditorGUILayout.LabelField("Name", "Duration");
+		foreach (Spine.Animation a in m_skeletonData.Animations) {
+			GUILayout.BeginHorizontal();
+
+			if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) {
+				if (m_skeletonAnimation.state.GetCurrent(0) != null && m_skeletonAnimation.state.GetCurrent(0).Animation == a) {
+					GUI.contentColor = Color.black;
+					if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
+						StopAnimation();
+					}
+					GUI.contentColor = Color.white;
+				} else {
+					if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
+						PlayAnimation(a.Name, true);
 					}
-					EditorGUILayout.EndHorizontal();
-				}
-				EditorGUILayout.BeginHorizontal();
-				EditorGUILayout.Space();
-				if (GUILayout.Button("Add Mix")) {
-					duration.arraySize++;
-					toAnimation.arraySize++;
-					fromAnimation.arraySize++;
 				}
-				EditorGUILayout.Space();
-				EditorGUILayout.EndHorizontal();
-			}
-			
-			if (GUILayout.Button(new GUIContent("Setup Pose", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(105), GUILayout.Height(18))) {
-				StopAnimation();
-				m_skeletonAnimation.skeleton.SetToSetupPose();
-				m_requireRefresh = true;
+			} else {
+				GUILayout.Label("?", GUILayout.Width(24));
 			}
-			
-			#if UNITY_4_3
-			m_showAnimationList = EditorGUILayout.Foldout(m_showAnimationList, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
-			if(m_showAnimationList){
-			#else
-			m_showAnimationList.target = EditorGUILayout.Foldout(m_showAnimationList.target, new GUIContent("Animations", SpineEditorUtilities.Icons.animationRoot));
-			if (EditorGUILayout.BeginFadeGroup(m_showAnimationList.faded)) {
-				#endif
-					
-					
+			EditorGUILayout.LabelField(new GUIContent(a.Name, SpineEditorUtilities.Icons.animation), new GUIContent(a.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(a.Duration * 30)) + ")").PadLeft(12, ' ')));
+			GUILayout.EndHorizontal();
+		}
+	}
+
+	
+	void DrawSlotList() {
+		showSlotList = EditorGUILayout.Foldout(showSlotList, new GUIContent("Slots", SpineEditorUtilities.Icons.slotRoot));
+
+		if (!showSlotList)
+			return;
+
+		if (m_skeletonAnimation == null || m_skeletonAnimation.skeleton == null)
+			return;
+
+		EditorGUI.indentLevel++;
+		try {
+			showAttachments = EditorGUILayout.ToggleLeft("Show Attachments", showAttachments);
+		} catch {
+			return;
+		}
+
+		
+		List<Attachment> slotAttachments = new List<Attachment>();
+		List<string> slotAttachmentNames = new List<string>();
+		List<string> defaultSkinAttachmentNames = new List<string>();
+		var defaultSkin = m_skeletonData.Skins[0];
+		Skin skin = m_skeletonAnimation.skeleton.Skin;
+		if (skin == null) {
+			skin = defaultSkin;
+		}
+
+		for (int i = m_skeletonAnimation.skeleton.Slots.Count-1; i >= 0; i--) {
+			Slot slot = m_skeletonAnimation.skeleton.Slots[i];
+			EditorGUILayout.LabelField(new GUIContent(slot.Data.Name, SpineEditorUtilities.Icons.slot));
+			if (showAttachments) {
+				
+
+				EditorGUI.indentLevel++;
+				slotAttachments.Clear();
+				slotAttachmentNames.Clear();
+				defaultSkinAttachmentNames.Clear();
+
+				skin.FindNamesForSlot(i, slotAttachmentNames);
+				skin.FindAttachmentsForSlot(i, slotAttachments);
+
+				
+				if (skin != defaultSkin) {
+					defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
+					defaultSkin.FindNamesForSlot(i, slotAttachmentNames);
+					defaultSkin.FindAttachmentsForSlot(i, slotAttachments);
+				} else {
+					defaultSkin.FindNamesForSlot(i, defaultSkinAttachmentNames);
+				}
+				
+
+
+				for (int a = 0; a < slotAttachments.Count; a++) {
+					Attachment attachment = slotAttachments[a];
+					string name = slotAttachmentNames[a];
+
+					Texture2D icon = null;
+					var type = attachment.GetType();
+
+					if (type == typeof(RegionAttachment))
+						icon = SpineEditorUtilities.Icons.image;
+					else if (type == typeof(MeshAttachment))
+						icon = SpineEditorUtilities.Icons.mesh;
+					else if (type == typeof(BoundingBoxAttachment))
+						icon = SpineEditorUtilities.Icons.boundingBox;
+					else if (type == typeof(SkinnedMeshAttachment))
+						icon = SpineEditorUtilities.Icons.weights;
+					else
+						icon = SpineEditorUtilities.Icons.warning;
+
+					//TODO:  Waterboard Nate
+					//if (name != attachment.Name)
+						//icon = SpineEditorUtilities.Icons.skinPlaceholder;
+
+					bool initialState = slot.Attachment == attachment;
 					
+					bool toggled = EditorGUILayout.ToggleLeft(new GUIContent(name, icon), slot.Attachment == attachment);
+
+					if (!defaultSkinAttachmentNames.Contains(name)) {
+						Rect skinPlaceHolderIconRect = GUILayoutUtility.GetLastRect();
+						skinPlaceHolderIconRect.width = SpineEditorUtilities.Icons.skinPlaceholder.width;
+						skinPlaceHolderIconRect.height = SpineEditorUtilities.Icons.skinPlaceholder.height;
+						GUI.DrawTexture(skinPlaceHolderIconRect, SpineEditorUtilities.Icons.skinPlaceholder);
+					}
 					
-				EditorGUILayout.LabelField("Name", "Duration");
-				foreach (Spine.Animation a in skeletonData.Animations) {
-					GUILayout.BeginHorizontal();
-						
-					if (m_skeletonAnimation != null && m_skeletonAnimation.state != null) {
-						if (m_skeletonAnimation.state.GetCurrent(0) != null && m_skeletonAnimation.state.GetCurrent(0).Animation == a) {
-							GUI.contentColor = Color.black;
-							if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
-								StopAnimation();
-							}
-							GUI.contentColor = Color.white;
+
+					if (toggled != initialState) {
+						if (toggled) {
+							slot.Attachment = attachment;
 						} else {
-							if (GUILayout.Button("\u25BA", GUILayout.Width(24))) {
-								PlayAnimation(a.Name, true);
-							}
+							slot.Attachment = null;
 						}
-					} else {
-						GUILayout.Label("?", GUILayout.Width(24));
+						m_requireRefresh = true;
 					}
-					EditorGUILayout.LabelField(new GUIContent(a.Name, SpineEditorUtilities.Icons.animation), new GUIContent(a.Duration.ToString("f3") + "s" + ("(" + (Mathf.RoundToInt(a.Duration * 30)) + ")").PadLeft(12, ' ')));
-					GUILayout.EndHorizontal();
 				}
+				
+				
+				EditorGUI.indentLevel--;
 			}
-			#if !UNITY_4_3
-			EditorGUILayout.EndFadeGroup();
-			#endif
 		}
-			
-		if (!Application.isPlaying) {
-			if (serializedObject.ApplyModifiedProperties() ||
-				(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
-				    ) {
-				asset.Reset();
+
+		EditorGUI.indentLevel--;
+	}
+
+
+	void RepopulateWarnings() {
+		warnings.Clear();
+
+		if (skeletonJSON.objectReferenceValue == null)
+			warnings.Add("Missing Skeleton JSON");
+		else {
+
+			if (SpineEditorUtilities.IsSpineJSON((TextAsset)skeletonJSON.objectReferenceValue) == false) {
+				warnings.Add("Skeleton JSON is not a Valid JSON file");
+			} else {
+				bool detectedNullAtlasEntry = false;
+				List<Atlas> atlasList = new List<Atlas>();
+				for (int i = 0; i < atlasAssets.arraySize; i++) {
+					if (atlasAssets.GetArrayElementAtIndex(i).objectReferenceValue == null) {
+						detectedNullAtlasEntry = true;
+						break;
+					} else {
+						atlasList.Add(((AtlasAsset)atlasAssets.GetArrayElementAtIndex(i).objectReferenceValue).GetAtlas());
+					}
+				}
+
+				if (detectedNullAtlasEntry)
+					warnings.Add("AtlasAsset elements cannot be Null");
+				else {
+					//get requirements
+					var missingPaths = SpineEditorUtilities.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath((TextAsset)skeletonJSON.objectReferenceValue));
+
+					foreach (var atlas in atlasList) {
+						for (int i = 0; i < missingPaths.Count; i++) {
+							if (atlas.FindRegion(missingPaths[i]) != null) {
+								missingPaths.RemoveAt(i);
+								i--;
+							}
+						}
+					}
+
+					foreach (var str in missingPaths)
+						warnings.Add("Missing Region: '" + str + "'");
+
+
+
+				}
 			}
+
+
 		}
 	}
-		
+
 	//preview window stuff
 	private PreviewRenderUtility m_previewUtility;
 	private GameObject m_previewInstance;
 	private Vector2 previewDir;
 	private SkeletonAnimation m_skeletonAnimation;
-	private SkeletonData m_skeletonData;
+	//private SkeletonData m_skeletonData;
 	private static int sliderHash = "Slider".GetHashCode();
 	private float m_lastTime;
 	private bool m_playing;
 	private bool m_requireRefresh;
 	private Color m_originColor = new Color(0.3f, 0.3f, 0.3f, 1);
-		
-	private void StopAnimation () {
+
+	private void StopAnimation() {
 		m_skeletonAnimation.state.ClearTrack(0);
 		m_playing = false;
 	}
-		
+
 	List<Spine.Event> m_animEvents = new List<Spine.Event>();
 	List<float> m_animEventFrames = new List<float>();
 
-	private void PlayAnimation (string animName, bool loop) {
+	private void PlayAnimation(string animName, bool loop) {
 		m_animEvents.Clear();
 		m_animEventFrames.Clear();
-			
+
 		m_skeletonAnimation.state.SetAnimation(0, animName, loop);
-			
+
 		Spine.Animation a = m_skeletonAnimation.state.GetCurrent(0).Animation;
 		foreach (Timeline t in a.Timelines) {
 			if (t.GetType() == typeof(EventTimeline)) {
 				EventTimeline et = (EventTimeline)t;
-					
+
 				for (int i = 0; i < et.Events.Length; i++) {
 					m_animEvents.Add(et.Events[i]);
 					m_animEventFrames.Add(et.Frames[i]);
 				}
-					
+
 			}
 		}
-			
+
 		m_playing = true;
 	}
-		
-	private void InitPreview () {
+
+	private void InitPreview() {
 		if (this.m_previewUtility == null) {
 			this.m_lastTime = Time.realtimeSinceStartup;
 			this.m_previewUtility = new PreviewRenderUtility(true);
@@ -247,48 +438,59 @@ public class SkeletonDataAssetInspector : Editor {
 			this.CreatePreviewInstances();
 		}
 	}
-		
-	private void CreatePreviewInstances () {
+
+	private void CreatePreviewInstances() {
 		this.DestroyPreviewInstances();
 		if (this.m_previewInstance == null) {
-			string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", "");
-				
-			m_previewInstance = SpineEditorUtilities.SpawnAnimatedSkeleton((SkeletonDataAsset)target, skinName).gameObject;
-			m_previewInstance.hideFlags = HideFlags.HideAndDontSave;
-			m_previewInstance.layer = 0x1f;
-				
-				
-			m_skeletonAnimation = m_previewInstance.GetComponent<SkeletonAnimation>();
-			m_skeletonAnimation.initialSkinName = skinName;
-			m_skeletonAnimation.LateUpdate();
-				
-			m_skeletonData = m_skeletonAnimation.skeletonDataAsset.GetSkeletonData(true);
-				
-			m_previewInstance.renderer.enabled = false;
-				
-			m_initialized = true;
-			AdjustCameraGoals(true);
+			try {
+				string skinName = EditorPrefs.GetString(m_skeletonDataAssetGUID + "_lastSkin", "");
+
+				m_previewInstance = SpineEditorUtilities.SpawnAnimatedSkeleton((SkeletonDataAsset)target, skinName).gameObject;
+				m_previewInstance.hideFlags = HideFlags.HideAndDontSave;
+				m_previewInstance.layer = 0x1f;
+
+
+				m_skeletonAnimation = m_previewInstance.GetComponent<SkeletonAnimation>();
+				m_skeletonAnimation.initialSkinName = skinName;
+				m_skeletonAnimation.LateUpdate();
+
+				m_skeletonData = m_skeletonAnimation.skeletonDataAsset.GetSkeletonData(true);
+
+				m_previewInstance.renderer.enabled = false;
+
+				m_initialized = true;
+				AdjustCameraGoals(true);
+			} catch {
+
+			}
 		}
 	}
-		
-	private void DestroyPreviewInstances () {
+
+	private void DestroyPreviewInstances() {
 		if (this.m_previewInstance != null) {
 			DestroyImmediate(this.m_previewInstance);
 			m_previewInstance = null;
 		}
 		m_initialized = false;
 	}
-		
-	public override bool HasPreviewGUI () {
+
+	public override bool HasPreviewGUI() {
 		//TODO: validate json data
+
+		for (int i = 0; i < atlasAssets.arraySize; i++) {
+			var prop = atlasAssets.GetArrayElementAtIndex(i);
+			if (prop.objectReferenceValue == null)
+				return false;
+		}
+
 		return skeletonJSON.objectReferenceValue != null;
 	}
-		
+
 	Texture m_previewTex = new Texture();
 
-	public override void OnInteractivePreviewGUI (Rect r, GUIStyle background) {
+	public override void OnInteractivePreviewGUI(Rect r, GUIStyle background) {
 		this.InitPreview();
-			
+
 		if (UnityEngine.Event.current.type == EventType.Repaint) {
 			if (m_requireRefresh) {
 				this.m_previewUtility.BeginPreview(r, background);
@@ -299,50 +501,54 @@ public class SkeletonDataAssetInspector : Editor {
 			if (this.m_previewTex != null)
 				GUI.DrawTexture(r, m_previewTex, ScaleMode.StretchToFill, false);
 		}
-			
+
 		DrawSkinToolbar(r);
 		NormalizedTimeBar(r);
 		//TODO: implement panning
 		//		this.previewDir = Drag2D(this.previewDir, r);
 		MouseScroll(r);
 	}
-		
+
 	float m_orthoGoal = 1;
 	Vector3 m_posGoal = new Vector3(0, 0, -10);
 	double m_adjustFrameEndTime = 0;
 
-	private void AdjustCameraGoals (bool calculateMixTime) {
+	private void AdjustCameraGoals(bool calculateMixTime) {
+		if (this.m_previewInstance == null)
+			return;
+
 		if (calculateMixTime) {
 			if (m_skeletonAnimation.state.GetCurrent(0) != null) {
 				m_adjustFrameEndTime = EditorApplication.timeSinceStartup + m_skeletonAnimation.state.GetCurrent(0).Mix;
 			}
 		}
-			
-			
+
+
 		GameObject go = this.m_previewInstance;
+
 		Bounds bounds = go.renderer.bounds;
 		m_orthoGoal = bounds.size.y;
-			
+
 		m_posGoal = bounds.center + new Vector3(0, 0, -10);
 	}
-		
-	private void AdjustCameraGoals () {
+
+	private void AdjustCameraGoals() {
 		AdjustCameraGoals(false);
 	}
-		
-	private void AdjustCamera () {
+
+	private void AdjustCamera() {
 		if (m_previewUtility == null)
 			return;
-			
-			
+
+
 		if (EditorApplication.timeSinceStartup < m_adjustFrameEndTime) {
 			AdjustCameraGoals();
 		}
-			
+
 		float orthoSet = Mathf.Lerp(this.m_previewUtility.m_Camera.orthographicSize, m_orthoGoal, 0.1f);
-			
+
 		this.m_previewUtility.m_Camera.orthographicSize = orthoSet;
-			
+
 		float dist = Vector3.Distance(m_previewUtility.m_Camera.transform.position, m_posGoal);
 		if (dist > 60f * ((SkeletonDataAsset)target).scale) {
 			Vector3 pos = Vector3.Lerp(this.m_previewUtility.m_Camera.transform.position, m_posGoal, 0.1f);
@@ -352,138 +558,138 @@ public class SkeletonDataAssetInspector : Editor {
 			m_requireRefresh = true;
 		}
 	}
-		
-	private void DoRenderPreview (bool drawHandles) {
+
+	private void DoRenderPreview(bool drawHandles) {
 		GameObject go = this.m_previewInstance;
-			
-		if (m_requireRefresh) {
+
+		if (m_requireRefresh && go != null) {
 			go.renderer.enabled = true;
-				
+
 			if (EditorApplication.isPlaying) {
 				//do nothing
 			} else {
 				m_skeletonAnimation.Update((Time.realtimeSinceStartup - m_lastTime));
 			}
-				
+
 			m_lastTime = Time.realtimeSinceStartup;
-				
+
 			if (!EditorApplication.isPlaying)
 				m_skeletonAnimation.LateUpdate();
-				
+
 			if (drawHandles) {
 				Handles.SetCamera(m_previewUtility.m_Camera);
 				Handles.color = m_originColor;
-					
+
 				Handles.DrawLine(new Vector3(-1000 * m_skeletonDataAsset.scale, 0, 0), new Vector3(1000 * m_skeletonDataAsset.scale, 0, 0));
 				Handles.DrawLine(new Vector3(0, 1000 * m_skeletonDataAsset.scale, 0), new Vector3(0, -1000 * m_skeletonDataAsset.scale, 0));
 			}
-				
+
 			this.m_previewUtility.m_Camera.Render();
 			go.renderer.enabled = false;
 		}
-			
-			
+
+
 	}
-		
-	void Update () {
+
+	void Update() {
 		AdjustCamera();
-			
+
 		if (m_playing) {
 			m_requireRefresh = true;
 			Repaint();
 		} else if (m_requireRefresh) {
-				Repaint();
-			} else {
-				#if !UNITY_4_3
-				if (m_showAnimationList.isAnimating)
-					Repaint();
-				#endif
-			}
+			Repaint();
+		} else {
+			//only needed if using smooth menus
+		}
 	}
-		
-	void DrawSkinToolbar (Rect r) {
+
+	void DrawSkinToolbar(Rect r) {
 		if (m_skeletonAnimation == null)
 			return;
-			
+
 		if (m_skeletonAnimation.skeleton != null) {
 			string label = (m_skeletonAnimation.skeleton != null && m_skeletonAnimation.skeleton.Skin != null) ? m_skeletonAnimation.skeleton.Skin.Name : "default";
-				
+
 			Rect popRect = new Rect(r);
 			popRect.y += 32;
 			popRect.x += 4;
 			popRect.height = 24;
 			popRect.width = 40;
 			EditorGUI.DropShadowLabel(popRect, new GUIContent("Skin", SpineEditorUtilities.Icons.skinsRoot));
-				
+
 			popRect.y += 11;
 			popRect.width = 150;
 			popRect.x += 44;
-				
+
 			if (GUI.Button(popRect, label, EditorStyles.popup)) {
 				SelectSkinContext();
 			}
 		}
 	}
-		
-	void SelectSkinContext () {
+
+	void SelectSkinContext() {
 		GenericMenu menu = new GenericMenu();
-			
+
 		foreach (Skin s in m_skeletonData.Skins) {
 			menu.AddItem(new GUIContent(s.Name), this.m_skeletonAnimation.skeleton.Skin == s, SetSkin, (object)s);
 		}
-			
+
 		menu.ShowAsContext();
 	}
-		
-	void SetSkin (object o) {
+
+	void SetSkin(object o) {
 		Skin skin = (Skin)o;
-			
+
 		m_skeletonAnimation.initialSkinName = skin.Name;
 		m_skeletonAnimation.Reset();
 		m_requireRefresh = true;
-			
+
 		EditorPrefs.SetString(m_skeletonDataAssetGUID + "_lastSkin", skin.Name);
 	}
-		
-	void NormalizedTimeBar (Rect r) {
+
+	void NormalizedTimeBar(Rect r) {
+		if (m_skeletonAnimation == null)
+			return;
+
 		Rect barRect = new Rect(r);
 		barRect.height = 32;
 		barRect.x += 4;
 		barRect.width -= 4;
-			
+
 		GUI.Box(barRect, "");
-			
+
 		Rect lineRect = new Rect(barRect);
 		float width = lineRect.width;
 		TrackEntry t = m_skeletonAnimation.state.GetCurrent(0);
-			
+
 		if (t != null) {
 			int loopCount = (int)(t.Time / t.EndTime);
 			float currentTime = t.Time - (t.EndTime * loopCount);
-				
+
 			float normalizedTime = currentTime / t.Animation.Duration;
-				
+
 			lineRect.x = barRect.x + (width * normalizedTime) - 0.5f;
 			lineRect.width = 2;
-				
+
 			GUI.color = Color.red;
 			GUI.DrawTexture(lineRect, EditorGUIUtility.whiteTexture);
 			GUI.color = Color.white;
-				
+
 			for (int i = 0; i < m_animEvents.Count; i++) {
 				//TODO: Tooltip
 				//Spine.Event spev = animEvents[i];
-					
+
 				float fr = m_animEventFrames[i];
-					
+
 				Rect evRect = new Rect(barRect);
 				evRect.x = Mathf.Clamp(((fr / t.Animation.Duration) * width) - (SpineEditorUtilities.Icons._event.width / 2), barRect.x, float.MaxValue);
 				evRect.width = SpineEditorUtilities.Icons._event.width;
 				evRect.height = SpineEditorUtilities.Icons._event.height;
 				evRect.y += SpineEditorUtilities.Icons._event.height;
 				GUI.DrawTexture(evRect, SpineEditorUtilities.Icons._event);
-					
-					
+
+
 				//TODO:  Tooltip
 				/*
 				UnityEngine.Event ev = UnityEngine.Event.current;
@@ -500,24 +706,24 @@ public class SkeletonDataAssetInspector : Editor {
 			}
 		}
 	}
-		
-	void MouseScroll (Rect position) {
+
+	void MouseScroll(Rect position) {
 		UnityEngine.Event current = UnityEngine.Event.current;
 		int controlID = GUIUtility.GetControlID(sliderHash, FocusType.Passive);
-			
+
 		switch (current.GetTypeForControl(controlID)) {
-		case EventType.ScrollWheel:
-			if (position.Contains(current.mousePosition)) {
-					
-				m_orthoGoal += current.delta.y * ((SkeletonDataAsset)target).scale * 10;
-				GUIUtility.hotControl = controlID;
-				current.Use();
-			}
-			break;
+			case EventType.ScrollWheel:
+				if (position.Contains(current.mousePosition)) {
+
+					m_orthoGoal += current.delta.y * ((SkeletonDataAsset)target).scale * 10;
+					GUIUtility.hotControl = controlID;
+					current.Use();
+				}
+				break;
 		}
-			
+
 	}
-		
+
 	//TODO:  Implement preview panning
 	/*
 	static Vector2 Drag2D(Vector2 scrollPosition, Rect position)
@@ -559,45 +765,45 @@ public class SkeletonDataAssetInspector : Editor {
 		return scrollPosition;
 	}
 	*/
-		
-	public override GUIContent GetPreviewTitle () {
+
+	public override GUIContent GetPreviewTitle() {
 		return new GUIContent("Preview");
 	}
-		
-	public override void OnPreviewSettings () {
+
+	public override void OnPreviewSettings() {
 		if (!m_initialized) {
 			GUILayout.HorizontalSlider(0, 0, 2, GUILayout.MaxWidth(64));
 		} else {
 			float speed = GUILayout.HorizontalSlider(m_skeletonAnimation.timeScale, 0, 2, GUILayout.MaxWidth(64));
-				
+
 			//snap to nearest 0.25
 			float y = speed / 0.25f;
 			int q = Mathf.RoundToInt(y);
 			speed = q * 0.25f;
-				
+
 			m_skeletonAnimation.timeScale = speed;
 		}
 	}
-		
+
 	//TODO:  Fix first-import error
 	//TODO:  Update preview without thumbnail
-	public override Texture2D RenderStaticPreview (string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
+	public override Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
 		Texture2D tex = new Texture2D(width, height, TextureFormat.ARGB32, false);
-			
+
 		this.InitPreview();
-			
+
 		if (this.m_previewUtility.m_Camera == null)
 			return null;
-			
+
 		m_requireRefresh = true;
 		this.DoRenderPreview(false);
 		AdjustCameraGoals(false);
-			
+
 		this.m_previewUtility.m_Camera.orthographicSize = m_orthoGoal / 2;
 		this.m_previewUtility.m_Camera.transform.position = m_posGoal;
 		this.m_previewUtility.BeginStaticPreview(new Rect(0, 0, width, height));
 		this.DoRenderPreview(false);
-			
+
 		//TODO:  Figure out why this is throwing errors on first attempt
 		//		if(m_previewUtility != null){
 		//			Handles.SetCamera(this.m_previewUtility.m_Camera);

+ 9 - 3
spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs

@@ -33,15 +33,17 @@ using UnityEngine;
 
 [CustomEditor(typeof(SkeletonRenderer))]
 public class SkeletonRendererInspector : Editor {
-	protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles;
+	protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, submeshSeparators;
 
 	protected virtual void OnEnable () {
+		SpineEditorUtilities.ConfirmInitialization();
 		skeletonDataAsset = serializedObject.FindProperty("skeletonDataAsset");
 		initialSkinName = serializedObject.FindProperty("initialSkinName");
 		normals = serializedObject.FindProperty("calculateNormals");
 		tangents = serializedObject.FindProperty("calculateTangents");
 		meshes = serializedObject.FindProperty("renderMeshes");
 		immutableTriangles = serializedObject.FindProperty("immutableTriangles");
+		submeshSeparators = serializedObject.FindProperty("submeshSeparators");
 	}
 
 	protected virtual void gui () {
@@ -52,8 +54,11 @@ public class SkeletonRendererInspector : Editor {
 		float reloadWidth = GUI.skin.label.CalcSize(new GUIContent("Reload")).x + 20;
 		if (GUILayout.Button("Reload", GUILayout.Width(reloadWidth))) {
 			if (component.skeletonDataAsset != null) {
-				if (component.skeletonDataAsset.atlasAsset != null)
-					component.skeletonDataAsset.atlasAsset.Reset();
+				foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
+					if (aa != null)
+						aa.Reset();
+				}
+				
 				component.skeletonDataAsset.Reset();
 			}
 			component.Reset();
@@ -92,6 +97,7 @@ public class SkeletonRendererInspector : Editor {
 			new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility"));
 		EditorGUILayout.PropertyField(normals);
 		EditorGUILayout.PropertyField(tangents);
+		EditorGUILayout.PropertyField(submeshSeparators, true);
 	}
 
 	override public void OnInspectorGUI () {

+ 585 - 0
spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs

@@ -0,0 +1,585 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.1
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software (typically granted by licensing Spine), you
+ * may not (a) modify, translate, adapt or otherwise create derivative works,
+ * improvements of the Software or develop new applications using the Software
+ * or (b) remove, delete, alter or obscure any trademarks or any copyright,
+ * trademark, patent or other intellectual property or proprietary rights
+ * notices on or in the Software, including any copy thereof. Redistributions
+ * in binary or source form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Spine Attribute Drawers created by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+using UnityEngine;
+using UnityEditor;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using System.Reflection;
+using Spine;
+
+
+public struct SpineDrawerValuePair {
+	public string str;
+	public SerializedProperty property;
+
+	public SpineDrawerValuePair(string val, SerializedProperty property) {
+		this.str = val;
+		this.property = property;
+	}
+}
+
+[CustomPropertyDrawer(typeof(SpineSlot))]
+public class SpineSlotDrawer : PropertyDrawer {
+	SkeletonDataAsset skeletonDataAsset;
+
+
+	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+		if (property.propertyType != SerializedPropertyType.String) {
+			EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
+			return;
+		}
+
+		SpineSlot attrib = (SpineSlot)attribute;
+
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
+
+		if (dataProperty != null) {
+			if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
+				skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
+			} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
+				var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
+				if (renderer != null)
+					skeletonDataAsset = renderer.skeletonDataAsset;
+			} else {
+				EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
+				return;
+			}
+
+		} else if (property.serializedObject.targetObject is Component) {
+			var component = (Component)property.serializedObject.targetObject;
+			if (component.GetComponent<SkeletonRenderer>() != null) {
+				var skeletonRenderer = component.GetComponent<SkeletonRenderer>();
+				skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
+			}
+		}
+
+		if (skeletonDataAsset == null) {
+			EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
+			return;
+		}
+
+		position = EditorGUI.PrefixLabel(position, label);
+
+		if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
+			Selector(property);
+		}
+
+	}
+
+	void Selector(SerializedProperty property) {
+		SpineSlot attrib = (SpineSlot)attribute;
+		SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
+		if (data == null)
+			return;
+
+		GenericMenu menu = new GenericMenu();
+
+		menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
+		menu.AddSeparator("");
+
+		for (int i = 0; i < data.Slots.Count; i++) {
+			string name = data.Slots[i].Name;
+			if (name.StartsWith(attrib.startsWith))
+				menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+		}
+
+		menu.ShowAsContext();
+	}
+
+	void HandleSelect(object val) {
+		var pair = (SpineDrawerValuePair)val;
+		pair.property.stringValue = pair.str;
+		pair.property.serializedObject.ApplyModifiedProperties();
+	}
+
+	public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
+		return 18;
+	}
+}
+
+[CustomPropertyDrawer(typeof(SpineSkin))]
+public class SpineSkinDrawer : PropertyDrawer {
+	SkeletonDataAsset skeletonDataAsset;
+
+	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+		if (property.propertyType != SerializedPropertyType.String) {
+			EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
+			return;
+		}
+
+		SpineSkin attrib = (SpineSkin)attribute;
+
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
+
+		if (dataProperty != null) {
+			if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
+				skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
+			} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
+				var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
+				if (renderer != null)
+					skeletonDataAsset = renderer.skeletonDataAsset;
+			} else {
+				EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
+				return;
+			}
+
+		} else if (property.serializedObject.targetObject is Component) {
+			var component = (Component)property.serializedObject.targetObject;
+			if (component.GetComponent<SkeletonRenderer>() != null) {
+				var skeletonRenderer = component.GetComponent<SkeletonRenderer>();
+				skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
+			}
+		}
+
+		if (skeletonDataAsset == null) {
+			EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
+			return;
+		}
+
+		position = EditorGUI.PrefixLabel(position, label);
+
+		if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
+			Selector(property);
+		}
+
+	}
+
+	void Selector(SerializedProperty property) {
+		SpineSkin attrib = (SpineSkin)attribute;
+		SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
+		if (data == null)
+			return;
+
+		GenericMenu menu = new GenericMenu();
+
+		menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
+		menu.AddSeparator("");
+
+		for (int i = 0; i < data.Skins.Count; i++) {
+			string name = data.Skins[i].Name;
+			if (name.StartsWith(attrib.startsWith))
+				menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+		}
+
+		menu.ShowAsContext();
+	}
+
+	void HandleSelect(object val) {
+		var pair = (SpineDrawerValuePair)val;
+		pair.property.stringValue = pair.str;
+		pair.property.serializedObject.ApplyModifiedProperties();
+	}
+
+	public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
+		return 18;
+	}
+}
+
+[CustomPropertyDrawer(typeof(SpineAtlasRegion))]
+public class SpineAtlasRegionDrawer : PropertyDrawer {
+	Component component;
+	SerializedProperty atlasProp;
+
+	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+		if (property.propertyType != SerializedPropertyType.String) {
+			EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
+			return;
+		}
+
+		component = (Component)property.serializedObject.targetObject;
+
+		if (component != null)
+			atlasProp = property.serializedObject.FindProperty("atlasAsset");
+		else
+			atlasProp = null;
+
+
+		if (atlasProp == null) {
+			EditorGUI.LabelField(position, "ERROR:", "Must have AtlasAsset variable!");
+			return;
+		} else if (atlasProp.objectReferenceValue == null) {
+			EditorGUI.LabelField(position, "ERROR:", "Atlas variable must not be null!");
+			return;
+		} else if (atlasProp.objectReferenceValue.GetType() != typeof(AtlasAsset)) {
+			EditorGUI.LabelField(position, "ERROR:", "Atlas variable must be of type AtlasAsset!");
+		}
+
+		position = EditorGUI.PrefixLabel(position, label);
+
+		if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
+			Selector(property);
+		}
+
+	}
+
+	void Selector(SerializedProperty property) {
+		GenericMenu menu = new GenericMenu();
+		AtlasAsset atlasAsset = (AtlasAsset)atlasProp.objectReferenceValue;
+		Atlas atlas = atlasAsset.GetAtlas();
+		FieldInfo field = typeof(Atlas).GetField("regions", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.NonPublic);
+		List<AtlasRegion> regions = (List<AtlasRegion>)field.GetValue(atlas);
+
+		for (int i = 0; i < regions.Count; i++) {
+			string name = regions[i].name;
+			menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+		}
+
+
+		menu.ShowAsContext();
+	}
+
+	void HandleSelect(object val) {
+		var pair = (SpineDrawerValuePair)val;
+		pair.property.stringValue = pair.str;
+		pair.property.serializedObject.ApplyModifiedProperties();
+	}
+
+	public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
+		return 18;
+	}
+}
+
+[CustomPropertyDrawer(typeof(SpineAnimation))]
+public class SpineAnimationDrawer : PropertyDrawer {
+	SkeletonDataAsset skeletonDataAsset;
+
+
+	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+
+
+		if (property.propertyType != SerializedPropertyType.String) {
+			EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
+			return;
+		}
+
+		SpineAnimation attrib = (SpineAnimation)attribute;
+
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
+
+		if (dataProperty != null) {
+			if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
+				skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
+			} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
+				var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
+				if (renderer != null)
+					skeletonDataAsset = renderer.skeletonDataAsset;
+			} else {
+				EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
+				return;
+			}
+		} else if (property.serializedObject.targetObject is Component) {
+			var component = (Component)property.serializedObject.targetObject;
+			if (component.GetComponent<SkeletonRenderer>() != null) {
+				var skeletonRenderer = component.GetComponent<SkeletonRenderer>();
+				skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
+			}
+		}
+
+		if (skeletonDataAsset == null) {
+			EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
+			return;
+		}
+
+		position = EditorGUI.PrefixLabel(position, label);
+
+		if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
+			Selector(property);
+		}
+
+	}
+
+	void Selector(SerializedProperty property) {
+
+		SpineAnimation attrib = (SpineAnimation)attribute;
+
+		GenericMenu menu = new GenericMenu();
+
+		var animations = skeletonDataAsset.GetAnimationStateData().SkeletonData.Animations;
+		for (int i = 0; i < animations.Count; i++) {
+			string name = animations[i].Name;
+			if (name.StartsWith(attrib.startsWith))
+				menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+		}
+
+		menu.ShowAsContext();
+	}
+
+	void HandleSelect(object val) {
+		var pair = (SpineDrawerValuePair)val;
+		pair.property.stringValue = pair.str;
+		pair.property.serializedObject.ApplyModifiedProperties();
+	}
+
+	public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
+		return 18;
+	}
+}
+
+[CustomPropertyDrawer(typeof(SpineAttachment))]
+public class SpineAttachmentDrawer : PropertyDrawer {
+
+	SkeletonDataAsset skeletonDataAsset;
+	SkeletonRenderer skeletonRenderer;
+
+	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+
+		if (property.propertyType != SerializedPropertyType.String) {
+			EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
+			return;
+		}
+
+		SpineAttachment attrib = (SpineAttachment)attribute;
+
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
+
+		if (dataProperty != null) {
+			if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
+				skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
+			} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
+				var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
+				if (renderer != null)
+					skeletonDataAsset = renderer.skeletonDataAsset;
+				else {
+					EditorGUI.LabelField(position, "ERROR:", "No SkeletonRenderer");
+				}
+			} else {
+				EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
+				return;
+			}
+
+		} else if (property.serializedObject.targetObject is Component) {
+			var component = (Component)property.serializedObject.targetObject;
+			if (component.GetComponent<SkeletonRenderer>() != null) {
+				skeletonRenderer = component.GetComponent<SkeletonRenderer>();
+				skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
+			}
+		}
+
+		if (skeletonDataAsset == null && skeletonRenderer == null) {
+			EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset or SkeletonRenderer");
+			return;
+		}
+
+		position = EditorGUI.PrefixLabel(position, label);
+
+		if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
+			Selector(property);
+		}
+
+	}
+
+	void Selector(SerializedProperty property) {
+		SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
+
+		if (data == null)
+			return;
+
+		SpineAttachment attrib = (SpineAttachment)attribute;
+
+		List<Skin> validSkins = new List<Skin>();
+
+		if (skeletonRenderer != null && attrib.currentSkinOnly) {
+			if (skeletonRenderer.skeleton.Skin != null) {
+				validSkins.Add(skeletonRenderer.skeleton.Skin);
+			} else {
+				validSkins.Add(data.Skins[0]);
+			}
+		} else {
+			foreach (Skin skin in data.Skins) {
+				if (skin != null)
+					validSkins.Add(skin);
+			}
+		}
+
+		GenericMenu menu = new GenericMenu();
+		List<string> attachmentNames = new List<string>();
+		List<string> placeholderNames = new List<string>();
+
+		string prefix = "";
+
+		if (skeletonRenderer != null && attrib.currentSkinOnly)
+			menu.AddDisabledItem(new GUIContent(skeletonRenderer.gameObject.name + " (SkeletonRenderer)"));
+		else
+			menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
+		menu.AddSeparator("");
+
+		menu.AddItem(new GUIContent("Null"), property.stringValue == "", HandleSelect, new SpineDrawerValuePair("", property));
+		menu.AddSeparator("");
+
+		Skin defaultSkin = data.Skins[0];
+
+		SerializedProperty slotProperty = property.serializedObject.FindProperty(attrib.slotField);
+		string slotMatch = "";
+		if (slotProperty != null) {
+			if (slotProperty.propertyType == SerializedPropertyType.String) {
+				slotMatch = slotProperty.stringValue.ToLower();
+			}
+		}
+
+		foreach (Skin skin in validSkins) {
+			string skinPrefix = skin.Name + "/";
+
+			if (validSkins.Count > 1)
+				prefix = skinPrefix;
+
+			for (int i = 0; i < data.Slots.Count; i++) {
+				if (slotMatch.Length > 0 && data.Slots[i].Name.ToLower().Contains(slotMatch) == false)
+					continue;
+
+				attachmentNames.Clear();
+				placeholderNames.Clear();
+
+				skin.FindNamesForSlot(i, attachmentNames);
+				if (skin != defaultSkin) {
+					defaultSkin.FindNamesForSlot(i, attachmentNames);
+					skin.FindNamesForSlot(i, placeholderNames);
+				}
+					
+
+				for (int a = 0; a < attachmentNames.Count; a++) {
+					
+					string attachmentPath = attachmentNames[a];
+					string menuPath = prefix + data.Slots[i].Name + "/" + attachmentPath;
+					string name = attachmentNames[a];
+
+					if (attrib.returnAttachmentPath)
+						name = skin.Name + "/" + data.Slots[i].Name + "/" + attachmentPath;
+
+					if (attrib.placeholdersOnly && placeholderNames.Contains(attachmentPath) == false) {
+						menu.AddDisabledItem(new GUIContent(menuPath));
+					} else {
+						menu.AddItem(new GUIContent(menuPath), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+					}
+					
+					
+				}
+			}
+		}
+
+
+		menu.ShowAsContext();
+	}
+
+	void HandleSelect(object val) {
+		var pair = (SpineDrawerValuePair)val;
+		pair.property.stringValue = pair.str;
+		pair.property.serializedObject.ApplyModifiedProperties();
+	}
+
+	public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
+		return 18;
+	}
+}
+
+[CustomPropertyDrawer(typeof(SpineBone))]
+public class SpineBoneDrawer : PropertyDrawer {
+	SkeletonDataAsset skeletonDataAsset;
+
+	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
+		if (property.propertyType != SerializedPropertyType.String) {
+			EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
+			return;
+		}
+
+		SpineBone attrib = (SpineBone)attribute;
+
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
+
+		if (dataProperty != null) {
+			if (dataProperty.objectReferenceValue is SkeletonDataAsset) {
+				skeletonDataAsset = (SkeletonDataAsset)dataProperty.objectReferenceValue;
+			} else if (dataProperty.objectReferenceValue is SkeletonRenderer) {
+				var renderer = (SkeletonRenderer)dataProperty.objectReferenceValue;
+				if (renderer != null)
+					skeletonDataAsset = renderer.skeletonDataAsset;
+			} else {
+				EditorGUI.LabelField(position, "ERROR:", "Invalid reference type");
+				return;
+			}
+
+		} else if (property.serializedObject.targetObject is Component) {
+			var component = (Component)property.serializedObject.targetObject;
+			if (component.GetComponent<SkeletonRenderer>() != null) {
+				var skeletonRenderer = component.GetComponent<SkeletonRenderer>();
+				skeletonDataAsset = skeletonRenderer.skeletonDataAsset;
+			}
+		}
+
+		if (skeletonDataAsset == null) {
+			EditorGUI.LabelField(position, "ERROR:", "Must have reference to a SkeletonDataAsset");
+			return;
+		}
+
+		position = EditorGUI.PrefixLabel(position, label);
+
+		if (GUI.Button(position, property.stringValue, EditorStyles.popup)) {
+			Selector(property);
+		}
+
+	}
+
+	void Selector(SerializedProperty property) {
+		SpineBone attrib = (SpineBone)attribute;
+		SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
+		if (data == null)
+			return;
+
+		GenericMenu menu = new GenericMenu();
+
+		menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
+		menu.AddSeparator("");
+
+		for (int i = 0; i < data.Bones.Count; i++) {
+			string name = data.Bones[i].Name;
+			if (name.StartsWith(attrib.startsWith))
+				menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+		}
+
+		menu.ShowAsContext();
+	}
+
+	void HandleSelect(object val) {
+		var pair = (SpineDrawerValuePair)val;
+		pair.property.stringValue = pair.str;
+		pair.property.serializedObject.ApplyModifiedProperties();
+	}
+
+	public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
+		return 18;
+	}
+}

+ 8 - 0
spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f2de282d583d4a641bf1c349f0a3eef9
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 

+ 369 - 157
spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs

@@ -39,6 +39,8 @@ using System.Collections;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
+using System.Linq;
+using System.Reflection;
 using Spine;
 
 [InitializeOnLoad]
@@ -51,10 +53,12 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		public static Texture2D poseBones;
 		public static Texture2D boneNib;
 		public static Texture2D slot;
+		public static Texture2D slotRoot;
 		public static Texture2D skinPlaceholder;
 		public static Texture2D image;
 		public static Texture2D boundingBox;
 		public static Texture2D mesh;
+		public static Texture2D weights;
 		public static Texture2D skin;
 		public static Texture2D skinsRoot;
 		public static Texture2D animation;
@@ -78,7 +82,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 						new Vector3(0.1f, 0.1f, 0)
 					};
 					_boneMesh.uv = new Vector2[4];
-					_boneMesh.triangles = new int[6]{0,1,2,2,3,0};
+					_boneMesh.triangles = new int[6] { 0, 1, 2, 2, 3, 0 };
 					_boneMesh.RecalculateBounds();
 					_boneMesh.RecalculateNormals();
 				}
@@ -108,17 +112,19 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 		internal static Material _boneMaterial;
 
-		public static void Initialize () {
+		public static void Initialize() {
 			skeleton = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skeleton.png");
 			nullBone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-null.png");
 			bone = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-bone.png");
 			poseBones = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-poseBones.png");
 			boneNib = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-boneNib.png");
 			slot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-slot.png");
+			slotRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-slotRoot.png");
 			skinPlaceholder = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinPlaceholder.png");
 			image = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-image.png");
 			boundingBox = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-boundingBox.png");
 			mesh = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-mesh.png");
+			weights = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-weights.png");
 			skin = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinPlaceholder.png");
 			skinsRoot = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-skinsRoot.png");
 			animation = (Texture2D)AssetDatabase.LoadMainAssetAtPath(SpineEditorUtilities.editorGUIPath + "/icon-animation.png");
@@ -140,12 +146,17 @@ public class SpineEditorUtilities : AssetPostprocessor {
 	public static float defaultScale = 0.01f;
 	public static float defaultMix = 0.2f;
 	public static string defaultShader = "Spine/Skeleton";
+	public static bool initialized;
+
+	static SpineEditorUtilities() {
+		Initialize();
+	}
 	
-	static SpineEditorUtilities () {
+	static void Initialize(){
 		DirectoryInfo rootDir = new DirectoryInfo(Application.dataPath);
 		FileInfo[] files = rootDir.GetFiles("SpineEditorUtilities.cs", SearchOption.AllDirectories);
 		editorPath = Path.GetDirectoryName(files[0].FullName.Replace("\\", "/").Replace(Application.dataPath, "Assets"));
-		editorGUIPath = editorPath + "/GUI";	
+		editorGUIPath = editorPath + "/GUI";
 
 		Icons.Initialize();
 
@@ -156,9 +167,15 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI;
 
 		HierarchyWindowChanged();
+		initialized = true;
+	}
+	
+	public static void ConfirmInitialization(){
+		if(!initialized || Icons.skeleton == null)
+		Initialize();
 	}
 
-	static void HierarchyWindowChanged () {
+	static void HierarchyWindowChanged() {
 		skeletonRendererTable.Clear();
 		skeletonUtilityBoneTable.Clear();
 
@@ -172,122 +189,353 @@ public class SpineEditorUtilities : AssetPostprocessor {
 			skeletonUtilityBoneTable.Add(b.gameObject.GetInstanceID(), b);
 	}
 
-	static void HierarchyWindowItemOnGUI (int instanceId, Rect selectionRect) {
+	static void HierarchyWindowItemOnGUI(int instanceId, Rect selectionRect) {
 		if (skeletonRendererTable.ContainsKey(instanceId)) {
-			Rect r = new Rect(selectionRect); 
+			Rect r = new Rect(selectionRect);
 			r.x = r.width - 15;
 			r.width = 15;
 
 			GUI.Label(r, Icons.spine);
 		} else if (skeletonUtilityBoneTable.ContainsKey(instanceId)) {
-				Rect r = new Rect(selectionRect); 
-				r.x -= 26;
+			Rect r = new Rect(selectionRect);
+			r.x -= 26;
 
-				if (skeletonUtilityBoneTable[instanceId] != null) {
-					if (skeletonUtilityBoneTable[instanceId].transform.childCount == 0)
-						r.x += 13;
-				
-					r.y += 2;
+			if (skeletonUtilityBoneTable[instanceId] != null) {
+				if (skeletonUtilityBoneTable[instanceId].transform.childCount == 0)
+					r.x += 13;
 
-					r.width = 13;
-					r.height = 13;
+				r.y += 2;
 
-					if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow) {
-						GUI.DrawTexture(r, Icons.bone);
-					} else {
-						GUI.DrawTexture(r, Icons.poseBones);
-					}
-				}
+				r.width = 13;
+				r.height = 13;
 
+				if (skeletonUtilityBoneTable[instanceId].mode == SkeletonUtilityBone.Mode.Follow) {
+					GUI.DrawTexture(r, Icons.bone);
+				} else {
+					GUI.DrawTexture(r, Icons.poseBones);
+				}
 			}
 
+		}
+
 	}
-	
-	[MenuItem("Assets/Spine/Ingest")]
-	static void IngestSpineProjectFromSelection () {
-		TextAsset spineJson = null;
-		TextAsset atlasText = null;
 
-		List<TextAsset> spineJsonList = new List<TextAsset>();
+	static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) {
+		ImportSpineContent(imported, false);
+	}
+	public static void ImportSpineContent(string[] imported, bool reimport = false) {
+
+		List<string> atlasPaths = new List<string>();
+		List<string> imagePaths = new List<string>();
+		List<string> skeletonPaths = new List<string>();
+
+		foreach (string str in imported) {
+			string extension = Path.GetExtension(str).ToLower();
+			switch (extension) {
+				case ".txt":
+					if (str.EndsWith(".atlas.txt")) {
+						atlasPaths.Add(str);
+					}
+					break;
+				case ".png":
+				case ".jpg":
+					imagePaths.Add(str);
+					break;
+				case ".json":
+					TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(str, typeof(TextAsset));
+					if (IsSpineJSON(spineJson)) {
+						skeletonPaths.Add(str);
+					}
+					break;
+			}
+		}
+
 
-		foreach (UnityEngine.Object o in Selection.objects) {
-			if (o.GetType() != typeof(TextAsset))
+		List<AtlasAsset> atlases = new List<AtlasAsset>();
+
+		//import atlases first
+		foreach (string ap in atlasPaths) {
+			if (!reimport && CheckForValidAtlas(ap))
 				continue;
-			
-			string fileName = Path.GetFileName(AssetDatabase.GetAssetPath(o));
-			
-			if (fileName.EndsWith(".json"))
-				spineJson = (TextAsset)o;
-			else if (fileName.EndsWith(".atlas.txt"))
-					atlasText = (TextAsset)o;
+
+			TextAsset atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(ap, typeof(TextAsset));
+			AtlasAsset atlas = IngestSpineAtlas(atlasText);
+			atlases.Add(atlas);
 		}
-		
-		if (spineJson == null) {
-			EditorUtility.DisplayDialog("Error!", "Spine JSON file not found in selection!", "OK");
-			return;
+
+		//import skeletons and match them with atlases
+		bool abortSkeletonImport = false;
+		foreach (string sp in skeletonPaths) {
+			if (!reimport && CheckForValidSkeletonData(sp)) {
+				Debug.Log("Automatically skipping: " + sp);
+				continue;
+			}
+				
+
+			string dir = Path.GetDirectoryName(sp);
+
+			var localAtlases = FindAtlasesAtPath(dir);
+			var requiredPaths = GetRequiredAtlasRegions(sp);
+			var atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases);
+
+			if (atlasMatch != null) {
+				IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch);
+			} else {
+				bool resolved = false;
+				while (!resolved) {
+					int result = EditorUtility.DisplayDialogComplex("Skeleton JSON Import Error!", "Could not find matching AtlasAsset for " + Path.GetFileNameWithoutExtension(sp), "Select", "Skip", "Abort");
+					switch (result) {
+						case -1:
+							Debug.Log("Select Atlas");
+							AtlasAsset selectedAtlas = GetAtlasDialog(Path.GetDirectoryName(sp));
+							if (selectedAtlas != null) {
+								localAtlases.Clear();
+								localAtlases.Add(selectedAtlas);
+								atlasMatch = GetMatchingAtlas(requiredPaths, localAtlases);
+								if (atlasMatch != null) {
+									resolved = true;
+									IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasMatch);
+								}
+							}
+
+							break;
+						case 0:
+							var atlasList = MultiAtlasDialog(requiredPaths, Path.GetDirectoryName(sp), Path.GetFileNameWithoutExtension(sp));
+
+							if (atlasList != null)
+								IngestSpineProject(AssetDatabase.LoadAssetAtPath(sp, typeof(TextAsset)) as TextAsset, atlasList.ToArray());
+
+							resolved = true;
+							break;
+
+						case 1:
+							Debug.Log("Skipped importing: " + Path.GetFileName(sp));
+							resolved = true;
+							break;
+						
+
+						case 2:
+							//abort
+							abortSkeletonImport = true;
+							resolved = true;
+							break;
+					}
+				}
+			}
+
+			if (abortSkeletonImport)
+				break;
 		}
-		
-		string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
-		string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
-		
-		if (atlasText == null) {
-			string atlasPath = assetPath + "/" + primaryName + ".atlas.txt";
-			atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset));
+
+		//TODO:  any post processing of images
+	}
+
+	static bool CheckForValidSkeletonData(string skeletonJSONPath) {
+
+		string dir = Path.GetDirectoryName(skeletonJSONPath);
+		TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(skeletonJSONPath, typeof(TextAsset));
+		DirectoryInfo dirInfo = new DirectoryInfo(dir);
+
+		FileInfo[] files = dirInfo.GetFiles("*.asset");
+
+		foreach (var f in files) {
+			string localPath = dir + "/" + f.Name;
+			var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
+			if (obj is SkeletonDataAsset) {
+				var skeletonDataAsset = (SkeletonDataAsset)obj;
+				if (skeletonDataAsset.skeletonJSON == textAsset)
+					return true;
+			}
 		}
 
-		AtlasAsset atlasAsset = IngestSpineAtlas(atlasText);
+		return false;
+	}
+
+	static bool CheckForValidAtlas(string atlasPath) {
+
+		string dir = Path.GetDirectoryName(atlasPath);
+		TextAsset textAsset = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset));
+		DirectoryInfo dirInfo = new DirectoryInfo(dir);
+
+		FileInfo[] files = dirInfo.GetFiles("*.asset");
+
+		foreach (var f in files) {
+			string localPath = dir + "/" + f.Name;
+			var obj = AssetDatabase.LoadAssetAtPath(localPath, typeof(Object));
+			if (obj is AtlasAsset) {
+				var atlasAsset = (AtlasAsset)obj;
+				if (atlasAsset.atlasFile == textAsset)
+					return true;
+			}
+		}
 
-		IngestSpineProject(spineJson, atlasAsset);
+		return false;
 	}
 
-	static void OnPostprocessAllAssets (string[] imported, string[] deleted, string[] moved, string[] movedFromAssetPaths) {
-		//debug
-//		return;
+	static List<AtlasAsset> MultiAtlasDialog(List<string> requiredPaths, string initialDirectory, string header = "") {
 
-		AtlasAsset sharedAtlas = null;
+		List<AtlasAsset> atlasAssets = new List<AtlasAsset>();
 
-		System.Array.Sort<string>(imported);
+		bool resolved = false;
+		string lastAtlasPath = initialDirectory;
+		while (!resolved) {
+			StringBuilder sb = new StringBuilder();
+			sb.AppendLine(header);
+			sb.AppendLine("Atlases:");
+			if (atlasAssets.Count == 0) {
+				sb.AppendLine("\t--none--");
+			}
+			for (int i = 0; i < atlasAssets.Count; i++) {
+				sb.AppendLine("\t" + atlasAssets[i].name);
+			}
 
-		foreach (string str in imported) {
-			if (Path.GetExtension(str).ToLower() == ".json") {
-				TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(str, typeof(TextAsset));
-				if (IsSpineJSON(spineJson)) {
-
-					if (sharedAtlas != null) {
-						string spinePath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
-						string atlasPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(sharedAtlas));
-						if (spinePath != atlasPath)
-							sharedAtlas = null;
-					}
+				sb.AppendLine();
+			sb.AppendLine("Missing Regions:");
 
-					SkeletonDataAsset data = AutoIngestSpineProject(spineJson, sharedAtlas);
-					if (data == null)
-						continue;
+			List<string> missingRegions = new List<string>(requiredPaths);
 
-					sharedAtlas = data.atlasAsset;
+			foreach (var atlasAsset in atlasAssets) {
+				var atlas = atlasAsset.GetAtlas();
+				for (int i = 0; i < missingRegions.Count; i++) {
+					if (atlas.FindRegion(missingRegions[i]) != null) {
+						missingRegions.RemoveAt(i);
+						i--;
+					}
+				}
+			}
 
+			if (missingRegions.Count == 0) {
+				break;
+			}
 
-					string dir = Path.GetDirectoryName(Path.GetDirectoryName(AssetDatabase.GetAssetPath(data)));
-					string prefabPath = Path.Combine(dir, data.skeletonJSON.name + ".prefab").Replace("\\", "/");
+			for (int i = 0; i < missingRegions.Count; i++) {
+				sb.AppendLine("\t" + missingRegions[i]);
+			}
 
-					if (File.Exists(prefabPath) == false) {
-						SkeletonAnimation anim = SpawnAnimatedSkeleton(data);
-						PrefabUtility.CreatePrefab(prefabPath, anim.gameObject, ReplacePrefabOptions.ReplaceNameBased);
-						if (EditorApplication.isPlaying)
-							GameObject.Destroy(anim.gameObject);
-						else
-							GameObject.DestroyImmediate(anim.gameObject);
-					} else {
+			int result = EditorUtility.DisplayDialogComplex("Atlas Selection", sb.ToString(), "Select", "Finish", "Abort");
+
+			switch(result){
+				case 0:
+					AtlasAsset selectedAtlasAsset = GetAtlasDialog(lastAtlasPath);
+					if (selectedAtlasAsset != null) {
+						var atlas = selectedAtlasAsset.GetAtlas();
+						bool hasValidRegion = false;
+						foreach (string str in missingRegions) {
+							if (atlas.FindRegion(str) != null) {
+								hasValidRegion = true;
+								break;
+							}
+						}
 
+						atlasAssets.Add(selectedAtlasAsset);
 					}
+					break;
+
+				case 1:
+					resolved = true;
+					break;
+
+				case 2:
+					atlasAssets = null;
+					resolved = true;
+					break;
+			}
+
+
+		}
+		
+
+		return atlasAssets;
+	}
+
+	static AtlasAsset GetAtlasDialog(string dirPath) {
+		string path = EditorUtility.OpenFilePanel("Select AtlasAsset...", dirPath, "asset");
+		if (path == "")
+			return null;
+
+		int subLen = Application.dataPath.Length - 6;
+		string assetRelativePath = path.Substring(subLen, path.Length - subLen).Replace("\\", "/");
 
+		Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAsset));
 
+		if (obj == null || obj.GetType() != typeof(AtlasAsset))
+			return null;
+
+		return (AtlasAsset)obj;
+	}
+
+	public static List<string> GetRequiredAtlasRegions(string jsonPath) {
+		List<string> requiredPaths = new List<string>();
+
+		TextAsset spineJson = (TextAsset)AssetDatabase.LoadAssetAtPath(jsonPath, typeof(TextAsset));
+
+		StringReader reader = new StringReader(spineJson.text);
+		var root = Json.Deserialize(reader) as Dictionary<string, object>;
+
+		foreach (KeyValuePair<string, object> entry in (Dictionary<string, object>)root["skins"]) {
+			foreach (KeyValuePair<string, object> slotEntry in (Dictionary<string, object>)entry.Value) {
+
+				foreach (KeyValuePair<string, object> attachmentEntry in ((Dictionary<string, object>)slotEntry.Value)) {
+					var data = ((Dictionary<string, object>)attachmentEntry.Value);
+					if (data.ContainsKey("path"))
+						requiredPaths.Add((string)data["path"]);
+					else if (data.ContainsKey("name"))
+						requiredPaths.Add((string)data["name"]);
+					else
+						requiredPaths.Add(attachmentEntry.Key);
+					//requiredPaths.Add((string)sdf["path"]);
+				}
+			}
+		}
+
+		return requiredPaths;
+	}
+	static AtlasAsset GetMatchingAtlas(List<string> requiredPaths, List<AtlasAsset> atlasAssets) {
+		AtlasAsset atlasAssetMatch = null;
+
+		foreach (AtlasAsset a in atlasAssets) {
+			Atlas atlas = a.GetAtlas();
+			bool failed = false;
+			foreach (string regionPath in requiredPaths) {
+				if (atlas.FindRegion(regionPath) == null) {
+					failed = true;
+					break;
 				}
 			}
+
+			if (!failed) {
+				atlasAssetMatch = a;
+				break;
+			}
+
+		}
+
+		return atlasAssetMatch;
+	}
+
+	static List<AtlasAsset> FindAtlasesAtPath(string path) {
+		List<AtlasAsset> arr = new List<AtlasAsset>();
+
+		DirectoryInfo dir = new DirectoryInfo(path);
+		FileInfo[] assetInfoArr = dir.GetFiles("*.asset");
+
+		int subLen = Application.dataPath.Length - 6;
+
+		foreach (var f in assetInfoArr) {
+			string assetRelativePath = f.FullName.Substring(subLen, f.FullName.Length - subLen).Replace("\\", "/");
+
+			Object obj = AssetDatabase.LoadAssetAtPath(assetRelativePath, typeof(AtlasAsset));
+			if (obj != null) {
+				arr.Add(obj as AtlasAsset);
+			}
+
 		}
+
+
+		return arr;
 	}
 
-	static bool IsSpineJSON (TextAsset asset) {
+	public static bool IsSpineJSON(TextAsset asset) {
 		object obj = Json.Deserialize(new StringReader(asset.text));
 		if (obj == null) {
 			Debug.LogError("Is not valid JSON");
@@ -307,51 +555,7 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return true;
 	}
 
-	static SkeletonDataAsset AutoIngestSpineProject (TextAsset spineJson, Object atlasSource = null) {
-		TextAsset atlasText = null;
-		AtlasAsset atlasAsset = null;
-
-		if (atlasSource != null) {
-			if (atlasSource.GetType() == typeof(TextAsset)) {
-				atlasText = (TextAsset)atlasSource;
-			} else if (atlasSource.GetType() == typeof(AtlasAsset)) {
-					atlasAsset = (AtlasAsset)atlasSource;
-				}
-		}
-
-		if (atlasText == null && atlasAsset == null) {
-			string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
-			string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
-			
-			if (atlasText == null) {
-				string atlasPath = assetPath + "/" + primaryName + ".atlas.txt";
-				atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(TextAsset));
-
-				if (atlasText == null) {
-					//can't find atlas, likely because using a shared atlas
-					bool abort = !EditorUtility.DisplayDialog("Atlas not Found", "Expecting " + spineJson.name + ".atlas\n" + "Press OK to select Atlas", "OK", "Abort");
-					if (abort) {
-						//do nothing, let it error later
-					} else {
-						string path = EditorUtility.OpenFilePanel("Find Atlas source...", Path.GetDirectoryName(Application.dataPath) + "/" + assetPath, "txt");
-						if (path != "") {
-							path = path.Replace("\\", "/");
-							path = path.Replace(Application.dataPath.Replace("\\", "/"), "Assets");
-							atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset));
-						}
-					}
-
-				}
-			}
-		}
-
-		if (atlasAsset == null)
-			atlasAsset = IngestSpineAtlas(atlasText);
-
-		return IngestSpineProject(spineJson, atlasAsset);
-	}
-
-	static AtlasAsset IngestSpineAtlas (TextAsset atlasText) {
+	static AtlasAsset IngestSpineAtlas(TextAsset atlasText) {
 		if (atlasText == null) {
 			Debug.LogWarning("Atlas source cannot be null!");
 			return null;
@@ -376,17 +580,17 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 		string[] atlasLines = atlasStr.Split('\n');
 		List<string> pageFiles = new List<string>();
-		for (int i = 0; i < atlasLines.Length-1; i++) {
+		for (int i = 0; i < atlasLines.Length - 1; i++) {
 			if (atlasLines[i].Length == 0)
 				pageFiles.Add(atlasLines[i + 1]);
 		}
-		
+
 		atlasAsset.materials = new Material[pageFiles.Count];
-		
+
 		for (int i = 0; i < pageFiles.Count; i++) {
 			string texturePath = assetPath + "/" + pageFiles[i];
 			Texture2D texture = (Texture2D)AssetDatabase.LoadAssetAtPath(texturePath, typeof(Texture2D));
-			
+
 			TextureImporter texImporter = (TextureImporter)TextureImporter.GetAtPath(texturePath);
 			texImporter.textureFormat = TextureImporterFormat.AutomaticTruecolor;
 			texImporter.mipmapEnabled = false;
@@ -396,13 +600,13 @@ public class SpineEditorUtilities : AssetPostprocessor {
 			EditorUtility.SetDirty(texImporter);
 			AssetDatabase.ImportAsset(texturePath);
 			AssetDatabase.SaveAssets();
-			
+
 			string pageName = Path.GetFileNameWithoutExtension(pageFiles[i]);
-			
+
 			//because this looks silly
 			if (pageName == primaryName && pageFiles.Count == 1)
 				pageName = "Material";
-			
+
 			string materialPath = assetPath + "/" + primaryName + "_" + pageName + ".mat";
 			Material mat = (Material)AssetDatabase.LoadAssetAtPath(materialPath, typeof(Material));
 
@@ -410,12 +614,12 @@ public class SpineEditorUtilities : AssetPostprocessor {
 				mat = new Material(Shader.Find(defaultShader));
 				AssetDatabase.CreateAsset(mat, materialPath);
 			}
-			
+
 			mat.mainTexture = texture;
 			EditorUtility.SetDirty(mat);
 
 			AssetDatabase.SaveAssets();
-			
+
 			atlasAsset.materials[i] = mat;
 		}
 
@@ -429,40 +633,41 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		return (AtlasAsset)AssetDatabase.LoadAssetAtPath(atlasPath, typeof(AtlasAsset));
 	}
 
-	static SkeletonDataAsset IngestSpineProject (TextAsset spineJson, AtlasAsset atlasAsset = null) {
+	static SkeletonDataAsset IngestSpineProject(TextAsset spineJson, params AtlasAsset[] atlasAssets) {
 		string primaryName = Path.GetFileNameWithoutExtension(spineJson.name);
 		string assetPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(spineJson));
 		string filePath = assetPath + "/" + primaryName + "_SkeletonData.asset";
 
-		if (spineJson != null && atlasAsset != null) {
+		if (spineJson != null && atlasAssets != null) {
 
 			SkeletonDataAsset skelDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset));
 			if (skelDataAsset == null) {
 				skelDataAsset = SkeletonDataAsset.CreateInstance<SkeletonDataAsset>();
-				skelDataAsset.atlasAsset = atlasAsset;
+				skelDataAsset.atlasAssets = atlasAssets;
 				skelDataAsset.skeletonJSON = spineJson;
 				skelDataAsset.fromAnimation = new string[0];
 				skelDataAsset.toAnimation = new string[0];
 				skelDataAsset.duration = new float[0];
 				skelDataAsset.defaultMix = defaultMix;
 				skelDataAsset.scale = defaultScale;
-				
+
 				AssetDatabase.CreateAsset(skelDataAsset, filePath);
 				AssetDatabase.SaveAssets();
 			} else {
+				skelDataAsset.atlasAssets = atlasAssets;
 				skelDataAsset.Reset();
 				skelDataAsset.GetSkeletonData(true);
 			}
 
 			return skelDataAsset;
 		} else {
-			EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and Atlas TextAsset", "OK");
+			EditorUtility.DisplayDialog("Error!", "Must specify both Spine JSON and AtlasAsset array", "OK");
 			return null;
 		}
 	}
 
 	[MenuItem("Assets/Spine/Spawn")]
-	static void SpawnAnimatedSkeleton () {
+	static void SpawnAnimatedSkeleton() {
 		Object[] arr = Selection.objects;
 		foreach (Object o in arr) {
 			string guid = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o));
@@ -474,59 +679,66 @@ public class SpineEditorUtilities : AssetPostprocessor {
 	}
 
 	[MenuItem("Assets/Spine/Spawn", true)]
-	static bool ValidateSpawnAnimatedSkeleton () {
+	static bool ValidateSpawnAnimatedSkeleton() {
 		Object[] arr = Selection.objects;
-		
+
 		if (arr.Length == 0)
 			return false;
-		
+
 		foreach (Object o in arr) {
 			if (o.GetType() != typeof(SkeletonDataAsset))
 				return false;
 		}
-		
+
 		return true;
 	}
 
-	public static SkeletonAnimation SpawnAnimatedSkeleton (SkeletonDataAsset skeletonDataAsset, string skinName) {
+	public static SkeletonAnimation SpawnAnimatedSkeleton(SkeletonDataAsset skeletonDataAsset, string skinName) {
 		return SpawnAnimatedSkeleton(skeletonDataAsset, skeletonDataAsset.GetSkeletonData(true).FindSkin(skinName));
 	}
 
-	public static SkeletonAnimation SpawnAnimatedSkeleton (SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
+	public static SkeletonAnimation SpawnAnimatedSkeleton(SkeletonDataAsset skeletonDataAsset, Skin skin = null) {
 		GameObject go = new GameObject(skeletonDataAsset.name.Replace("_SkeletonData", ""), typeof(MeshFilter), typeof(MeshRenderer), typeof(SkeletonAnimation));
 		SkeletonAnimation anim = go.GetComponent<SkeletonAnimation>();
 		anim.skeletonDataAsset = skeletonDataAsset;
 
 		bool requiresNormals = false;
 
-		foreach (Material m in anim.skeletonDataAsset.atlasAsset.materials) {
-			if (m.shader.name.Contains("Lit")) {
-				requiresNormals = true;
-				break;
+		foreach (AtlasAsset atlasAsset in anim.skeletonDataAsset.atlasAssets) {
+			foreach (Material m in atlasAsset.materials) {
+				if (m.shader.name.Contains("Lit")) {
+					requiresNormals = true;
+					break;
+				}
 			}
 		}
 
+		
+
 		anim.calculateNormals = requiresNormals;
 
 		SkeletonData data = skeletonDataAsset.GetSkeletonData(true);
 
 		if (data == null) {
-			string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAsset);
-			skeletonDataAsset.atlasAsset = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
+			for(int i = 0; i < skeletonDataAsset.atlasAssets.Length; i++){
+				string reloadAtlasPath = AssetDatabase.GetAssetPath(skeletonDataAsset.atlasAssets[i]);
+				skeletonDataAsset.atlasAssets[i] = (AtlasAsset)AssetDatabase.LoadAssetAtPath(reloadAtlasPath, typeof(AtlasAsset));
+			}			
+			
 			data = skeletonDataAsset.GetSkeletonData(true);
 		}
 
 		if (skin == null)
 			skin = data.DefaultSkin;
-			
+
 		if (skin == null)
 			skin = data.Skins[0];
 
 		anim.Reset();
-		
+
 		anim.skeleton.SetSkin(skin);
 		anim.initialSkinName = skin.Name;
-		
+
 		anim.skeleton.Update(1);
 		anim.state.Update(1);
 		anim.state.Apply(anim.skeleton);
@@ -534,4 +746,4 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 		return anim;
 	}
-}
+}

+ 26 - 8
spine-unity/Assets/spine-unity/SkeletonDataAsset.cs

@@ -35,7 +35,7 @@ using UnityEngine;
 using Spine;
 
 public class SkeletonDataAsset : ScriptableObject {
-	public AtlasAsset atlasAsset;
+	public AtlasAsset[] atlasAssets;
 	public TextAsset skeletonJSON;
 	public float scale = 1;
 	public String[] fromAnimation;
@@ -45,13 +45,14 @@ public class SkeletonDataAsset : ScriptableObject {
 	private SkeletonData skeletonData;
 	private AnimationStateData stateData;
 
-	public void Reset () {
+	public void Reset() {
 		skeletonData = null;
 		stateData = null;
 	}
 
-	public SkeletonData GetSkeletonData (bool quiet) {
-		if (atlasAsset == null) {
+	public SkeletonData GetSkeletonData(bool quiet) {
+		if (atlasAssets == null) {
+			atlasAssets = new AtlasAsset[0];
 			if (!quiet)
 				Debug.LogError("Atlas not set for SkeletonData asset: " + name, this);
 			Reset();
@@ -65,16 +66,33 @@ public class SkeletonDataAsset : ScriptableObject {
 			return null;
 		}
 
-		Atlas atlas = atlasAsset.GetAtlas();
-		if (atlas == null) {
+
+
+		if (atlasAssets.Length == 0) {
 			Reset();
 			return null;
 		}
 
+		Atlas[] atlasArr = new Atlas[atlasAssets.Length];
+		for (int i = 0; i < atlasAssets.Length; i++) {
+			if (atlasAssets[i] == null) {
+				Reset();
+				return null;
+			}
+			atlasArr[i] = atlasAssets[i].GetAtlas();
+			if (atlasArr[i] == null) {
+				Reset();
+				return null;
+			}
+		}
+
+
+
+
 		if (skeletonData != null)
 			return skeletonData;
 
-		SkeletonJson json = new SkeletonJson(atlas);
+		SkeletonJson json = new SkeletonJson(atlasArr);
 		json.Scale = scale;
 		try {
 			skeletonData = json.ReadSkeletonData(new StringReader(skeletonJSON.text));
@@ -95,7 +113,7 @@ public class SkeletonDataAsset : ScriptableObject {
 		return skeletonData;
 	}
 
-	public AnimationStateData GetAnimationStateData () {
+	public AnimationStateData GetAnimationStateData() {
 		if (stateData != null)
 			return stateData;
 		GetSkeletonData(false);

+ 12 - 12
spine-unity/Assets/spine-unity/SkeletonExtensions.cs

@@ -38,71 +38,71 @@ using System.Collections;
 using Spine;
 
 public static class SkeletonExtensions {
-	
-	public static void SetColor (this Slot slot, Color color) {
+
+	public static void SetColor(this Slot slot, Color color) {
 		slot.A = color.a;
 		slot.R = color.r;
 		slot.G = color.g;
 		slot.B = color.b;
 	}
 
-	public static void SetColor (this Slot slot, Color32 color) {
+	public static void SetColor(this Slot slot, Color32 color) {
 		slot.A = color.a / 255f;
 		slot.R = color.r / 255f;
 		slot.G = color.g / 255f;
 		slot.B = color.b / 255f;
 	}
 
-	public static void SetColor (this RegionAttachment attachment, Color color) {
+	public static void SetColor(this RegionAttachment attachment, Color color) {
 		attachment.A = color.a;
 		attachment.R = color.r;
 		attachment.G = color.g;
 		attachment.B = color.b;
 	}
 
-	public static void SetColor (this RegionAttachment attachment, Color32 color) {
+	public static void SetColor(this RegionAttachment attachment, Color32 color) {
 		attachment.A = color.a / 255f;
 		attachment.R = color.r / 255f;
 		attachment.G = color.g / 255f;
 		attachment.B = color.b / 255f;
 	}
 
-	public static void SetColor (this MeshAttachment attachment, Color color) {
+	public static void SetColor(this MeshAttachment attachment, Color color) {
 		attachment.A = color.a;
 		attachment.R = color.r;
 		attachment.G = color.g;
 		attachment.B = color.b;
 	}
 
-	public static void SetColor (this MeshAttachment attachment, Color32 color) {
+	public static void SetColor(this MeshAttachment attachment, Color32 color) {
 		attachment.A = color.a / 255f;
 		attachment.R = color.r / 255f;
 		attachment.G = color.g / 255f;
 		attachment.B = color.b / 255f;
 	}
 
-	public static void SetColor (this SkinnedMeshAttachment attachment, Color color) {
+	public static void SetColor(this SkinnedMeshAttachment attachment, Color color) {
 		attachment.A = color.a;
 		attachment.R = color.r;
 		attachment.G = color.g;
 		attachment.B = color.b;
 	}
 
-	public static void SetColor (this SkinnedMeshAttachment attachment, Color32 color) {
+	public static void SetColor(this SkinnedMeshAttachment attachment, Color32 color) {
 		attachment.A = color.a / 255f;
 		attachment.R = color.r / 255f;
 		attachment.G = color.g / 255f;
 		attachment.B = color.b / 255f;
 	}
 
-	public static void SetPosition (this Bone bone, Vector2 position) {
+	public static void SetPosition(this Bone bone, Vector2 position) {
 		bone.X = position.x;
 		bone.Y = position.y;
 	}
 
-	public static void SetPosition (this Bone bone, Vector3 position) {
+	public static void SetPosition(this Bone bone, Vector3 position) {
 		bone.X = position.x;
 		bone.Y = position.y;
 	}
 
-}
+}

+ 17 - 1
spine-unity/Assets/spine-unity/SkeletonRenderer.cs

@@ -51,6 +51,14 @@ public class SkeletonRenderer : MonoBehaviour {
 	public float zSpacing;
 	public bool renderMeshes = true, immutableTriangles;
 	public bool logErrors = false;
+
+	[SpineSlot]
+	public string[] submeshSeparators = new string[0];
+
+	[HideInInspector]
+	public List<Slot> submeshSeparatorSlots = new List<Slot>();
+
+
 	private MeshFilter meshFilter;
 	private Mesh mesh1, mesh2;
 	private bool useMesh1;
@@ -62,6 +70,7 @@ public class SkeletonRenderer : MonoBehaviour {
 	private Material[] sharedMaterials = new Material[0];
 	private readonly List<Material> submeshMaterials = new List<Material>();
 	private readonly List<Submesh> submeshes = new List<Submesh>();
+	
 
 	public virtual void Reset () {
 		if (meshFilter != null)
@@ -99,6 +108,12 @@ public class SkeletonRenderer : MonoBehaviour {
 		skeleton = new Skeleton(skeletonData);
 		if (initialSkinName != null && initialSkinName.Length > 0 && initialSkinName != "default")
 			skeleton.SetSkin(initialSkinName);
+
+		submeshSeparatorSlots.Clear();
+		for (int i = 0; i < submeshSeparators.Length; i++) {
+			submeshSeparatorSlots.Add(skeleton.FindSlot(submeshSeparators[i]));
+		}
+
 		if (OnReset != null)
 			OnReset(this);
 	}
@@ -156,7 +171,8 @@ public class SkeletonRenderer : MonoBehaviour {
 
 			// Populate submesh when material changes.
 			Material material = (Material)((AtlasRegion)rendererObject).page.rendererObject;
-			if ((lastMaterial != material && lastMaterial != null) || slot.Data.name[0] == '*') {
+
+			if ((lastMaterial != material && lastMaterial != null) || submeshSeparatorSlots.Contains(slot)) {
 				AddSubmesh(lastMaterial, submeshStartSlotIndex, i, submeshTriangleCount, submeshFirstVertex, false);
 				submeshTriangleCount = 0;
 				submeshFirstVertex = vertexCount;

+ 5 - 1
spine-unity/Assets/spine-unity/SkeletonUtility/SkeletonUtilityKinematicShadow.cs

@@ -4,6 +4,7 @@ using System.Collections.Generic;
 
 public class SkeletonUtilityKinematicShadow : MonoBehaviour {
 	public bool hideShadow = true;
+	public Transform parent;
 	Dictionary<Transform, Transform> shadowTable;
 	GameObject shadowRoot;
 
@@ -12,7 +13,10 @@ public class SkeletonUtilityKinematicShadow : MonoBehaviour {
 		if (hideShadow)
 			shadowRoot.hideFlags = HideFlags.HideInHierarchy;
 
-		shadowRoot.transform.parent = transform.root;
+		if(parent == null)
+			shadowRoot.transform.parent = transform.root;
+		else
+			shadowRoot.transform.parent = parent;
 
 		shadowTable = new Dictionary<Transform, Transform>();
 

+ 198 - 0
spine-unity/Assets/spine-unity/SpineAttributes.cs

@@ -0,0 +1,198 @@
+/******************************************************************************
+ * Spine Runtimes Software License
+ * Version 2.1
+ * 
+ * Copyright (c) 2013, Esoteric Software
+ * All rights reserved.
+ * 
+ * You are granted a perpetual, non-exclusive, non-sublicensable and
+ * non-transferable license to install, execute and perform the Spine Runtimes
+ * Software (the "Software") solely for internal use. Without the written
+ * permission of Esoteric Software (typically granted by licensing Spine), you
+ * may not (a) modify, translate, adapt or otherwise create derivative works,
+ * improvements of the Software or develop new applications using the Software
+ * or (b) remove, delete, alter or obscure any trademarks or any copyright,
+ * trademark, patent or other intellectual property or proprietary rights
+ * notices on or in the Software, including any copy thereof. Redistributions
+ * in binary or source form must include this license and terms.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE "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 SOFTARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) 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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Spine Attributes created by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+using UnityEngine;
+using System.Collections;
+
+public class SpineSlot : PropertyAttribute {
+	public string startsWith = "";
+	public string dataField = "";
+
+	/// <summary>
+	/// Smart popup menu for Spine Slots
+	/// </summary>
+	/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
+	/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
+	/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives).
+	/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
+	/// </param>
+	public SpineSlot(string startsWith = "", string dataField = "") {
+		this.startsWith = startsWith;
+		this.dataField = dataField;
+	}
+}
+
+public class SpineSkin : PropertyAttribute {
+	public string startsWith = "";
+	public string dataField = "";
+
+	/// <summary>
+	/// Smart popup menu for Spine Skins
+	/// </summary>
+	/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
+	/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
+	/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
+	/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
+	/// </param>
+	public SpineSkin(string startsWith = "", string dataField = "") {
+		this.startsWith = startsWith;
+		this.dataField = dataField;
+	}
+}
+public class SpineAnimation : PropertyAttribute {
+	public string startsWith = "";
+	public string dataField = "";
+
+	/// <summary>
+	/// Smart popup menu for Spine Animations
+	/// </summary>
+	/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
+	/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
+	/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
+	/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
+	/// </param>
+	public SpineAnimation(string startsWith = "", string dataField = "") {
+		this.startsWith = startsWith;
+		this.dataField = dataField;
+	}
+}
+
+public class SpineAttachment : PropertyAttribute {
+	public bool returnAttachmentPath = false;
+	public bool currentSkinOnly = false;
+	public bool placeholdersOnly = false;
+	public string dataField = "";
+	public string slotField = "";
+
+
+	public SpineAttachment() {
+
+	}
+
+	/// <summary>
+	/// Smart popup menu for Spine Attachments
+	/// </summary>
+	/// <param name="currentSkinOnly">Filters popup results to only include the current Skin.  Only valid when a SkeletonRenderer is the data source.</param>
+	/// <param name="returnAttachmentPath">Returns a fully qualified path for an Attachment in the format "Skin/Slot/AttachmentName"</param>
+	/// <param name="placeholdersOnly">Filters popup results to exclude attachments that are not children of Skin Placeholders</param>
+	/// <param name="slotField">If specified, a locally scoped field with the name supplied by in slotField will be used to limit the popup results to children of a named slot</param>
+	/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
+	/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
+	/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
+	/// </param>
+	public SpineAttachment(bool currentSkinOnly = true, bool returnAttachmentPath = false, bool placeholdersOnly = false, string slotField = "", string dataField = "") {
+		this.currentSkinOnly = currentSkinOnly;
+		this.returnAttachmentPath = returnAttachmentPath;
+		this.placeholdersOnly = placeholdersOnly;
+		this.slotField = slotField;
+		this.dataField = dataField;		
+	}
+
+	public static Hierarchy GetHierarchy(string fullPath) {
+		return new Hierarchy(fullPath);
+	}
+
+	public static Spine.Attachment GetAttachment(string attachmentPath, Spine.SkeletonData skeletonData) {
+		var hierarchy = SpineAttachment.GetHierarchy(attachmentPath);
+		if (hierarchy.name == "")
+			return null;
+
+		return skeletonData.FindSkin(hierarchy.skin).GetAttachment(skeletonData.FindSlotIndex(hierarchy.slot), hierarchy.name);
+	}
+
+	public static Spine.Attachment GetAttachment(string attachmentPath, SkeletonDataAsset skeletonDataAsset) {
+		return GetAttachment(attachmentPath, skeletonDataAsset.GetSkeletonData(true));
+	}
+
+	public struct Hierarchy {
+		public string skin;
+		public string slot;
+		public string name;
+
+		public Hierarchy(string fullPath) {
+			string[] chunks = fullPath.Split(new char[]{'/'}, System.StringSplitOptions.RemoveEmptyEntries);
+			if (chunks.Length == 0) {
+				skin = "";
+				slot = "";
+				name = "";
+				return;
+			}
+			else if (chunks.Length < 2) {
+				throw new System.Exception("Cannot generate Attachment Hierarchy from string! Not enough components! [" + fullPath + "]");
+			}
+			skin = chunks[0];
+			slot = chunks[1];
+			name = "";
+			for (int i = 2; i < chunks.Length; i++) {
+				name += chunks[i];
+			}
+		}
+	}
+}
+
+public class SpineBone : PropertyAttribute {
+	public string startsWith = "";
+	public string dataField = "";
+
+	/// <summary>
+	/// Smart popup menu for Spine Bones
+	/// </summary>
+	/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
+	/// <param name="dataField">If specified, a locally scoped field with the name supplied by in dataField will be used to fill the popup results.
+	/// Valid types are SkeletonDataAsset and SkeletonRenderer (and derivatives)
+	/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
+	/// </param>
+	public SpineBone(string startsWith = "", string dataField = "") {
+		this.startsWith = startsWith;
+		this.dataField = dataField;
+	}
+
+	public static Spine.Bone GetBone(string boneName, SkeletonRenderer renderer) {
+		if (renderer.skeleton == null)
+			return null;
+
+		return renderer.skeleton.FindBone(boneName);
+	}
+
+	public static Spine.BoneData GetBoneData(string boneName, SkeletonDataAsset skeletonDataAsset) {
+		var data = skeletonDataAsset.GetSkeletonData(true);
+
+		return data.FindBone(boneName);
+	}
+}
+
+public class SpineAtlasRegion : PropertyAttribute {
+	//TODO:  Standardize with Skeleton attributes
+	//NOTE:  For now, relies on locally scoped field named "atlasAsset" for source.
+}

+ 8 - 0
spine-unity/Assets/spine-unity/SpineAttributes.cs.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ce216f51ebc1d3f40929f4e58d1c65e5
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: