Browse Source

[unity] Improved multiedit for attributes. Updated inspectors.

pharan 7 years ago
parent
commit
230ae89d66

+ 14 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/BoneFollowerInspector.cs

@@ -46,7 +46,7 @@ namespace Spine.Unity.Editor {
 		[MenuItem ("CONTEXT/SkeletonRenderer/Add BoneFollower GameObject")]
 		[MenuItem ("CONTEXT/SkeletonRenderer/Add BoneFollower GameObject")]
 		static void AddBoneFollowerGameObject (MenuCommand cmd) {
 		static void AddBoneFollowerGameObject (MenuCommand cmd) {
 			var skeletonRenderer = cmd.context as SkeletonRenderer;
 			var skeletonRenderer = cmd.context as SkeletonRenderer;
-			var go = new GameObject("BoneFollower");
+			var go = new GameObject("New BoneFollower");
 			var t = go.transform;
 			var t = go.transform;
 			t.SetParent(skeletonRenderer.transform);
 			t.SetParent(skeletonRenderer.transform);
 			t.localPosition = Vector3.zero;
 			t.localPosition = Vector3.zero;
@@ -65,8 +65,20 @@ namespace Spine.Unity.Editor {
 			var skeletonRenderer = cmd.context as SkeletonRenderer;
 			var skeletonRenderer = cmd.context as SkeletonRenderer;
 			return skeletonRenderer.valid;
 			return skeletonRenderer.valid;
 		}
 		}
+
+		[MenuItem("CONTEXT/BoneFollower/Rename BoneFollower GameObject")]
+		static void RenameGameObject (MenuCommand cmd) {
+			AutonameGameObject(cmd.context as BoneFollower);
+		}
 		#endregion
 		#endregion
 
 
+		static void AutonameGameObject (BoneFollower boneFollower) {
+			if (boneFollower == null) return;
+
+			string boneName = boneFollower.boneName;
+			boneFollower.gameObject.name = string.IsNullOrEmpty(boneName) ? "BoneFollower" : string.Format("{0} (BoneFollower)", boneName);
+		}
+
 		void OnEnable () {
 		void OnEnable () {
 			skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
 			skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
 			boneName = serializedObject.FindProperty("boneName");
 			boneName = serializedObject.FindProperty("boneName");
@@ -195,7 +207,7 @@ namespace Spine.Unity.Editor {
 			bool missingRigidBody = (hasCollider2D && component.GetComponent<Rigidbody2D>() == null) || (hasCollider3D && component.GetComponent<Rigidbody>() == null);
 			bool missingRigidBody = (hasCollider2D && component.GetComponent<Rigidbody2D>() == null) || (hasCollider3D && component.GetComponent<Rigidbody>() == null);
 			if (missingRigidBody) {
 			if (missingRigidBody) {
 				using (new SpineInspectorUtility.BoxScope()) {
 				using (new SpineInspectorUtility.BoxScope()) {
-					EditorGUILayout.HelpBox("Collider detected. Unity recommends adding a Rigidbody to the parent Transforms of any colliders that are intended to be dynamically repositioned and rotated.", MessageType.Warning);
+					EditorGUILayout.HelpBox("Collider detected. Unity recommends adding a Rigidbody to the Transforms of any colliders that are intended to be dynamically repositioned and rotated.", MessageType.Warning);
 					var rbType = hasCollider2D ? typeof(Rigidbody2D) : typeof(Rigidbody);
 					var rbType = hasCollider2D ? typeof(Rigidbody2D) : typeof(Rigidbody);
 					string rbLabel = string.Format("Add {0}", rbType.Name);
 					string rbLabel = string.Format("Add {0}", rbType.Name);
 					var rbContent = SpineInspectorUtility.TempContent(rbLabel, SpineInspectorUtility.UnityIcon(rbType), "Add a rigidbody to this GameObject to be the Physics body parent of the attached collider.");
 					var rbContent = SpineInspectorUtility.TempContent(rbLabel, SpineInspectorUtility.UnityIcon(rbType), "Add a rigidbody to this GameObject to be the Physics body parent of the attached collider.");

+ 15 - 17
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonAnimationInspector.cs

@@ -56,16 +56,12 @@ namespace Spine.Unity.Editor {
 			bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject);
 			bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject);
 
 
 			if (multi) {
 			if (multi) {
-				foreach (var o in targets)		
-					TrySetAnimation(o, multi);
+				foreach (var o in targets)
+					TrySetAnimation(o as SkeletonAnimation, multi);
 				
 				
 				EditorGUILayout.Space();
 				EditorGUILayout.Space();
 				if (!sameData) {
 				if (!sameData) {
-					#if UNITY_5_3_OR_NEWER
 					EditorGUILayout.DelayedTextField(animationName);
 					EditorGUILayout.DelayedTextField(animationName);
-					#else
-					animationName.stringValue = EditorGUILayout.TextField(animationName.displayName, animationName.stringValue);
-					#endif
 				} else {
 				} else {
 					EditorGUI.BeginChangeCheck();
 					EditorGUI.BeginChangeCheck();
 					EditorGUILayout.PropertyField(animationName);
 					EditorGUILayout.PropertyField(animationName);
@@ -78,7 +74,7 @@ namespace Spine.Unity.Editor {
 					component.timeScale = Mathf.Max(component.timeScale, 0);
 					component.timeScale = Mathf.Max(component.timeScale, 0);
 				}
 				}
 			} else {
 			} else {
-				TrySetAnimation(target, multi);
+				TrySetAnimation(target as SkeletonAnimation, multi);
 
 
 				EditorGUILayout.Space();
 				EditorGUILayout.Space();
 				EditorGUI.BeginChangeCheck();
 				EditorGUI.BeginChangeCheck();
@@ -99,31 +95,33 @@ namespace Spine.Unity.Editor {
 			}
 			}
 		}
 		}
 
 
-		protected void TrySetAnimation (Object o, bool multi) {
-			var skeletonAnimation = o as SkeletonAnimation;
+		protected void TrySetAnimation (SkeletonAnimation skeletonAnimation, bool multi) {
 			if (skeletonAnimation == null) return;
 			if (skeletonAnimation == null) return;
 			if (!skeletonAnimation.valid)
 			if (!skeletonAnimation.valid)
 				return;
 				return;
 
 
 			if (!isInspectingPrefab) {
 			if (!isInspectingPrefab) {
 				if (wasAnimationNameChanged) {
 				if (wasAnimationNameChanged) {
+					var skeleton = skeletonAnimation.Skeleton;
+					var state = skeletonAnimation.AnimationState;
+
 					if (!Application.isPlaying) {
 					if (!Application.isPlaying) {
-						if (skeletonAnimation.state != null) skeletonAnimation.state.ClearTrack(0);
-						skeletonAnimation.skeleton.SetToSetupPose();
+						if (state != null) state.ClearTrack(0);
+						skeleton.SetToSetupPose();
 					}
 					}
 
 
-					Spine.Animation animationToUse = skeletonAnimation.skeleton.Data.FindAnimation(animationName.stringValue);
+					Spine.Animation animationToUse = skeleton.Data.FindAnimation(animationName.stringValue);
 
 
 					if (!Application.isPlaying) {
 					if (!Application.isPlaying) {
-						if (animationToUse != null) animationToUse.PoseSkeleton(skeletonAnimation.Skeleton, 0f);
-						skeletonAnimation.Update(0);
+						if (animationToUse != null) animationToUse.PoseSkeleton(skeleton, 0f);
+						skeleton.UpdateWorldTransform();
 						skeletonAnimation.LateUpdate();
 						skeletonAnimation.LateUpdate();
 						requireRepaint = true;
 						requireRepaint = true;
 					} else {
 					} else {
 						if (animationToUse != null)
 						if (animationToUse != null)
-							skeletonAnimation.state.SetAnimation(0, animationToUse, loop.boolValue);
+							state.SetAnimation(0, animationToUse, loop.boolValue);
 						else
 						else
-							skeletonAnimation.state.ClearTrack(0);
+							state.ClearTrack(0);
 					}
 					}
 
 
 					wasAnimationNameChanged = false;
 					wasAnimationNameChanged = false;
@@ -131,7 +129,7 @@ namespace Spine.Unity.Editor {
 
 
 				// 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 (!multi && Application.isPlaying) {
 				if (!multi && Application.isPlaying) {
-					TrackEntry current = skeletonAnimation.state.GetCurrent(0);
+					TrackEntry current = skeletonAnimation.AnimationState.GetCurrent(0);
 					if (current != null) {
 					if (current != null) {
 						if (skeletonAnimation.AnimationName != animationName.stringValue)
 						if (skeletonAnimation.AnimationName != animationName.stringValue)
 							animationName.stringValue = current.Animation.Name;
 							animationName.stringValue = current.Animation.Name;

+ 150 - 112
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SkeletonRendererInspector.cs

@@ -49,14 +49,20 @@ namespace Spine.Unity.Editor {
 		protected SerializedProperty skeletonDataAsset, initialSkinName;
 		protected SerializedProperty skeletonDataAsset, initialSkinName;
 		protected SerializedProperty initialFlipX, initialFlipY;
 		protected SerializedProperty initialFlipX, initialFlipY;
 		protected SerializedProperty singleSubmesh, separatorSlotNames, clearStateOnDisable, immutableTriangles;
 		protected SerializedProperty singleSubmesh, separatorSlotNames, clearStateOnDisable, immutableTriangles;
-		protected SerializedProperty normals, tangents, meshes, zSpacing, pmaVertexColors, tintBlack; // MeshGenerator settings
+		protected SerializedProperty normals, tangents, zSpacing, pmaVertexColors, tintBlack; // MeshGenerator settings
 		protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
 		protected SpineInspectorUtility.SerializedSortingProperties sortingProperties;
+
 		protected bool isInspectingPrefab;
 		protected bool isInspectingPrefab;
+		protected bool forceReloadQueued = false;
 
 
 		protected GUIContent SkeletonDataAssetLabel, SkeletonUtilityButtonContent;
 		protected GUIContent SkeletonDataAssetLabel, SkeletonUtilityButtonContent;
-		protected GUIContent PMAVertexColorsLabel, ClearStateOnDisableLabel, ZSpacingLabel, MeshesLabel, ImmubleTrianglesLabel, TintBlackLabel, SingleSubmeshLabel;
+		protected GUIContent PMAVertexColorsLabel, ClearStateOnDisableLabel, ZSpacingLabel, ImmubleTrianglesLabel, TintBlackLabel, SingleSubmeshLabel;
 		protected GUIContent NormalsLabel, TangentsLabel;
 		protected GUIContent NormalsLabel, TangentsLabel;
-		const string ReloadButtonLabel = "Reload";
+		
+		const string ReloadButtonString = "Reload";
+		static GUILayoutOption reloadButtonWidth;
+		static GUILayoutOption ReloadButtonWidth { get { return reloadButtonWidth = reloadButtonWidth ?? GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonString)).x + 20); } }
+		static GUIStyle ReloadButtonStyle { get { return EditorStyles.miniButtonRight; } }
 
 
 		protected bool TargetIsValid {
 		protected bool TargetIsValid {
 			get {
 			get {
@@ -76,13 +82,12 @@ namespace Spine.Unity.Editor {
 
 
 		protected virtual void OnEnable () {
 		protected virtual void OnEnable () {
 			isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
 			isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
-			
+
 			SpineEditorUtilities.ConfirmInitialization();
 			SpineEditorUtilities.ConfirmInitialization();
 
 
 			// Labels
 			// Labels
 			SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", Icons.spine);
 			SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", Icons.spine);
 			SkeletonUtilityButtonContent = new GUIContent("Add Skeleton Utility", Icons.skeletonUtility);
 			SkeletonUtilityButtonContent = new GUIContent("Add Skeleton Utility", Icons.skeletonUtility);
-			MeshesLabel = new GUIContent("Render MeshAttachments", "Disable to optimize rendering for skeletons that don't use Mesh Attachments");
 			ImmubleTrianglesLabel = new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility");
 			ImmubleTrianglesLabel = new GUIContent("Immutable Triangles", "Enable to optimize rendering for skeletons that never change attachment visbility");
 			PMAVertexColorsLabel = new GUIContent("PMA Vertex Colors", "Use this if you are using the default Spine/Skeleton shader or any premultiply-alpha shader.");
 			PMAVertexColorsLabel = new GUIContent("PMA Vertex Colors", "Use this if you are using the default Spine/Skeleton shader or any premultiply-alpha shader.");
 			ClearStateOnDisableLabel = new GUIContent("Clear State On Disable", "Use this if you are pooling or enabling/disabling your Spine GameObject.");
 			ClearStateOnDisableLabel = new GUIContent("Clear State On Disable", "Use this if you are pooling or enabling/disabling your Spine GameObject.");
@@ -90,7 +95,7 @@ namespace Spine.Unity.Editor {
 			NormalsLabel = new GUIContent("Add Normals", "Use this if your shader requires vertex normals. A more efficient solution for 2D setups is to modify the shader to assume a single normal value for the whole mesh.");
 			NormalsLabel = new GUIContent("Add Normals", "Use this if your shader requires vertex normals. A more efficient solution for 2D setups is to modify the shader to assume a single normal value for the whole mesh.");
 			TangentsLabel = new GUIContent("Solve Tangents", "Calculates the tangents per frame. Use this if you are using lit shaders (usually with normal maps) that require vertex tangents.");
 			TangentsLabel = new GUIContent("Solve Tangents", "Calculates the tangents per frame. Use this if you are using lit shaders (usually with normal maps) that require vertex tangents.");
 			TintBlackLabel = new GUIContent("Tint Black (!)", "Adds black tint vertex data to the mesh as UV2 and UV3. Black tinting requires that the shader interpret UV2 and UV3 as black tint colors for this effect to work. You may also use the default [Spine/Skeleton Tint Black] shader.\n\nIf you only need to tint the whole skeleton and not individual parts, the [Spine/Skeleton Tint] shader is recommended for better efficiency and changing/animating the _Black material property via MaterialPropertyBlock.");
 			TintBlackLabel = new GUIContent("Tint Black (!)", "Adds black tint vertex data to the mesh as UV2 and UV3. Black tinting requires that the shader interpret UV2 and UV3 as black tint colors for this effect to work. You may also use the default [Spine/Skeleton Tint Black] shader.\n\nIf you only need to tint the whole skeleton and not individual parts, the [Spine/Skeleton Tint] shader is recommended for better efficiency and changing/animating the _Black material property via MaterialPropertyBlock.");
-			SingleSubmeshLabel = new GUIContent("Use Single Submesh", "Simplifies submesh determination by assuming you are only using one Material and need only one submesh. This is will disable render separation and custom slot materials.");
+			SingleSubmeshLabel = new GUIContent("Use Single Submesh", "Simplifies submesh generation by assuming you are only using one Material and need only one submesh. This is will disable multiple materials, render separation, and custom slot materials.");
 
 
 			var so = this.serializedObject;
 			var so = this.serializedObject;
 			skeletonDataAsset = so.FindProperty("skeletonDataAsset");
 			skeletonDataAsset = so.FindProperty("skeletonDataAsset");
@@ -99,7 +104,6 @@ namespace Spine.Unity.Editor {
 			initialFlipY = so.FindProperty("initialFlipY");
 			initialFlipY = so.FindProperty("initialFlipY");
 			normals = so.FindProperty("addNormals");
 			normals = so.FindProperty("addNormals");
 			tangents = so.FindProperty("calculateTangents");
 			tangents = so.FindProperty("calculateTangents");
-			meshes = so.FindProperty("renderMeshes");
 			immutableTriangles = so.FindProperty("immutableTriangles");
 			immutableTriangles = so.FindProperty("immutableTriangles");
 			pmaVertexColors = so.FindProperty("pmaVertexColors");
 			pmaVertexColors = so.FindProperty("pmaVertexColors");
 			clearStateOnDisable = so.FindProperty("clearStateOnDisable");
 			clearStateOnDisable = so.FindProperty("clearStateOnDisable");
@@ -111,80 +115,115 @@ namespace Spine.Unity.Editor {
 
 
 			zSpacing = so.FindProperty("zSpacing");
 			zSpacing = so.FindProperty("zSpacing");
 
 
-			SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject);
-			sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(rso);
+			SerializedObject renderersSerializedObject = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject); // Allows proper multi-edit behavior.
+			sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(renderersSerializedObject);
 		}
 		}
 
 
-		GUIContent[] skins;
-		ExposedList<Skin> loadedSkinList;
+		public void OnSceneGUI () {
+			var skeletonRenderer = (SkeletonRenderer)target;
+			var skeleton = skeletonRenderer.Skeleton;
+			var transform = skeletonRenderer.transform;
+			if (skeleton == null) return;
 
 
-		protected virtual void DrawInspectorGUI (bool multi) {
-			bool valid = TargetIsValid;
-			var reloadWidth = GUILayout.Width(GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20);
-			var reloadButtonStyle = EditorStyles.miniButtonRight;
+			SpineHandles.DrawBones(transform, skeleton);
+		}
 
 
-			if (multi) {
-				using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
-					SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
-					if (GUILayout.Button(ReloadButtonLabel, reloadButtonStyle, reloadWidth)) {
+		override public void OnInspectorGUI () {
+			bool multi = serializedObject.isEditingMultipleObjects;
+			DrawInspectorGUI(multi);
+			if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current)) {
+				if (!Application.isPlaying) {
+					if (multi) {
+						foreach (var o in targets) EditorForceInitializeComponent((SkeletonRenderer)o);
+					} else {
+						EditorForceInitializeComponent((SkeletonRenderer)target);
+					}
+					SceneView.RepaintAll();
+				}
+			}
+
+			if (!Application.isPlaying && Event.current.type == EventType.Layout) {
+				bool mismatchDetected = false;
+				if (multi) {
+					foreach (var o in targets)
+						mismatchDetected |= UpdateIfSkinMismatch((SkeletonRenderer)o);
+				} else {
+					mismatchDetected |= UpdateIfSkinMismatch(target as SkeletonRenderer);
+				}
+
+				if (mismatchDetected) {
+					mismatchDetected = false;
+					SceneView.RepaintAll();
+				}
+			}
+		}
+
+		protected virtual void DrawInspectorGUI (bool multi) {
+			// Initialize.
+			if (Event.current.type == EventType.Layout) {
+				if (forceReloadQueued) {
+					forceReloadQueued = false;
+					if (multi) {
+						foreach (var c in targets)
+							EditorForceReloadSkeletonDataAssetAndComponent(c as SkeletonRenderer);
+					} else {
+						EditorForceReloadSkeletonDataAssetAndComponent(target as SkeletonRenderer);
+					}
+				} else {
+					if (multi) {
 						foreach (var c in targets) {
 						foreach (var c in targets) {
 							var component = c as SkeletonRenderer;
 							var component = c as SkeletonRenderer;
-							if (component.skeletonDataAsset != null) {
-								foreach (AtlasAssetBase aa in component.skeletonDataAsset.atlasAssets) {
-									if (aa != null)
-										aa.Clear();
-								}
-								component.skeletonDataAsset.Clear();
+							if (!component.valid) {
+								EditorForceInitializeComponent(component);
+								if (!component.valid) continue;
 							}
 							}
-							component.Initialize(true);
 						}
 						}
+					} else {
+						var component = (SkeletonRenderer)target;
+						if (!component.valid)
+							EditorForceInitializeComponent(component);
 					}
 					}
 				}
 				}
 
 
-				foreach (var c in targets) {
-					var component = c as SkeletonRenderer;
-					if (!component.valid) {
-						if (Event.current.type == EventType.Layout) {
-							component.Initialize(true);
-							component.LateUpdate();
+				#if NO_PREFAB_MESH
+				if (isInspectingPrefab) {
+					if (multi) {
+						foreach (var c in targets) {
+							var component = (SkeletonRenderer)c;
+							MeshFilter meshFilter = component.GetComponent<MeshFilter>();
+							if (meshFilter != null && meshFilter.sharedMesh != null)
+								meshFilter.sharedMesh = null;
 						}
 						}
-						if (!component.valid)
-							continue;
-					}
-
-					#if NO_PREFAB_MESH
-					if (isInspectingPrefab) {
+					} else {
+						var component = (SkeletonRenderer)target;
 						MeshFilter meshFilter = component.GetComponent<MeshFilter>();
 						MeshFilter meshFilter = component.GetComponent<MeshFilter>();
 						if (meshFilter != null && meshFilter.sharedMesh != null)
 						if (meshFilter != null && meshFilter.sharedMesh != null)
 							meshFilter.sharedMesh = null;
 							meshFilter.sharedMesh = null;
 					}
 					}
-					#endif
 				}
 				}
-					
-				if (valid)
-					EditorGUILayout.PropertyField(initialSkinName);					
+				#endif
+			}
 
 
-			} else {
-				var component = (SkeletonRenderer)target;
+			bool valid = TargetIsValid;
 
 
-				if (!component.valid && Event.current.type == EventType.Layout) {
-					component.Initialize(true);
-					component.LateUpdate();
+			// Fields.
+			if (multi) {
+				using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
+					SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
+					if (GUILayout.Button(ReloadButtonString, ReloadButtonStyle, ReloadButtonWidth))
+						forceReloadQueued = true;
 				}
 				}
 
 
+				if (valid) EditorGUILayout.PropertyField(initialSkinName, SpineInspectorUtility.TempContent("Initial Skin"));
+
+			} else {
+				var component = (SkeletonRenderer)target;
+
 				using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
 				using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox)) {
 					SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
 					SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
 					if (component.valid) {
 					if (component.valid) {
-						if (GUILayout.Button(ReloadButtonLabel, reloadButtonStyle, reloadWidth)) {
-							if (component.skeletonDataAsset != null) {
-								foreach (AtlasAssetBase aa in component.skeletonDataAsset.atlasAssets) {
-									if (aa != null)
-										aa.Clear();
-								}
-								component.skeletonDataAsset.Clear();
-							}
-							component.Initialize(true);
-						}
+						if (GUILayout.Button(ReloadButtonString, ReloadButtonStyle, ReloadButtonWidth))
+							forceReloadQueued = true;
 					}
 					}
 				}
 				}
 
 
@@ -193,38 +232,14 @@ namespace Spine.Unity.Editor {
 					return;
 					return;
 				}
 				}
 
 
-				#if NO_PREFAB_MESH
-				if (isInspectingPrefab) {
-					MeshFilter meshFilter = component.GetComponent<MeshFilter>();
-					if (meshFilter != null && meshFilter.sharedMesh != null)
-						meshFilter.sharedMesh = null;
+				if (!SkeletonDataAssetIsValid(component.skeletonDataAsset)) {
+					EditorGUILayout.HelpBox("Skeleton Data Asset error. Please check Skeleton Data Asset.", MessageType.Error);
+					return;
 				}
 				}
-				#endif
-
-				// Initial skin name.
-				if (component.valid) {
-					var skeletonDataSkins = component.skeleton.Data.Skins;
-					int skinCount = skeletonDataSkins.Count;
-					if (loadedSkinList != skeletonDataSkins) {
-						skins = new GUIContent[skinCount];
-						loadedSkinList = skeletonDataSkins;
-						for (int i = 0; i < skins.Length; i++) {
-							string skinNameString = skeletonDataSkins.Items[i].Name;
-							skins[i] = new GUIContent(skinNameString, Icons.skin);
-						}
-					}
-					
-					int skinIndex = 0;
-					for (int i = 0; i < skins.Length; i++) {
-						string skinNameString = skeletonDataSkins.Items[i].Name;
-						if (skinNameString == initialSkinName.stringValue)
-							skinIndex = i;
-					}
 
 
-					skinIndex = EditorGUILayout.Popup(SpineInspectorUtility.TempContent("Initial Skin"), skinIndex, skins);
-					if (skins.Length > 0) // Support attachmentless/skinless SkeletonData.
-						initialSkinName.stringValue = skins[skinIndex].text;
-				}
+				if (valid)
+					EditorGUILayout.PropertyField(initialSkinName, SpineInspectorUtility.TempContent("Initial Skin"));
+				
 			}
 			}
 
 
 			EditorGUILayout.Space();
 			EditorGUILayout.Space();
@@ -232,8 +247,9 @@ namespace Spine.Unity.Editor {
 			// Sorting Layers
 			// Sorting Layers
 			SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
 			SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
 
 
-			if (!TargetIsValid) return;
-			
+			if (!valid)
+				return;
+
 			// More Render Options...
 			// More Render Options...
 			using (new SpineInspectorUtility.BoxScope()) {
 			using (new SpineInspectorUtility.BoxScope()) {
 				EditorGUI.BeginChangeCheck();
 				EditorGUI.BeginChangeCheck();
@@ -250,11 +266,11 @@ namespace Spine.Unity.Editor {
 				EditorGUILayout.EndHorizontal();
 				EditorGUILayout.EndHorizontal();
 
 
 				if (advancedFoldout) {
 				if (advancedFoldout) {
-					
+
 					using (new SpineInspectorUtility.IndentScope()) {
 					using (new SpineInspectorUtility.IndentScope()) {
 						using (new EditorGUILayout.HorizontalScope()) {
 						using (new EditorGUILayout.HorizontalScope()) {
 							SpineInspectorUtility.ToggleLeftLayout(initialFlipX);
 							SpineInspectorUtility.ToggleLeftLayout(initialFlipX);
-							SpineInspectorUtility.ToggleLeftLayout(initialFlipY);							
+							SpineInspectorUtility.ToggleLeftLayout(initialFlipY);
 							EditorGUILayout.Space();
 							EditorGUILayout.Space();
 						}
 						}
 
 
@@ -263,7 +279,6 @@ namespace Spine.Unity.Editor {
 						using (new SpineInspectorUtility.LabelWidthScope()) {
 						using (new SpineInspectorUtility.LabelWidthScope()) {
 							// Optimization options
 							// Optimization options
 							if (singleSubmesh != null) EditorGUILayout.PropertyField(singleSubmesh, SingleSubmeshLabel);
 							if (singleSubmesh != null) EditorGUILayout.PropertyField(singleSubmesh, SingleSubmeshLabel);
-							//if (meshes != null) EditorGUILayout.PropertyField(meshes, MeshesLabel);
 							if (immutableTriangles != null) EditorGUILayout.PropertyField(immutableTriangles, ImmubleTrianglesLabel);
 							if (immutableTriangles != null) EditorGUILayout.PropertyField(immutableTriangles, ImmubleTrianglesLabel);
 							EditorGUILayout.PropertyField(clearStateOnDisable, ClearStateOnDisableLabel);
 							EditorGUILayout.PropertyField(clearStateOnDisable, ClearStateOnDisableLabel);
 							EditorGUILayout.Space();
 							EditorGUILayout.Space();
@@ -290,7 +305,7 @@ namespace Spine.Unity.Editor {
 
 
 						EditorGUILayout.Space();
 						EditorGUILayout.Space();
 
 
-						if (TargetIsValid && !isInspectingPrefab) {
+						if (valid && !isInspectingPrefab) {
 							if (multi) {
 							if (multi) {
 								// Support multi-edit SkeletonUtility button.
 								// Support multi-edit SkeletonUtility button.
 								//	EditorGUILayout.Space();
 								//	EditorGUILayout.Space();
@@ -302,7 +317,7 @@ namespace Spine.Unity.Editor {
 								//	}
 								//	}
 							} else {
 							} else {
 								var component = (Component)target;
 								var component = (Component)target;
-								if (component.GetComponent<SkeletonUtility>() == null) {						
+								if (component.GetComponent<SkeletonUtility>() == null) {
 									if (SpineInspectorUtility.CenteredButton(SkeletonUtilityButtonContent, 21, true, 200f))
 									if (SpineInspectorUtility.CenteredButton(SkeletonUtilityButtonContent, 21, true, 200f))
 										component.gameObject.AddComponent<SkeletonUtility>();
 										component.gameObject.AddComponent<SkeletonUtility>();
 								}
 								}
@@ -330,7 +345,7 @@ namespace Spine.Unity.Editor {
 				var sr = separatorSlotNames.serializedObject.targetObject as ISkeletonComponent;
 				var sr = separatorSlotNames.serializedObject.targetObject as ISkeletonComponent;
 				var skeleton = sr.Skeleton;
 				var skeleton = sr.Skeleton;
 				int lastSlot = skeleton.Slots.Count - 1;
 				int lastSlot = skeleton.Slots.Count - 1;
-				if (skeleton != null) {					
+				if (skeleton != null) {
 					for (int i = 0, n = separatorSlotNames.arraySize; i < n; i++) {
 					for (int i = 0, n = separatorSlotNames.arraySize; i < n; i++) {
 						int index = skeleton.FindSlotIndex(separatorSlotNames.GetArrayElementAtIndex(i).stringValue);
 						int index = skeleton.FindSlotIndex(separatorSlotNames.GetArrayElementAtIndex(i).stringValue);
 						if (index == 0 || index == lastSlot) {
 						if (index == 0 || index == lastSlot) {
@@ -360,27 +375,50 @@ namespace Spine.Unity.Editor {
 			}
 			}
 		}
 		}
 
 
-		public void OnSceneGUI () {
-			var skeletonRenderer = (SkeletonRenderer)target;
-			var skeleton = skeletonRenderer.skeleton;
-			var transform = skeletonRenderer.transform;
-			if (skeleton == null) return;
-
-			SpineHandles.DrawBones(transform, skeleton);
+		static bool UpdateIfSkinMismatch (SkeletonRenderer skeletonRenderer) {
+			if (!skeletonRenderer.valid) return false;
+
+			var skin = skeletonRenderer.Skeleton.Skin;
+			string skeletonSkinName = skin != null ? skin.Name : null;
+			string componentSkinName = skeletonRenderer.initialSkinName;
+			bool defaultCase = skin == null && string.IsNullOrEmpty(componentSkinName);
+			bool fieldMatchesSkin = defaultCase || string.Equals(componentSkinName, skeletonSkinName, System.StringComparison.Ordinal);
+
+			if (!fieldMatchesSkin) {
+				Skin skinToSet = string.IsNullOrEmpty(componentSkinName) ? null : skeletonRenderer.Skeleton.Data.FindSkin(componentSkinName);
+				skeletonRenderer.Skeleton.Skin = skinToSet;
+				skeletonRenderer.Skeleton.SetSlotsToSetupPose();
+				skeletonRenderer.LateUpdate();
+				return true;
+			}
+			return false;
 		}
 		}
 
 
-		override public void OnInspectorGUI () {
-			bool multi = serializedObject.isEditingMultipleObjects;
-			DrawInspectorGUI(multi);
-			if (serializedObject.ApplyModifiedProperties() || SpineInspectorUtility.UndoRedoPerformed(Event.current)) {
-				if (!Application.isPlaying) {
-					if (multi)
-						foreach (var o in targets)
-							((SkeletonRenderer)o).Initialize(true);
-					else
-						((SkeletonRenderer)target).Initialize(true);
+		static void EditorForceReloadSkeletonDataAssetAndComponent (SkeletonRenderer component) {
+			if (component == null) return;
+
+			// Clear all and reload.
+			if (component.skeletonDataAsset != null) {
+				foreach (AtlasAssetBase aa in component.skeletonDataAsset.atlasAssets) {
+					if (aa != null) aa.Clear();
 				}
 				}
+				component.skeletonDataAsset.Clear();
 			}
 			}
+			component.skeletonDataAsset.GetSkeletonData(true);
+
+			// Reinitialize.
+			EditorForceInitializeComponent(component);
+		}
+
+		static void EditorForceInitializeComponent (SkeletonRenderer component) {
+			if (component == null) return;
+			if (!SkeletonDataAssetIsValid(component.SkeletonDataAsset)) return;
+			component.Initialize(true);
+			component.LateUpdate();
+		}
+
+		static bool SkeletonDataAssetIsValid (SkeletonDataAsset asset) {
+			return asset != null && asset.GetSkeletonData(quiet: true) != null;
 		}
 		}
 
 
 	}
 	}

+ 70 - 52
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs

@@ -39,24 +39,24 @@ using Spine;
 
 
 namespace Spine.Unity.Editor {
 namespace Spine.Unity.Editor {
 	public struct SpineDrawerValuePair {
 	public struct SpineDrawerValuePair {
-		public string str;
+		public string stringValue;
 		public SerializedProperty property;
 		public SerializedProperty property;
 
 
 		public SpineDrawerValuePair (string val, SerializedProperty property) {
 		public SpineDrawerValuePair (string val, SerializedProperty property) {
-			this.str = val;
+			this.stringValue = val;
 			this.property = property;
 			this.property = property;
 		}
 		}
 	}
 	}
 
 
 	public abstract class SpineTreeItemDrawerBase<T> : PropertyDrawer where T:SpineAttributeBase {
 	public abstract class SpineTreeItemDrawerBase<T> : PropertyDrawer where T:SpineAttributeBase {
 		protected SkeletonDataAsset skeletonDataAsset;
 		protected SkeletonDataAsset skeletonDataAsset;
-		internal const string NoneString = "<None>";
+		internal const string NoneStringConstant = "<None>";
 
 
-		// Analysis disable once StaticFieldInGenericType
-		static GUIContent noneLabel;
-		static GUIContent NoneLabel (Texture2D image = null) {
-			if (noneLabel == null)
-				noneLabel = new GUIContent(NoneString);
+		internal virtual string NoneString { get { return NoneStringConstant; } }
+
+		GUIContent noneLabel;
+		GUIContent NoneLabel (Texture2D image = null) {
+			if (noneLabel == null) noneLabel = new GUIContent(NoneString);
 			noneLabel.image = image;
 			noneLabel.image = image;
 			return noneLabel;
 			return noneLabel;
 		}
 		}
@@ -74,6 +74,12 @@ namespace Spine.Unity.Editor {
 				return;
 				return;
 			}
 			}
 
 
+			// Handle multi-editing when instances don't use the same SkeletonDataAsset.
+			if (!SpineInspectorUtility.TargetsUseSameData(property.serializedObject)) { 
+				EditorGUI.DelayedTextField(position, property, label);
+				return;
+			}
+
 			SerializedProperty dataField = property.FindBaseOrSiblingProperty(TargetAttribute.dataField);
 			SerializedProperty dataField = property.FindBaseOrSiblingProperty(TargetAttribute.dataField);
 
 
 			if (dataField != null) {
 			if (dataField != null) {
@@ -116,8 +122,8 @@ namespace Spine.Unity.Editor {
 				
 				
 			position = EditorGUI.PrefixLabel(position, label);
 			position = EditorGUI.PrefixLabel(position, label);
 
 
-			var image = Icon;
-			var propertyStringValue = (property.hasMultipleDifferentValues) ? SpineInspectorUtility.EmDash : property.stringValue;
+			Texture2D image = Icon;
+			string propertyStringValue = (property.hasMultipleDifferentValues) ? SpineInspectorUtility.EmDash : property.stringValue;
 			if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel(image) : SpineInspectorUtility.TempContent(propertyStringValue, image), EditorStyles.popup))
 			if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel(image) : SpineInspectorUtility.TempContent(propertyStringValue, image), EditorStyles.popup))
 				Selector(property);
 				Selector(property);
 		}
 		}
@@ -151,8 +157,10 @@ namespace Spine.Unity.Editor {
 
 
 		protected virtual void HandleSelect (object menuItemObject) {
 		protected virtual void HandleSelect (object menuItemObject) {
 			var clickedItem = (SpineDrawerValuePair)menuItemObject;
 			var clickedItem = (SpineDrawerValuePair)menuItemObject;
-			clickedItem.property.stringValue = clickedItem.str;
-			clickedItem.property.serializedObject.ApplyModifiedProperties();
+			var serializedProperty = clickedItem.property;
+			if (serializedProperty.serializedObject.isEditingMultipleObjects) serializedProperty.stringValue = "oaifnoiasf°ñ123526"; // HACK: to trigger change on multi-editing.
+			serializedProperty.stringValue = clickedItem.stringValue;
+			serializedProperty.serializedObject.ApplyModifiedProperties();
 		}
 		}
 
 
 		public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
 		public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
@@ -167,9 +175,8 @@ namespace Spine.Unity.Editor {
 		protected override Texture2D Icon {	get { return SpineEditorUtilities.Icons.slot; } }
 		protected override Texture2D Icon {	get { return SpineEditorUtilities.Icons.slot; } }
 
 
 		protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineSlot targetAttribute, SkeletonData data) {
 		protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineSlot targetAttribute, SkeletonData data) {
-
 			if (TargetAttribute.includeNone)
 			if (TargetAttribute.includeNone)
-				menu.AddItem(new GUIContent(NoneString), string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
+				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
 
 			for (int i = 0; i < data.Slots.Count; i++) {
 			for (int i = 0; i < data.Slots.Count; i++) {
 				string name = data.Slots.Items[i].Name;
 				string name = data.Slots.Items[i].Name;
@@ -186,7 +193,7 @@ namespace Spine.Unity.Editor {
 							var bbAttachment = attachment as BoundingBoxAttachment;
 							var bbAttachment = attachment as BoundingBoxAttachment;
 							if (bbAttachment != null) {
 							if (bbAttachment != null) {
 								string menuLabel = bbAttachment.IsWeighted() ? name + " (!)" : name;
 								string menuLabel = bbAttachment.IsWeighted() ? name + " (!)" : name;
-								menu.AddItem(new GUIContent(menuLabel), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+								menu.AddItem(new GUIContent(menuLabel), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 								hasBoundingBox = true;
 								hasBoundingBox = true;
 								break;
 								break;
 							}
 							}
@@ -196,7 +203,7 @@ namespace Spine.Unity.Editor {
 							menu.AddDisabledItem(new GUIContent(name));
 							menu.AddDisabledItem(new GUIContent(name));
 
 
 					} else {
 					} else {
-						menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+						menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 					}
 					}
 
 
 				}
 				}
@@ -208,28 +215,33 @@ namespace Spine.Unity.Editor {
 
 
 	[CustomPropertyDrawer(typeof(SpineSkin))]
 	[CustomPropertyDrawer(typeof(SpineSkin))]
 	public class SpineSkinDrawer : SpineTreeItemDrawerBase<SpineSkin> {
 	public class SpineSkinDrawer : SpineTreeItemDrawerBase<SpineSkin> {
+		const string DefaultSkinName = "default";
 
 
 		protected override Texture2D Icon {	get { return SpineEditorUtilities.Icons.skin; } }
 		protected override Texture2D Icon {	get { return SpineEditorUtilities.Icons.skin; } }
 
 
-		public static void GetSkinMenuItems (SkeletonData data, List<string> animationNames, List<GUIContent> menuItems, bool includeNone = true) {
+		internal override string NoneString { get { return TargetAttribute.defaultAsEmptyString ? DefaultSkinName : NoneStringConstant; } }
+
+		public static void GetSkinMenuItems (SkeletonData data, List<string> outputNames, List<GUIContent> outputMenuItems, bool includeNone = true) {
 			if (data == null) return;
 			if (data == null) return;
+			if (outputNames == null) return;
+			if (outputMenuItems == null) return;
 
 
 			var skins = data.Skins;
 			var skins = data.Skins;
 
 
-			animationNames.Clear();
-			menuItems.Clear();
+			outputNames.Clear();
+			outputMenuItems.Clear();
 
 
 			var icon = SpineEditorUtilities.Icons.skin;
 			var icon = SpineEditorUtilities.Icons.skin;
 
 
 			if (includeNone) {
 			if (includeNone) {
-				animationNames.Add("");
-				menuItems.Add(new GUIContent(NoneString, icon));
+				outputNames.Add("");
+				outputMenuItems.Add(new GUIContent(NoneStringConstant, icon));
 			}
 			}
 
 
 			foreach (var s in skins) {
 			foreach (var s in skins) {
-				var skinName = s.Name;
-				animationNames.Add(skinName);
-				menuItems.Add(new GUIContent(skinName, icon));
+				string skinName = s.Name;
+				outputNames.Add(skinName);
+				outputMenuItems.Add(new GUIContent(skinName, icon));
 			}
 			}
 		}
 		}
 
 
@@ -239,9 +251,13 @@ namespace Spine.Unity.Editor {
 
 
 			for (int i = 0; i < data.Skins.Count; i++) {
 			for (int i = 0; i < data.Skins.Count; i++) {
 				string name = data.Skins.Items[i].Name;
 				string name = data.Skins.Items[i].Name;
-				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
-					menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
-			}
+				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal)) {
+					bool isDefault = string.Equals(name, DefaultSkinName, StringComparison.Ordinal);
+					string choiceValue = TargetAttribute.defaultAsEmptyString && isDefault ? string.Empty : name;
+					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && choiceValue == property.stringValue, HandleSelect, new SpineDrawerValuePair(choiceValue, property));
+				}
+					
+			}			
 		}
 		}
 
 
 	}
 	}
@@ -251,23 +267,25 @@ namespace Spine.Unity.Editor {
 
 
 		protected override Texture2D Icon {	get { return SpineEditorUtilities.Icons.animation; } }
 		protected override Texture2D Icon {	get { return SpineEditorUtilities.Icons.animation; } }
 
 
-		public static void GetAnimationMenuItems (SkeletonData data, List<string> animationNames, List<GUIContent> menuItems, bool includeNone = true) {
+		public static void GetAnimationMenuItems (SkeletonData data, List<string> outputNames, List<GUIContent> outputMenuItems, bool includeNone = true) {
 			if (data == null) return;
 			if (data == null) return;
+			if (outputNames == null) return;
+			if (outputMenuItems == null) return;
 
 
 			var animations = data.Animations;
 			var animations = data.Animations;
 
 
-			animationNames.Clear();
-			menuItems.Clear();
+			outputNames.Clear();
+			outputMenuItems.Clear();
 
 
 			if (includeNone) {
 			if (includeNone) {
-				animationNames.Add("");
-				menuItems.Add(new GUIContent(NoneString, SpineEditorUtilities.Icons.animation));
+				outputNames.Add("");
+				outputMenuItems.Add(new GUIContent(NoneStringConstant, SpineEditorUtilities.Icons.animation));
 			}
 			}
 
 
 			foreach (var a in animations) {
 			foreach (var a in animations) {
-				var animationName = a.Name;
-				animationNames.Add(animationName);
-				menuItems.Add(new GUIContent(animationName, SpineEditorUtilities.Icons.animation));
+				string animationName = a.Name;
+				outputNames.Add(animationName);
+				outputMenuItems.Add(new GUIContent(animationName, SpineEditorUtilities.Icons.animation));
 			}
 			}
 		}
 		}
 
 
@@ -275,12 +293,12 @@ namespace Spine.Unity.Editor {
 			var animations = skeletonDataAsset.GetAnimationStateData().SkeletonData.Animations;
 			var animations = skeletonDataAsset.GetAnimationStateData().SkeletonData.Animations;
 
 
 			if (TargetAttribute.includeNone)
 			if (TargetAttribute.includeNone)
-				menu.AddItem(new GUIContent(NoneString), string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
+				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
 
 			for (int i = 0; i < animations.Count; i++) {
 			for (int i = 0; i < animations.Count; i++) {
 				string name = animations.Items[i].Name;
 				string name = animations.Items[i].Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
-					menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
 			}
 		}
 		}
 
 
@@ -301,7 +319,7 @@ namespace Spine.Unity.Editor {
 
 
 			if (includeNone) {
 			if (includeNone) {
 				eventNames.Add("");
 				eventNames.Add("");
-				menuItems.Add(new GUIContent(NoneString, SpineEditorUtilities.Icons.userEvent));
+				menuItems.Add(new GUIContent(NoneStringConstant, SpineEditorUtilities.Icons.userEvent));
 			}
 			}
 
 
 			foreach (var a in animations) {
 			foreach (var a in animations) {
@@ -315,12 +333,12 @@ namespace Spine.Unity.Editor {
 			var events = skeletonDataAsset.GetSkeletonData(false).Events;
 			var events = skeletonDataAsset.GetSkeletonData(false).Events;
 
 
 			if (TargetAttribute.includeNone)
 			if (TargetAttribute.includeNone)
-				menu.AddItem(new GUIContent(NoneString), string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
+				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
 
 			for (int i = 0; i < events.Count; i++) {
 			for (int i = 0; i < events.Count; i++) {
 				string name = events.Items[i].Name;
 				string name = events.Items[i].Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
-					menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
 			}
 		}
 		}
 
 
@@ -335,12 +353,12 @@ namespace Spine.Unity.Editor {
 			var constraints = skeletonDataAsset.GetSkeletonData(false).IkConstraints;
 			var constraints = skeletonDataAsset.GetSkeletonData(false).IkConstraints;
 
 
 			if (TargetAttribute.includeNone)
 			if (TargetAttribute.includeNone)
-				menu.AddItem(new GUIContent(NoneString), string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
+				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
 
 			for (int i = 0; i < constraints.Count; i++) {
 			for (int i = 0; i < constraints.Count; i++) {
 				string name = constraints.Items[i].Name;
 				string name = constraints.Items[i].Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
-					menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
 			}
 		}
 		}
 
 
@@ -355,12 +373,12 @@ namespace Spine.Unity.Editor {
 			var constraints = skeletonDataAsset.GetSkeletonData(false).TransformConstraints;
 			var constraints = skeletonDataAsset.GetSkeletonData(false).TransformConstraints;
 
 
 			if (TargetAttribute.includeNone)
 			if (TargetAttribute.includeNone)
-				menu.AddItem(new GUIContent(NoneString), string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
+				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
 
 			for (int i = 0; i < constraints.Count; i++) {
 			for (int i = 0; i < constraints.Count; i++) {
 				string name = constraints.Items[i].Name;
 				string name = constraints.Items[i].Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
-					menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
 			}
 		}
 		}
 	}
 	}
@@ -374,12 +392,12 @@ namespace Spine.Unity.Editor {
 			var constraints = skeletonDataAsset.GetSkeletonData(false).PathConstraints;
 			var constraints = skeletonDataAsset.GetSkeletonData(false).PathConstraints;
 
 
 			if (TargetAttribute.includeNone)
 			if (TargetAttribute.includeNone)
-				menu.AddItem(new GUIContent(NoneString), string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
+				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
 
 			for (int i = 0; i < constraints.Count; i++) {
 			for (int i = 0; i < constraints.Count; i++) {
 				string name = constraints.Items[i].Name;
 				string name = constraints.Items[i].Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
-					menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
 			}
 		}
 		}
 	}
 	}
@@ -422,7 +440,7 @@ namespace Spine.Unity.Editor {
 			menu.AddSeparator("");
 			menu.AddSeparator("");
 			if (TargetAttribute.includeNone) {
 			if (TargetAttribute.includeNone) {
 				const string NullAttachmentName = "";
 				const string NullAttachmentName = "";
-				menu.AddItem(new GUIContent("Null"), property.stringValue == NullAttachmentName, HandleSelect, new SpineDrawerValuePair(NullAttachmentName, property));
+				menu.AddItem(new GUIContent("Null"), !property.hasMultipleDifferentValues && property.stringValue == NullAttachmentName, HandleSelect, new SpineDrawerValuePair(NullAttachmentName, property));
 				menu.AddSeparator("");
 				menu.AddSeparator("");
 			}
 			}
 
 
@@ -465,7 +483,7 @@ namespace Spine.Unity.Editor {
 						if (targetAttribute.placeholdersOnly && !placeholderNames.Contains(attachmentPath)) {
 						if (targetAttribute.placeholdersOnly && !placeholderNames.Contains(attachmentPath)) {
 							menu.AddDisabledItem(new GUIContent(menuPath));
 							menu.AddDisabledItem(new GUIContent(menuPath));
 						} else {
 						} else {
-							menu.AddItem(new GUIContent(menuPath), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+							menu.AddItem(new GUIContent(menuPath), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 						}
 						}
 					}
 					}
 
 
@@ -485,12 +503,12 @@ namespace Spine.Unity.Editor {
 			menu.AddSeparator("");
 			menu.AddSeparator("");
 
 
 			if (TargetAttribute.includeNone)
 			if (TargetAttribute.includeNone)
-				menu.AddItem(new GUIContent(NoneString), string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
+				menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property));
 
 
 			for (int i = 0; i < data.Bones.Count; i++) {
 			for (int i = 0; i < data.Bones.Count; i++) {
 				string name = data.Bones.Items[i].Name;
 				string name = data.Bones.Items[i].Name;
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
 				if (name.StartsWith(targetAttribute.startsWith, StringComparison.Ordinal))
-					menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+					menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
 			}
 		}
 		}
 
 
@@ -540,7 +558,7 @@ namespace Spine.Unity.Editor {
 
 
 			for (int i = 0; i < regions.Count; i++) {
 			for (int i = 0; i < regions.Count; i++) {
 				string name = regions[i].name;
 				string name = regions[i].name;
-				menu.AddItem(new GUIContent(name), name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
+				menu.AddItem(new GUIContent(name), !property.hasMultipleDifferentValues && name == property.stringValue, HandleSelect, new SpineDrawerValuePair(name, property));
 			}
 			}
 
 
 			menu.ShowAsContext();
 			menu.ShowAsContext();
@@ -548,7 +566,7 @@ namespace Spine.Unity.Editor {
 
 
 		static void HandleSelect (object val) {
 		static void HandleSelect (object val) {
 			var pair = (SpineDrawerValuePair)val;
 			var pair = (SpineDrawerValuePair)val;
-			pair.property.stringValue = pair.str;
+			pair.property.stringValue = pair.stringValue;
 			pair.property.serializedObject.ApplyModifiedProperties();
 			pair.property.serializedObject.ApplyModifiedProperties();
 		}
 		}
 
 

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

@@ -1202,7 +1202,7 @@ namespace Spine.Unity.Editor {
 				var data = skeletonDataAsset.GetSkeletonData(false);
 				var data = skeletonDataAsset.GetSkeletonData(false);
 				bool noSkins = data.DefaultSkin == null && (data.Skins == null || data.Skins.Count == 0); // Support attachmentless/skinless SkeletonData.
 				bool noSkins = data.DefaultSkin == null && (data.Skins == null || data.Skins.Count == 0); // Support attachmentless/skinless SkeletonData.
 				skin = skin ?? data.DefaultSkin ?? (noSkins ? null : data.Skins.Items[0]);
 				skin = skin ?? data.DefaultSkin ?? (noSkins ? null : data.Skins.Items[0]);
-				if (skin != null) {
+				if (skin != null && skin != data.DefaultSkin) {
 					skeletonRenderer.initialSkinName = skin.Name;
 					skeletonRenderer.initialSkinName = skin.Name;
 				}
 				}
 			}
 			}

+ 3 - 2
spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineInspectorUtility.cs

@@ -165,8 +165,9 @@ namespace Spine.Unity.Editor {
 		public static GUIStyle GrayMiniLabel {
 		public static GUIStyle GrayMiniLabel {
 			get {
 			get {
 				if (grayMiniLabel == null) {
 				if (grayMiniLabel == null) {
-					grayMiniLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
-					grayMiniLabel.alignment = TextAnchor.UpperLeft;
+					grayMiniLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel) {
+						alignment = TextAnchor.UpperLeft
+					};
 				}
 				}
 				return grayMiniLabel;
 				return grayMiniLabel;
 			}
 			}

+ 1 - 0
spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs

@@ -48,6 +48,7 @@ namespace Spine.Unity {
 
 
 		public SkeletonDataAsset skeletonDataAsset;
 		public SkeletonDataAsset skeletonDataAsset;
 		public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
 		public SkeletonDataAsset SkeletonDataAsset { get { return skeletonDataAsset; } } // ISkeletonComponent
+		[SpineSkin(defaultAsEmptyString:true)]
 		public string initialSkinName;
 		public string initialSkinName;
 		public bool initialFlipX, initialFlipY;
 		public bool initialFlipX, initialFlipY;
 
 

+ 7 - 1
spine-unity/Assets/Spine/Runtime/spine-unity/SpineAttributes.cs

@@ -149,17 +149,23 @@ namespace Spine.Unity {
 		/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
 		/// <param name="startsWith">Filters popup results to elements that begin with supplied string.</param>
 		/// <param name = "includeNone">If true, the dropdown list will include a "none" option which stored as an empty string.</param>
 		/// <param name = "includeNone">If true, the dropdown list will include a "none" option which stored as an empty string.</param>
 		/// <param name = "fallbackToTextField">If true, and an animation list source can't be found, the field will fall back to a normal text field. If false, it will show an error.</param>
 		/// <param name = "fallbackToTextField">If true, and an animation list source can't be found, the field will fall back to a normal text field. If false, it will show an error.</param>
+		/// <param name = "defaultAsEmptyString">If true, the default choice will be serialized as an empty 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.
 		/// <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)
 		/// 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.
 		/// If left empty and the script the attribute is applied to is derived from Component, GetComponent<SkeletonRenderer>() will be called as a fallback.
 		/// </param>
 		/// </param>
-		public SpineSkin (string startsWith = "", string dataField = "", bool includeNone = true, bool fallbackToTextField = false) {
+
+		public bool defaultAsEmptyString = false;
+
+		public SpineSkin (string startsWith = "", string dataField = "", bool includeNone = true, bool fallbackToTextField = false, bool defaultAsEmptyString = false) {
 			this.startsWith = startsWith;
 			this.startsWith = startsWith;
 			this.dataField = dataField;
 			this.dataField = dataField;
 			this.includeNone = includeNone;
 			this.includeNone = includeNone;
 			this.fallbackToTextField = fallbackToTextField;
 			this.fallbackToTextField = fallbackToTextField;
+			this.defaultAsEmptyString = defaultAsEmptyString;
 		}
 		}
 	}
 	}
+
 	public class SpineAnimation : SpineAttributeBase {
 	public class SpineAnimation : SpineAttributeBase {
 		/// <summary>
 		/// <summary>
 		/// Smart popup menu for Spine Animations
 		/// Smart popup menu for Spine Animations