Pārlūkot izejas kodu

more attributes
more bug fixes
more helper menus
even more examples

Fenrisul 10 gadi atpakaļ
vecāks
revīzija
d60bc9241c

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


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


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


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

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

+ 36 - 2
spine-unity/Assets/Examples/Scripts/Chimera.cs

@@ -1,11 +1,45 @@
-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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * 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, returnFullPath: true, dataSource: "skeletonDataSource")]
+	[SpineAttachment(currentSkinOnly: false, returnAttachmentPath: true, dataField: "skeletonDataSource")]
 	public string attachmentPath;
 
 	[SpineSlot]

+ 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: 

+ 37 - 3
spine-unity/Assets/Examples/Scripts/FootSoldierExample.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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * FootSoldierExample created by Mitch Thompson
+ * Full irrevocable rights and permissions granted to Esoteric Software
+*****************************************************************************/
+using UnityEngine;
 using System.Collections;
 
 public class FootSoldierExample : MonoBehaviour {
@@ -11,10 +45,10 @@ public class FootSoldierExample : MonoBehaviour {
 	[SpineSlot]
 	public string eyesSlot;
 
-	[SpineAttachment(currentSkinOnly: true, slot: "eyesSlot")]
+	[SpineAttachment(currentSkinOnly: true, slotField: "eyesSlot")]
 	public string eyesOpenAttachment;
 
-	[SpineAttachment(currentSkinOnly: true, slot: "eyesSlot")]
+	[SpineAttachment(currentSkinOnly: true, slotField: "eyesSlot")]
 	public string blinkAttachment;
 
 	[Range(0, 0.2f)]

+ 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/Spineboy/spineboy_Atlas.asset


+ 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: 

+ 18 - 0
spine-unity/Assets/spine-unity/Editor/AtlasAssetInspector.cs

@@ -50,9 +50,27 @@ 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();

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

@@ -21,7 +21,7 @@ TextureImporter:
   grayScaleToAlpha: 0
   generateCubemap: 0
   seamlessCubemap: 0
-  textureFormat: -1
+  textureFormat: -3
   maxTextureSize: 1024
   textureSettings:
     filterMode: -1

+ 31 - 5
spine-unity/Assets/spine-unity/Editor/SkeletonDataAssetInspector.cs

@@ -102,12 +102,18 @@ public class SkeletonDataAssetInspector : Editor {
 		EditorGUILayout.PropertyField(skeletonJSON);
 		EditorGUILayout.PropertyField(scale);
 		if (EditorGUI.EndChangeCheck()) {
-			if (m_previewUtility != null) {
-				m_previewUtility.Cleanup();
-				m_previewUtility = null;
-			}
+			if (serializedObject.ApplyModifiedProperties()) {
 
-			RepopulateWarnings();
+				if (m_previewUtility != null) {
+					m_previewUtility.Cleanup();
+					m_previewUtility = null;
+				}
+
+				RepopulateWarnings();
+				OnEnable();
+				return;
+			}
+			
 		}
 
 		
@@ -118,6 +124,8 @@ public class SkeletonDataAssetInspector : Editor {
 			DrawSlotList();
 			
 		} else {
+
+			DrawReimportButton();
 			//Show Warnings
 			foreach (var str in warnings)
 				EditorGUILayout.LabelField(new GUIContent(str, SpineEditorUtilities.Icons.warning));
@@ -132,6 +140,24 @@ public class SkeletonDataAssetInspector : Editor {
 		}
 	}
 
+	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)

+ 160 - 31
spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * 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;
@@ -32,13 +66,13 @@ public class SpineSlotDrawer : PropertyDrawer {
 
 		SpineSlot attrib = (SpineSlot)attribute;
 
-		var skeletonDataAssetProperty = property.serializedObject.FindProperty(attrib.dataSource);
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
 
-		if (skeletonDataAssetProperty != null) {
-			if (skeletonDataAssetProperty.objectReferenceValue is SkeletonDataAsset) {
-				skeletonDataAsset = (SkeletonDataAsset)skeletonDataAssetProperty.objectReferenceValue;
-			} else if (skeletonDataAssetProperty.objectReferenceValue is SkeletonRenderer) {
-				var renderer = (SkeletonRenderer)skeletonDataAssetProperty.objectReferenceValue;
+		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 {
@@ -102,7 +136,6 @@ public class SpineSlotDrawer : PropertyDrawer {
 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");
@@ -111,13 +144,13 @@ public class SpineSkinDrawer : PropertyDrawer {
 
 		SpineSkin attrib = (SpineSkin)attribute;
 
-		var skeletonDataAssetProperty = property.serializedObject.FindProperty(attrib.dataSource);
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
 
-		if (skeletonDataAssetProperty != null) {
-			if (skeletonDataAssetProperty.objectReferenceValue is SkeletonDataAsset) {
-				skeletonDataAsset = (SkeletonDataAsset)skeletonDataAssetProperty.objectReferenceValue;
-			} else if (skeletonDataAssetProperty.objectReferenceValue is SkeletonRenderer) {
-				var renderer = (SkeletonRenderer)skeletonDataAssetProperty.objectReferenceValue;
+		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 {
@@ -256,13 +289,13 @@ public class SpineAnimationDrawer : PropertyDrawer {
 
 		SpineAnimation attrib = (SpineAnimation)attribute;
 
-		var skeletonDataAssetProperty = property.serializedObject.FindProperty(attrib.dataSource);
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
 
-		if (skeletonDataAssetProperty != null) {
-			if (skeletonDataAssetProperty.objectReferenceValue is SkeletonDataAsset) {
-				skeletonDataAsset = (SkeletonDataAsset)skeletonDataAssetProperty.objectReferenceValue;
-			} else if (skeletonDataAssetProperty.objectReferenceValue is SkeletonRenderer) {
-				var renderer = (SkeletonRenderer)skeletonDataAssetProperty.objectReferenceValue;
+		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 {
@@ -332,13 +365,13 @@ public class SpineAttachmentDrawer : PropertyDrawer {
 
 		SpineAttachment attrib = (SpineAttachment)attribute;
 
-		var skeletonDataAssetProperty = property.serializedObject.FindProperty(attrib.dataSource);
+		var dataProperty = property.serializedObject.FindProperty(attrib.dataField);
 
-		if (skeletonDataAssetProperty != null) {
-			if (skeletonDataAssetProperty.objectReferenceValue is SkeletonDataAsset) {
-				skeletonDataAsset = (SkeletonDataAsset)skeletonDataAssetProperty.objectReferenceValue;
-			} else if (skeletonDataAssetProperty.objectReferenceValue is SkeletonRenderer) {
-				var renderer = (SkeletonRenderer)skeletonDataAssetProperty.objectReferenceValue;
+		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 {
@@ -381,8 +414,11 @@ public class SpineAttachmentDrawer : PropertyDrawer {
 		List<Skin> validSkins = new List<Skin>();
 
 		if (skeletonRenderer != null && attrib.currentSkinOnly) {
-			if (skeletonRenderer.skeleton.Skin != null)
+			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)
@@ -392,6 +428,8 @@ public class SpineAttachmentDrawer : PropertyDrawer {
 
 		GenericMenu menu = new GenericMenu();
 		List<string> attachmentNames = new List<string>();
+		List<string> placeholderNames = new List<string>();
+
 		string prefix = "";
 
 		if (skeletonRenderer != null && attrib.currentSkinOnly)
@@ -405,7 +443,7 @@ public class SpineAttachmentDrawer : PropertyDrawer {
 
 		Skin defaultSkin = data.Skins[0];
 
-		SerializedProperty slotProperty = property.serializedObject.FindProperty(attrib.slotSource);
+		SerializedProperty slotProperty = property.serializedObject.FindProperty(attrib.slotField);
 		string slotMatch = "";
 		if (slotProperty != null) {
 			if (slotProperty.propertyType == SerializedPropertyType.String) {
@@ -424,18 +462,31 @@ public class SpineAttachmentDrawer : PropertyDrawer {
 					continue;
 
 				attachmentNames.Clear();
+				placeholderNames.Clear();
+
 				skin.FindNamesForSlot(i, attachmentNames);
-				if (skin != defaultSkin)
+				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.returnFullPath)
+					if (attrib.returnAttachmentPath)
 						name = skin.Name + "/" + data.Slots[i].Name + "/" + attachmentPath;
-					menu.AddItem(new GUIContent(menuPath), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+
+					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));
+					}
+					
+					
 				}
 			}
 		}
@@ -454,3 +505,81 @@ public class SpineAttachmentDrawer : PropertyDrawer {
 		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;
+	}
+}

+ 55 - 1
spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs

@@ -209,8 +209,10 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 	}
 
-
 	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>();
@@ -242,6 +244,9 @@ public class SpineEditorUtilities : AssetPostprocessor {
 
 		//import atlases first
 		foreach (string ap in atlasPaths) {
+			if (!reimport && CheckForValidAtlas(ap))
+				continue;
+
 			TextAsset atlasText = (TextAsset)AssetDatabase.LoadAssetAtPath(ap, typeof(TextAsset));
 			AtlasAsset atlas = IngestSpineAtlas(atlasText);
 			atlases.Add(atlas);
@@ -250,7 +255,14 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		//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);
@@ -307,6 +319,48 @@ public class SpineEditorUtilities : AssetPostprocessor {
 		//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;
+			}
+		}
+
+		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;
+			}
+		}
+
+		return false;
+	}
+
 	static List<AtlasAsset> MultiAtlasDialog(List<string> requiredPaths, string initialDirectory, string header = "") {
 
 		List<AtlasAsset> atlasAssets = new List<AtlasAsset>();

+ 123 - 36
spine-unity/Assets/spine-unity/SpineAttributes.cs

@@ -1,87 +1,138 @@
-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.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * 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 dataSource = "";
+	public string dataField = "";
 
 	/// <summary>
-	/// 
+	/// Smart popup menu for Spine Slots
 	/// </summary>
-	/// <param name="startsWith"></param>
-	/// <param name="dataSource">SerializedProperty name containing a reference to either a SkeletonRenderer or a SkeletonDataAsset</param>
-	public SpineSlot(string startsWith = "", string dataSource = "") {
+	/// <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.dataSource = dataSource;
+		this.dataField = dataField;
 	}
 }
 
 public class SpineSkin : PropertyAttribute {
 	public string startsWith = "";
-	public string dataSource = "";
+	public string dataField = "";
 
 	/// <summary>
-	/// 
+	/// Smart popup menu for Spine Skins
 	/// </summary>
-	/// <param name="startsWith"></param>
-	/// <param name="dataSource">SerializedProperty name containing a reference to either a SkeletonRenderer or a SkeletonDataAsset</param>
-	public SpineSkin(string startsWith = "", string dataSource = "") {
+	/// <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.dataSource = dataSource;
+		this.dataField = dataField;
 	}
 }
-
-public class SpineAtlasRegion : PropertyAttribute {
-
-}
-
 public class SpineAnimation : PropertyAttribute {
 	public string startsWith = "";
-	public string dataSource = "";
+	public string dataField = "";
 
 	/// <summary>
-	/// 
+	/// Smart popup menu for Spine Animations
 	/// </summary>
-	/// <param name="startsWith"></param>
-	/// <param name="dataSource">SerializedProperty name containing a reference to either a SkeletonRenderer or a SkeletonDataAsset</param>
-	public SpineAnimation(string startsWith = "", string dataSource = "") {
+	/// <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.dataSource = dataSource;
+		this.dataField = dataField;
 	}
 }
 
 public class SpineAttachment : PropertyAttribute {
-	public bool returnFullPath;
-	public bool currentSkinOnly;
-	public string dataSource = "";
-	public string slotSource = "";
+	public bool returnAttachmentPath = false;
+	public bool currentSkinOnly = false;
+	public bool placeholdersOnly = false;
+	public string dataField = "";
+	public string slotField = "";
 
 
 	public SpineAttachment() {
 
 	}
 
-	public SpineAttachment(bool currentSkinOnly = true, bool returnFullPath = false, string slot = "", string dataSource = "") {
+	/// <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.returnFullPath = returnFullPath;
-		this.slotSource = slot;
-		this.dataSource = dataSource;		
+		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 fullPath, Spine.SkeletonData skeletonData) {
-		var hierarchy = SpineAttachment.GetHierarchy(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 fullPath, SkeletonDataAsset skeletonDataAsset) {
-		return GetAttachment(fullPath, skeletonDataAsset.GetSkeletonData(true));
+	public static Spine.Attachment GetAttachment(string attachmentPath, SkeletonDataAsset skeletonDataAsset) {
+		return GetAttachment(attachmentPath, skeletonDataAsset.GetSkeletonData(true));
 	}
 
 	public struct Hierarchy {
@@ -108,4 +159,40 @@ public class SpineAttachment : PropertyAttribute {
 			}
 		}
 	}
+}
+
+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.
 }