Pārlūkot izejas kodu

Merge branch 'master' into dev

badlogic 9 gadi atpakaļ
vecāks
revīzija
a5e05c3e06

+ 2 - 1
spine-c/src/spine/Skeleton.c

@@ -188,7 +188,8 @@ static void _sortPathConstraintAttachmentBones(_spSkeleton* const internal, spAt
 		int i = 0;
 		int i = 0;
 		while (i < pathBonesCount) {
 		while (i < pathBonesCount) {
 			int boneCount = pathBones[i++];
 			int boneCount = pathBones[i++];
-			for (int n = i + boneCount; i < n; i++)
+			int n;
+			for (n = i + boneCount; i < n; i++)
 				_sortBone(internal, bones[pathBones[i]]);
 				_sortBone(internal, bones[pathBones[i]]);
 		}
 		}
 	}
 	}

+ 1 - 0
spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java

@@ -447,6 +447,7 @@ public class SkeletonViewer extends ApplicationAdapter {
 				table.add(new Label("", skin, "default", Color.LIGHT_GRAY)); // Version.
 				table.add(new Label("", skin, "default", Color.LIGHT_GRAY)); // Version.
 			}
 			}
 
 
+			// Events.
 			window.addListener(new InputListener() {
 			window.addListener(new InputListener() {
 				public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
 				public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
 					event.cancel();
 					event.cancel();

+ 1 - 1
spine-lua/SkeletonJson.lua

@@ -563,7 +563,7 @@ function SkeletonJson.new (attachmentLoader)
 				end
 				end
 				local frameIndex = 0
 				local frameIndex = 0
 				for i,valueMap in ipairs(values) do
 				for i,valueMap in ipairs(values) do
-					timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1) getValue(valueMap, "shearMix", 1))
+					timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1), getValue(valueMap, "shearMix", 1))
 					readCurve(valueMap, timeline, frameIndex)
 					readCurve(valueMap, timeline, frameIndex)
 					frameIndex = frameIndex + 1
 					frameIndex = frameIndex + 1
 				end
 				end

+ 3 - 3
spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs

@@ -38,14 +38,14 @@ namespace Spine.Unity {
 	public class AtlasAsset : ScriptableObject {
 	public class AtlasAsset : ScriptableObject {
 		public TextAsset atlasFile;
 		public TextAsset atlasFile;
 		public Material[] materials;
 		public Material[] materials;
-		private Atlas atlas;
+		protected Atlas atlas;
 
 
-		public void Reset () {
+		public virtual void Reset () {
 			atlas = null;
 			atlas = null;
 		}
 		}
 
 
 		/// <returns>The atlas or null if it could not be loaded.</returns>
 		/// <returns>The atlas or null if it could not be loaded.</returns>
-		public Atlas GetAtlas () {
+		public virtual Atlas GetAtlas () {
 			if (atlasFile == null) {
 			if (atlasFile == null) {
 				Debug.LogError("Atlas file not set for atlas asset: " + name, this);
 				Debug.LogError("Atlas file not set for atlas asset: " + name, this);
 				Reset();
 				Reset();

+ 65 - 34
spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs

@@ -35,9 +35,11 @@ using Spine;
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
 	
 	
 	[CustomEditor(typeof(SkeletonAnimation))]
 	[CustomEditor(typeof(SkeletonAnimation))]
+	[CanEditMultipleObjects]
 	public class SkeletonAnimationInspector : SkeletonRendererInspector {
 	public class SkeletonAnimationInspector : SkeletonRendererInspector {
 		protected SerializedProperty animationName, loop, timeScale, autoReset;
 		protected SerializedProperty animationName, loop, timeScale, autoReset;
 		protected bool wasAnimationNameChanged;
 		protected bool wasAnimationNameChanged;
+		protected bool requireRepaint;
 
 
 		protected override void OnEnable () {
 		protected override void OnEnable () {
 			base.OnEnable();
 			base.OnEnable();
@@ -46,64 +48,93 @@ namespace Spine.Unity.Editor {
 			timeScale = serializedObject.FindProperty("timeScale");
 			timeScale = serializedObject.FindProperty("timeScale");
 		}
 		}
 
 
-		protected override void DrawInspectorGUI () {
-			base.DrawInspectorGUI();
+		protected override void DrawInspectorGUI (bool multi) {
+			base.DrawInspectorGUI(multi);
+			if (!TargetIsValid) return;
+			bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject);
 
 
-			SkeletonAnimation component = (SkeletonAnimation)target;
-			if (!component.valid)
+			// Try to reflect the animation name on the scene object.
+			{
+				if (multi)
+					foreach (var o in targets)		
+						TrySetAnimation(o);
+				else
+					TrySetAnimation(target);
+			}
+			
+			EditorGUILayout.Space();
+
+			if (multi && !sameData)
+				EditorGUILayout.DelayedTextField(animationName);
+			else {
+				EditorGUI.BeginChangeCheck();
+				EditorGUILayout.PropertyField(animationName);
+				wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update.
+			}
+				
+			EditorGUILayout.PropertyField(loop);
+
+			EditorGUILayout.PropertyField(timeScale);
+			if (multi) {
+				foreach (var o in targets) {
+					var component = o as SkeletonAnimation;
+					component.timeScale = Mathf.Max(component.timeScale, 0);
+				}
+			} else {
+				var component = (SkeletonAnimation)target;
+				component.timeScale = Mathf.Max(component.timeScale, 0);
+			}
+
+			if (!isInspectingPrefab) {
+				if (requireRepaint) {
+					SceneView.RepaintAll();
+					requireRepaint = false;
+				}
+
+				DrawSkeletonUtilityButton(multi);
+			}
+		}
+
+		protected void TrySetAnimation (Object o) {
+			var skeletonAnimation = o as SkeletonAnimation;
+			if (skeletonAnimation == null) return;
+			if (!skeletonAnimation.valid)
 				return;
 				return;
 
 
 			if (!isInspectingPrefab) {
 			if (!isInspectingPrefab) {
 				if (wasAnimationNameChanged) {
 				if (wasAnimationNameChanged) {
 					if (!Application.isPlaying) {
 					if (!Application.isPlaying) {
-						if (component.state != null) component.state.ClearTrack(0);
-						component.skeleton.SetToSetupPose();
+						if (skeletonAnimation.state != null) skeletonAnimation.state.ClearTrack(0);
+						skeletonAnimation.skeleton.SetToSetupPose();
 					}
 					}
 
 
-					Spine.Animation animationToUse = component.skeleton.Data.FindAnimation(animationName.stringValue);
+					Spine.Animation animationToUse = skeletonAnimation.skeleton.Data.FindAnimation(animationName.stringValue);
 
 
 					if (!Application.isPlaying) {
 					if (!Application.isPlaying) {
-						if (animationToUse != null) animationToUse.Apply(component.skeleton, 0f, 0f, false, null);
-						component.Update();
-						component.LateUpdate();
-						SceneView.RepaintAll();
+						if (animationToUse != null) animationToUse.Apply(skeletonAnimation.skeleton, 0f, 0f, false, null);
+						skeletonAnimation.Update();
+						skeletonAnimation.LateUpdate();
+						requireRepaint = true;
 					} else {
 					} else {
 						if (animationToUse != null)
 						if (animationToUse != null)
-							component.state.SetAnimation(0, animationToUse, loop.boolValue);
+							skeletonAnimation.state.SetAnimation(0, animationToUse, loop.boolValue);
 						else
 						else
-							component.state.ClearTrack(0);
+							skeletonAnimation.state.ClearTrack(0);
 					}
 					}
 
 
 					wasAnimationNameChanged = false;
 					wasAnimationNameChanged = false;
 				}
 				}
 
 
 				// Reflect animationName serialized property in the inspector even if SetAnimation API was used.
 				// Reflect animationName serialized property in the inspector even if SetAnimation API was used.
-				if (Application.isPlaying) {
-					TrackEntry current = component.state.GetCurrent(0);
+				bool multi = animationName.serializedObject.isEditingMultipleObjects;
+				if (!multi && Application.isPlaying) {
+					TrackEntry current = skeletonAnimation.state.GetCurrent(0);
 					if (current != null) {
 					if (current != null) {
-						if (component.AnimationName != animationName.stringValue)
+						if (skeletonAnimation.AnimationName != animationName.stringValue)
 							animationName.stringValue = current.Animation.Name;
 							animationName.stringValue = current.Animation.Name;
 					}
 					}
 				}
 				}
 			}
 			}
-				
-			EditorGUILayout.Space();
-			EditorGUI.BeginChangeCheck();
-			EditorGUILayout.PropertyField(animationName);
-			wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update.
-
-			EditorGUILayout.PropertyField(loop);
-			EditorGUILayout.PropertyField(timeScale);
-			component.timeScale = Mathf.Max(component.timeScale, 0);
-
-			EditorGUILayout.Space();
-
-			if (!isInspectingPrefab) {
-				if (component.GetComponent<SkeletonUtility>() == null) {
-					if (GUILayout.Button(new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility), GUILayout.Height(30)))
-						component.gameObject.AddComponent<SkeletonUtility>();
-				}
-			}
 		}
 		}
 	}
 	}
 }
 }

+ 6 - 14
spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs

@@ -31,10 +31,10 @@
 // Contributed by: Mitch Thompson
 // Contributed by: Mitch Thompson
 
 
 using UnityEditor;
 using UnityEditor;
-using UnityEngine;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
 	[CustomEditor(typeof(SkeletonAnimator))]
 	[CustomEditor(typeof(SkeletonAnimator))]
+	[CanEditMultipleObjects]
 	public class SkeletonAnimatorInspector : SkeletonRendererInspector {
 	public class SkeletonAnimatorInspector : SkeletonRendererInspector {
 		protected SerializedProperty layerMixModes;
 		protected SerializedProperty layerMixModes;
 		protected override void OnEnable () {
 		protected override void OnEnable () {
@@ -42,22 +42,14 @@ namespace Spine.Unity.Editor {
 			layerMixModes = serializedObject.FindProperty("layerMixModes");
 			layerMixModes = serializedObject.FindProperty("layerMixModes");
 		}
 		}
 
 
-		protected override void DrawInspectorGUI () {
-			base.DrawInspectorGUI();
+		protected override void DrawInspectorGUI (bool multi) {
+			base.DrawInspectorGUI(multi);
 			EditorGUILayout.PropertyField(layerMixModes, true);
 			EditorGUILayout.PropertyField(layerMixModes, true);
-			var component = (SkeletonAnimator)target;
-			if (!component.valid)
-				return;
 
 
-			EditorGUILayout.Space();
+			if (!TargetIsValid) return;
 
 
-			if (!isInspectingPrefab) {
-				if (component.GetComponent<SkeletonUtility>() == null) {
-					if (GUILayout.Button(new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility), GUILayout.Height(30))) {
-						component.gameObject.AddComponent<SkeletonUtility>();
-					}
-				}
-			}
+			if (!isInspectingPrefab)
+				DrawSkeletonUtilityButton(multi);
 		}
 		}
 	}
 	}
 }
 }

+ 144 - 49
spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs

@@ -31,17 +31,34 @@
 #define NO_PREFAB_MESH
 #define NO_PREFAB_MESH
 
 
 using UnityEditor;
 using UnityEditor;
+using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
 	
 	
 	[CustomEditor(typeof(SkeletonRenderer))]
 	[CustomEditor(typeof(SkeletonRenderer))]
+	[CanEditMultipleObjects]
 	public class SkeletonRendererInspector : UnityEditor.Editor {
 	public class SkeletonRendererInspector : UnityEditor.Editor {
 		protected static bool advancedFoldout;
 		protected static bool advancedFoldout;
 		protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors;
 		protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors;
 		protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
 		protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
 		protected bool isInspectingPrefab;
 		protected bool isInspectingPrefab;
-		protected MeshFilter meshFilter;
+
+		protected bool TargetIsValid {
+			get {
+				if (serializedObject.isEditingMultipleObjects) {
+					foreach (var o in targets) {
+						var component = (SkeletonRenderer)o;
+						if (!component.valid)
+							return false;
+					}
+					return true;
+				} else {
+					var component = (SkeletonRenderer)target;
+					return component.valid;
+				}
+			}
+		}
 
 
 		protected virtual void OnEnable () {
 		protected virtual void OnEnable () {
 			isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
 			isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
@@ -60,8 +77,8 @@ namespace Spine.Unity.Editor {
 			frontFacing = serializedObject.FindProperty("frontFacing");
 			frontFacing = serializedObject.FindProperty("frontFacing");
 			zSpacing = serializedObject.FindProperty("zSpacing");
 			zSpacing = serializedObject.FindProperty("zSpacing");
 
 
-			var renderer = ((SkeletonRenderer)target).GetComponent<Renderer>();
-			sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(renderer);
+			SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject);
+			sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(rso);
 		}
 		}
 
 
 		public static void ReapplySeparatorSlotNames (SkeletonRenderer skeletonRenderer) {
 		public static void ReapplySeparatorSlotNames (SkeletonRenderer skeletonRenderer) {
@@ -76,62 +93,105 @@ namespace Spine.Unity.Editor {
 				var slot = skeleton.FindSlot(separatorSlotNames[i]);
 				var slot = skeleton.FindSlot(separatorSlotNames[i]);
 				if (slot != null) {
 				if (slot != null) {
 					separatorSlots.Add(slot);
 					separatorSlots.Add(slot);
-					//Debug.Log(slot + " added as separator.");
 				} else {
 				} else {
 					Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonRenderer.skeletonDataAsset.skeletonJSON.name);				
 					Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonRenderer.skeletonDataAsset.skeletonJSON.name);				
 				}
 				}
 			}
 			}
-
-			//Debug.Log("Reapplied Separator Slot Names. Count is now: " + separatorSlots.Count);
 		}
 		}
 
 
-		protected virtual void DrawInspectorGUI () {
-			// JOHN: todo: support multiediting.
-			SkeletonRenderer component = (SkeletonRenderer)target;
-
-			using (new EditorGUILayout.HorizontalScope()) {
-				EditorGUILayout.PropertyField(skeletonDataAsset);
-				const string ReloadButtonLabel = "Reload";
-				float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20;
-				if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) {
-					if (component.skeletonDataAsset != null) {
-						foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
-							if (aa != null)
-								aa.Reset();
+		protected virtual void DrawInspectorGUI (bool multi) {
+			bool valid = TargetIsValid;
+			if (multi) {
+				using (new EditorGUILayout.HorizontalScope()) {
+					EditorGUILayout.PropertyField(skeletonDataAsset);
+					const string ReloadButtonLabel = "Reload";
+					float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20;
+					if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) {
+						foreach (var c in targets) {
+							var component = c as SkeletonRenderer;
+							if (component.skeletonDataAsset != null) {
+								foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
+									if (aa != null)
+										aa.Reset();
+								}
+								component.skeletonDataAsset.Reset();
+							}
+							component.Initialize(true);
+						}
+					}
+				}
+
+				foreach (var c in targets) {
+					var component = c as SkeletonRenderer;
+					if (!component.valid) {
+						component.Initialize(true);
+						component.LateUpdate();
+						if (!component.valid)
+							continue;
+					}
+
+					#if NO_PREFAB_MESH
+					if (isInspectingPrefab) {
+						MeshFilter meshFilter = component.GetComponent<MeshFilter>();
+						if (meshFilter != null)
+							meshFilter.sharedMesh = null;
+					}
+					#endif
+				}
+					
+				if (valid)
+					EditorGUILayout.PropertyField(initialSkinName);
+			} else {
+				var component = (SkeletonRenderer)target;
+
+				using (new EditorGUILayout.HorizontalScope()) {
+					EditorGUILayout.PropertyField(skeletonDataAsset);
+					if (valid) {
+						const string ReloadButtonLabel = "Reload";
+						float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20;
+						if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) {
+							if (component.skeletonDataAsset != null) {
+								foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) {
+									if (aa != null)
+										aa.Reset();
+								}
+								component.skeletonDataAsset.Reset();
+							}
+							component.Initialize(true);
 						}
 						}
-						component.skeletonDataAsset.Reset();
 					}
 					}
+				}
+
+				if (!component.valid) {
 					component.Initialize(true);
 					component.Initialize(true);
+					component.LateUpdate();
+					if (!component.valid) {
+						EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning);
+						return;
+					}
 				}
 				}
-			}
 
 
-			if (!component.valid) {
-				component.Initialize(true);
-				component.LateUpdate();
-				if (!component.valid)
-					return;
-			}
+				#if NO_PREFAB_MESH
+				if (isInspectingPrefab) {
+					MeshFilter meshFilter = component.GetComponent<MeshFilter>();
+					if (meshFilter != null)
+						meshFilter.sharedMesh = null;
+				}
+				#endif
 
 
-			#if NO_PREFAB_MESH
-			if (meshFilter == null)
-				meshFilter = component.GetComponent<MeshFilter>();
-
-			if (isInspectingPrefab)
-				meshFilter.sharedMesh = null;
-			#endif
-
-			// Initial skin name.
-			{
-				string[] skins = new string[component.skeleton.Data.Skins.Count];
-				int skinIndex = 0;
-				for (int i = 0; i < skins.Length; i++) {
-					string skinNameString = component.skeleton.Data.Skins.Items[i].Name;
-					skins[i] = skinNameString;
-					if (skinNameString == initialSkinName.stringValue)
-						skinIndex = i;
+				// Initial skin name.
+				if (valid) {
+					string[] skins = new string[component.skeleton.Data.Skins.Count];
+					int skinIndex = 0;
+					for (int i = 0; i < skins.Length; i++) {
+						string skinNameString = component.skeleton.Data.Skins.Items[i].Name;
+						skins[i] = skinNameString;
+						if (skinNameString == initialSkinName.stringValue)
+							skinIndex = i;
+					}
+					skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins);			
+					initialSkinName.stringValue = skins[skinIndex];
 				}
 				}
-				skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins);			
-				initialSkinName.stringValue = skins[skinIndex];
 			}
 			}
 
 
 			EditorGUILayout.Space();
 			EditorGUILayout.Space();
@@ -139,6 +199,8 @@ namespace Spine.Unity.Editor {
 			// Sorting Layers
 			// Sorting Layers
 			SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
 			SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
 
 
+			if (!valid) return;
+			
 			// More Render Options...
 			// More Render Options...
 			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
 			using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
 				EditorGUI.indentLevel++;
 				EditorGUI.indentLevel++;
@@ -183,14 +245,47 @@ namespace Spine.Unity.Editor {
 			}
 			}
 		}
 		}
 
 
+		public void DrawSkeletonUtilityButton (bool multi) {
+			var buttonContent = new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility);
+			if (multi) {
+				// Support multi-edit SkeletonUtility button.
+				//	EditorGUILayout.Space();
+				//	bool addSkeletonUtility = GUILayout.Button(buttonContent, GUILayout.Height(30));
+				//	foreach (var t in targets) {
+				//		var component = t as SkeletonAnimation;
+				//		if (addSkeletonUtility && component.GetComponent<SkeletonUtility>() == null)
+				//			component.gameObject.AddComponent<SkeletonUtility>();
+				//	}
+			} else {
+				EditorGUILayout.Space();
+				var component = (SkeletonAnimation)target;
+				if (component.GetComponent<SkeletonUtility>() == null) {						
+					if (GUILayout.Button(buttonContent, GUILayout.Height(30)))
+						component.gameObject.AddComponent<SkeletonUtility>();
+				}
+			}
+		}
+
 		override public void OnInspectorGUI () {
 		override public void OnInspectorGUI () {
 			//serializedObject.Update();
 			//serializedObject.Update();
-			DrawInspectorGUI();
+			bool multi = serializedObject.isEditingMultipleObjects;
+			DrawInspectorGUI(multi);
 			if (serializedObject.ApplyModifiedProperties() ||
 			if (serializedObject.ApplyModifiedProperties() ||
 				(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
 				(UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed")
 			) {
 			) {
-				if (!Application.isPlaying)
-					((SkeletonRenderer)target).Initialize(true);
+				if (!Application.isPlaying) {
+					if (multi) {
+						foreach (var o in targets) {
+							var sr = o as SkeletonRenderer;
+							sr.Initialize(true);
+						}
+					} else {
+						((SkeletonRenderer)target).Initialize(true);
+					}
+
+				}
+					
+					
 			}
 			}
 		}
 		}
 
 

+ 4 - 2
spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs

@@ -53,8 +53,11 @@ namespace Spine.Unity.Editor {
 		internal const string NoneLabel = "<None>";
 		internal const string NoneLabel = "<None>";
 
 
 		protected T TargetAttribute { get { return (T)attribute; } }
 		protected T TargetAttribute { get { return (T)attribute; } }
+		protected SerializedProperty SerializedProperty { get; private set; }
 
 
 		public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
 		public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) {
+			SerializedProperty = property;
+
 			if (property.propertyType != SerializedPropertyType.String) {
 			if (property.propertyType != SerializedPropertyType.String) {
 				EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
 				EditorGUI.LabelField(position, "ERROR:", "May only apply to type string");
 				return;
 				return;
@@ -87,7 +90,7 @@ namespace Spine.Unity.Editor {
 
 
 			position = EditorGUI.PrefixLabel(position, label);
 			position = EditorGUI.PrefixLabel(position, label);
 
 
-			var propertyStringValue = property.stringValue;
+			var propertyStringValue = (property.hasMultipleDifferentValues) ? SpineInspectorUtility.EmDash : property.stringValue;
 			if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel : propertyStringValue, EditorStyles.popup))
 			if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel : propertyStringValue, EditorStyles.popup))
 				Selector(property);
 				Selector(property);
 
 
@@ -302,7 +305,6 @@ namespace Spine.Unity.Editor {
 
 
 	[CustomPropertyDrawer(typeof(SpineBone))]
 	[CustomPropertyDrawer(typeof(SpineBone))]
 	public class SpineBoneDrawer : SpineTreeItemDrawerBase<SpineBone> {
 	public class SpineBoneDrawer : SpineTreeItemDrawerBase<SpineBone> {
-
 		protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineBone targetAttribute, SkeletonData data) {
 		protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineBone targetAttribute, SkeletonData data) {
 			menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
 			menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name));
 			menu.AddSeparator("");
 			menu.AddSeparator("");

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

@@ -1162,7 +1162,7 @@ namespace Spine.Unity.Editor {
 				var skeletonInfo = (Dictionary<string, object>)root["skeleton"];
 				var skeletonInfo = (Dictionary<string, object>)root["skeleton"];
 				object jv;
 				object jv;
 				skeletonInfo.TryGetValue("spine", out jv);
 				skeletonInfo.TryGetValue("spine", out jv);
-				string jsonVersion = (jv == null) ? (string)jv : null;
+				string jsonVersion = jv as string;
 				if (!string.IsNullOrEmpty(jsonVersion)) {
 				if (!string.IsNullOrEmpty(jsonVersion)) {
 					string[] jsonVersionSplit = jsonVersion.Split('.');
 					string[] jsonVersionSplit = jsonVersion.Split('.');
 					bool match = false;
 					bool match = false;

+ 57 - 5
spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs

@@ -30,6 +30,7 @@
 
 
 using UnityEngine;
 using UnityEngine;
 using UnityEditor;
 using UnityEditor;
+using System.Collections.Generic;
 using System.Reflection;
 using System.Reflection;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
@@ -43,6 +44,10 @@ namespace Spine.Unity.Editor {
 			return n == 1 ? "" : "s";
 			return n == 1 ? "" : "s";
 		}
 		}
 
 
+		public static string EmDash {
+			get { return "\u2014"; }
+		}
+
 		public static void PropertyFieldWideLabel (SerializedProperty property, GUIContent label = null, float minimumLabelWidth = 150) {
 		public static void PropertyFieldWideLabel (SerializedProperty property, GUIContent label = null, float minimumLabelWidth = 150) {
 			using (new EditorGUILayout.HorizontalScope()) {
 			using (new EditorGUILayout.HorizontalScope()) {
 				GUILayout.Label(label ?? new GUIContent(property.displayName, property.tooltip), GUILayout.MinWidth(minimumLabelWidth));
 				GUILayout.Label(label ?? new GUIContent(property.displayName, property.tooltip), GUILayout.MinWidth(minimumLabelWidth));
@@ -70,25 +75,72 @@ namespace Spine.Unity.Editor {
 			public SerializedProperty sortingLayerID;
 			public SerializedProperty sortingLayerID;
 			public SerializedProperty sortingOrder;
 			public SerializedProperty sortingOrder;
 
 
-			public SerializedSortingProperties (Renderer r) {
-				renderer = new SerializedObject(r);
+			public SerializedSortingProperties (Renderer r) : this(new SerializedObject(r)) {}
+			public SerializedSortingProperties (Object[] renderers) : this(new SerializedObject(renderers)) {}
+			public SerializedSortingProperties (SerializedObject rendererSerializedObject) {
+				renderer = rendererSerializedObject;
 				sortingLayerID = renderer.FindProperty("m_SortingLayerID");
 				sortingLayerID = renderer.FindProperty("m_SortingLayerID");
 				sortingOrder = renderer.FindProperty("m_SortingOrder");
 				sortingOrder = renderer.FindProperty("m_SortingOrder");
 			}
 			}
 
 
 			public void ApplyModifiedProperties () {
 			public void ApplyModifiedProperties () {
 				renderer.ApplyModifiedProperties();
 				renderer.ApplyModifiedProperties();
+				this.SetDirty();
+			}
+
+			internal void SetDirty () {
+				if (renderer.isEditingMultipleObjects)
+					foreach (var o in renderer.targetObjects)
+						EditorUtility.SetDirty(o);
+				else
+					EditorUtility.SetDirty(renderer.targetObject);
+			}
+		}
+
+		public static SerializedObject GetRenderersSerializedObject (SerializedObject serializedObject) {
+			if (serializedObject.isEditingMultipleObjects) {
+				var renderers = new List<Object>();
+				foreach (var o in serializedObject.targetObjects) {
+					var component = o as Component;
+					if (component != null) {
+						var renderer = component.GetComponent<Renderer>();
+						if (renderer != null) 
+							renderers.Add(renderer);
+					}
+				}
+				return new SerializedObject(renderers.ToArray());
+			} else {
+				var component = serializedObject.targetObject as Component;
+				if (component != null) {
+					var renderer = component.GetComponent<Renderer>();
+					if (renderer != null)
+						return new SerializedObject(renderer);
+				}
+			}
+
+			return null;
+		}
+
+		public static bool TargetsUseSameData (SerializedObject so) {
+			bool multi = so.isEditingMultipleObjects;
+			if (multi) {
+				int n = so.targetObjects.Length;
+				var first = so.targetObjects[0] as SkeletonRenderer;
+				for (int i = 1; i < n; i++) {
+					var sr = so.targetObjects[i] as SkeletonRenderer;
+					if (sr != null && sr.skeletonDataAsset != first.skeletonDataAsset)
+						return false;
+				}
 			}
 			}
+			return true;
 		}
 		}
 
 
 		public static void SortingPropertyFields (SerializedSortingProperties prop, bool applyModifiedProperties) {
 		public static void SortingPropertyFields (SerializedSortingProperties prop, bool applyModifiedProperties) {
 			if (applyModifiedProperties) {
 			if (applyModifiedProperties) {
 				EditorGUI.BeginChangeCheck();
 				EditorGUI.BeginChangeCheck();
 				SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder);
 				SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder);
-				if(EditorGUI.EndChangeCheck()) {
+				if(EditorGUI.EndChangeCheck())
 					prop.ApplyModifiedProperties();
 					prop.ApplyModifiedProperties();
-					EditorUtility.SetDirty(prop.renderer.targetObject);
-				}
 			} else {
 			} else {
 				SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder);
 				SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder);
 			}
 			}

+ 1 - 0
spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs

@@ -40,6 +40,7 @@ namespace Spine.Unity.Editor {
 
 
 	[InitializeOnLoad]
 	[InitializeOnLoad]
 	[CustomEditor(typeof(SkeletonGraphic))]
 	[CustomEditor(typeof(SkeletonGraphic))]
+	[CanEditMultipleObjects]
 	public class SkeletonGraphicInspector : UnityEditor.Editor {
 	public class SkeletonGraphicInspector : UnityEditor.Editor {
 		SerializedProperty material_, color_;
 		SerializedProperty material_, color_;
 		SerializedProperty skeletonDataAsset_, initialSkinName_;
 		SerializedProperty skeletonDataAsset_, initialSkinName_;

+ 15 - 12
spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs

@@ -114,6 +114,8 @@ namespace Spine.Unity.Editor {
 				skeleton = skeletonRenderer.skeleton;
 				skeleton = skeletonRenderer.skeleton;
 			}
 			}
 
 
+			if (!skeletonRenderer.valid) return;
+
 			UpdateAttachments();
 			UpdateAttachments();
 			isPrefab |= PrefabUtility.GetPrefabType(this.target) == PrefabType.Prefab;
 			isPrefab |= PrefabUtility.GetPrefabType(this.target) == PrefabType.Prefab;
 		}
 		}
@@ -142,7 +144,6 @@ namespace Spine.Unity.Editor {
 
 
 		void UpdateAttachments () {
 		void UpdateAttachments () {
 			attachmentTable = new Dictionary<Slot, List<Attachment>>();
 			attachmentTable = new Dictionary<Slot, List<Attachment>>();
-
 			Skin skin = skeleton.Skin ?? skeletonRenderer.skeletonDataAsset.GetSkeletonData(true).DefaultSkin;
 			Skin skin = skeleton.Skin ?? skeletonRenderer.skeletonDataAsset.GetSkeletonData(true).DefaultSkin;
 			for (int i = skeleton.Slots.Count-1; i >= 0; i--) {
 			for (int i = skeleton.Slots.Count-1; i >= 0; i--) {
 				List<Attachment> attachments = new List<Attachment>();
 				List<Attachment> attachments = new List<Attachment>();
@@ -176,20 +177,22 @@ namespace Spine.Unity.Editor {
 				return;
 				return;
 			}
 			}
 
 
-			skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true);
-
-			GUILayout.BeginHorizontal();
-			EditorGUI.BeginDisabledGroup(skeletonUtility.boneRoot != null);
-			{
-				if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24)))
-					SpawnHierarchyContextMenu();
+			if (!skeletonRenderer.valid) {
+				GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", SpineEditorUtilities.Icons.warning));
+				return;	
 			}
 			}
-			EditorGUI.EndDisabledGroup();
 
 
-			//		if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24)))
-			//			skeletonUtility.SpawnSubRenderers(true);
+			skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true);
+
+			using (new GUILayout.HorizontalScope()) {
+				using (new EditorGUI.DisabledGroupScope(skeletonUtility.boneRoot != null)) {
+					if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24)))
+						SpawnHierarchyContextMenu();
+				}
 
 
-			GUILayout.EndHorizontal();
+				// if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24)))
+				// skeletonUtility.SpawnSubRenderers(true);
+			}
 
 
 			EditorGUI.BeginChangeCheck();
 			EditorGUI.BeginChangeCheck();
 			skeleton.FlipX = EditorGUILayout.ToggleLeft("Flip X", skeleton.FlipX);
 			skeleton.FlipX = EditorGUILayout.ToggleLeft("Flip X", skeleton.FlipX);