Przeglądaj źródła

[unity] Improved multiedit for attributes. Updated inspectors.

pharan 7 lat temu
rodzic
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")]
 		static void AddBoneFollowerGameObject (MenuCommand cmd) {
 			var skeletonRenderer = cmd.context as SkeletonRenderer;
-			var go = new GameObject("BoneFollower");
+			var go = new GameObject("New BoneFollower");
 			var t = go.transform;
 			t.SetParent(skeletonRenderer.transform);
 			t.localPosition = Vector3.zero;
@@ -65,8 +65,20 @@ namespace Spine.Unity.Editor {
 			var skeletonRenderer = cmd.context as SkeletonRenderer;
 			return skeletonRenderer.valid;
 		}
+
+		[MenuItem("CONTEXT/BoneFollower/Rename BoneFollower GameObject")]
+		static void RenameGameObject (MenuCommand cmd) {
+			AutonameGameObject(cmd.context as BoneFollower);
+		}
 		#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 () {
 			skeletonRenderer = serializedObject.FindProperty("skeletonRenderer");
 			boneName = serializedObject.FindProperty("boneName");
@@ -195,7 +207,7 @@ namespace Spine.Unity.Editor {
 			bool missingRigidBody = (hasCollider2D && component.GetComponent<Rigidbody2D>() == null) || (hasCollider3D && component.GetComponent<Rigidbody>() == null);
 			if (missingRigidBody) {
 				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);
 					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.");

+ 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);
 
 			if (multi) {
-				foreach (var o in targets)		
-					TrySetAnimation(o, multi);
+				foreach (var o in targets)
+					TrySetAnimation(o as SkeletonAnimation, multi);
 				
 				EditorGUILayout.Space();
 				if (!sameData) {
-					#if UNITY_5_3_OR_NEWER
 					EditorGUILayout.DelayedTextField(animationName);
-					#else
-					animationName.stringValue = EditorGUILayout.TextField(animationName.displayName, animationName.stringValue);
-					#endif
 				} else {
 					EditorGUI.BeginChangeCheck();
 					EditorGUILayout.PropertyField(animationName);
@@ -78,7 +74,7 @@ namespace Spine.Unity.Editor {
 					component.timeScale = Mathf.Max(component.timeScale, 0);
 				}
 			} else {
-				TrySetAnimation(target, multi);
+				TrySetAnimation(target as SkeletonAnimation, multi);
 
 				EditorGUILayout.Space();
 				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.valid)
 				return;
 
 			if (!isInspectingPrefab) {
 				if (wasAnimationNameChanged) {
+					var skeleton = skeletonAnimation.Skeleton;
+					var state = skeletonAnimation.AnimationState;
+
 					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 (animationToUse != null) animationToUse.PoseSkeleton(skeletonAnimation.Skeleton, 0f);
-						skeletonAnimation.Update(0);
+						if (animationToUse != null) animationToUse.PoseSkeleton(skeleton, 0f);
+						skeleton.UpdateWorldTransform();
 						skeletonAnimation.LateUpdate();
 						requireRepaint = true;
 					} else {
 						if (animationToUse != null)
-							skeletonAnimation.state.SetAnimation(0, animationToUse, loop.boolValue);
+							state.SetAnimation(0, animationToUse, loop.boolValue);
 						else
-							skeletonAnimation.state.ClearTrack(0);
+							state.ClearTrack(0);
 					}
 
 					wasAnimationNameChanged = false;
@@ -131,7 +129,7 @@ namespace Spine.Unity.Editor {
 
 				// Reflect animationName serialized property in the inspector even if SetAnimation API was used.
 				if (!multi && Application.isPlaying) {
-					TrackEntry current = skeletonAnimation.state.GetCurrent(0);
+					TrackEntry current = skeletonAnimation.AnimationState.GetCurrent(0);
 					if (current != null) {
 						if (skeletonAnimation.AnimationName != animationName.stringValue)
 							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 initialFlipX, initialFlipY;
 		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 bool isInspectingPrefab;
+		protected bool forceReloadQueued = false;
 
 		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;
-		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 {
 			get {
@@ -76,13 +82,12 @@ namespace Spine.Unity.Editor {
 
 		protected virtual void OnEnable () {
 			isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab);
-			
+
 			SpineEditorUtilities.ConfirmInitialization();
 
 			// Labels
 			SkeletonDataAssetLabel = new GUIContent("SkeletonData Asset", Icons.spine);
 			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");
 			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.");
@@ -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.");
 			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.");
-			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;
 			skeletonDataAsset = so.FindProperty("skeletonDataAsset");
@@ -99,7 +104,6 @@ namespace Spine.Unity.Editor {
 			initialFlipY = so.FindProperty("initialFlipY");
 			normals = so.FindProperty("addNormals");
 			tangents = so.FindProperty("calculateTangents");
-			meshes = so.FindProperty("renderMeshes");
 			immutableTriangles = so.FindProperty("immutableTriangles");
 			pmaVertexColors = so.FindProperty("pmaVertexColors");
 			clearStateOnDisable = so.FindProperty("clearStateOnDisable");
@@ -111,80 +115,115 @@ namespace Spine.Unity.Editor {
 
 			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) {
 							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>();
 						if (meshFilter != 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)) {
 					SpineInspectorUtility.PropertyFieldFitLabel(skeletonDataAsset, SkeletonDataAssetLabel);
 					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;
 				}
 
-				#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();
@@ -232,8 +247,9 @@ namespace Spine.Unity.Editor {
 			// Sorting Layers
 			SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true);
 
-			if (!TargetIsValid) return;
-			
+			if (!valid)
+				return;
+
 			// More Render Options...
 			using (new SpineInspectorUtility.BoxScope()) {
 				EditorGUI.BeginChangeCheck();
@@ -250,11 +266,11 @@ namespace Spine.Unity.Editor {
 				EditorGUILayout.EndHorizontal();
 
 				if (advancedFoldout) {
-					
+
 					using (new SpineInspectorUtility.IndentScope()) {
 						using (new EditorGUILayout.HorizontalScope()) {
 							SpineInspectorUtility.ToggleLeftLayout(initialFlipX);
-							SpineInspectorUtility.ToggleLeftLayout(initialFlipY);							
+							SpineInspectorUtility.ToggleLeftLayout(initialFlipY);
 							EditorGUILayout.Space();
 						}
 
@@ -263,7 +279,6 @@ namespace Spine.Unity.Editor {
 						using (new SpineInspectorUtility.LabelWidthScope()) {
 							// Optimization options
 							if (singleSubmesh != null) EditorGUILayout.PropertyField(singleSubmesh, SingleSubmeshLabel);
-							//if (meshes != null) EditorGUILayout.PropertyField(meshes, MeshesLabel);
 							if (immutableTriangles != null) EditorGUILayout.PropertyField(immutableTriangles, ImmubleTrianglesLabel);
 							EditorGUILayout.PropertyField(clearStateOnDisable, ClearStateOnDisableLabel);
 							EditorGUILayout.Space();
@@ -290,7 +305,7 @@ namespace Spine.Unity.Editor {
 
 						EditorGUILayout.Space();
 
-						if (TargetIsValid && !isInspectingPrefab) {
+						if (valid && !isInspectingPrefab) {
 							if (multi) {
 								// Support multi-edit SkeletonUtility button.
 								//	EditorGUILayout.Space();
@@ -302,7 +317,7 @@ namespace Spine.Unity.Editor {
 								//	}
 							} else {
 								var component = (Component)target;
-								if (component.GetComponent<SkeletonUtility>() == null) {						
+								if (component.GetComponent<SkeletonUtility>() == null) {
 									if (SpineInspectorUtility.CenteredButton(SkeletonUtilityButtonContent, 21, true, 200f))
 										component.gameObject.AddComponent<SkeletonUtility>();
 								}
@@ -330,7 +345,7 @@ namespace Spine.Unity.Editor {
 				var sr = separatorSlotNames.serializedObject.targetObject as ISkeletonComponent;
 				var skeleton = sr.Skeleton;
 				int lastSlot = skeleton.Slots.Count - 1;
-				if (skeleton != null) {					
+				if (skeleton != null) {
 					for (int i = 0, n = separatorSlotNames.arraySize; i < n; i++) {
 						int index = skeleton.FindSlotIndex(separatorSlotNames.GetArrayElementAtIndex(i).stringValue);
 						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 {
 	public struct SpineDrawerValuePair {
-		public string str;
+		public string stringValue;
 		public SerializedProperty property;
 
 		public SpineDrawerValuePair (string val, SerializedProperty property) {
-			this.str = val;
+			this.stringValue = val;
 			this.property = property;
 		}
 	}
 
 	public abstract class SpineTreeItemDrawerBase<T> : PropertyDrawer where T:SpineAttributeBase {
 		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;
 			return noneLabel;
 		}
@@ -74,6 +74,12 @@ namespace Spine.Unity.Editor {
 				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);
 
 			if (dataField != null) {
@@ -116,8 +122,8 @@ namespace Spine.Unity.Editor {
 				
 			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))
 				Selector(property);
 		}
@@ -151,8 +157,10 @@ namespace Spine.Unity.Editor {
 
 		protected virtual void HandleSelect (object 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) {
@@ -167,9 +175,8 @@ namespace Spine.Unity.Editor {
 		protected override Texture2D Icon {	get { return SpineEditorUtilities.Icons.slot; } }
 
 		protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineSlot targetAttribute, SkeletonData data) {
-
 			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++) {
 				string name = data.Slots.Items[i].Name;
@@ -186,7 +193,7 @@ namespace Spine.Unity.Editor {
 							var bbAttachment = attachment as BoundingBoxAttachment;
 							if (bbAttachment != null) {
 								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;
 								break;
 							}
@@ -196,7 +203,7 @@ namespace Spine.Unity.Editor {
 							menu.AddDisabledItem(new GUIContent(name));
 
 					} 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))]
 	public class SpineSkinDrawer : SpineTreeItemDrawerBase<SpineSkin> {
+		const string DefaultSkinName = "default";
 
 		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 (outputNames == null) return;
+			if (outputMenuItems == null) return;
 
 			var skins = data.Skins;
 
-			animationNames.Clear();
-			menuItems.Clear();
+			outputNames.Clear();
+			outputMenuItems.Clear();
 
 			var icon = SpineEditorUtilities.Icons.skin;
 
 			if (includeNone) {
-				animationNames.Add("");
-				menuItems.Add(new GUIContent(NoneString, icon));
+				outputNames.Add("");
+				outputMenuItems.Add(new GUIContent(NoneStringConstant, icon));
 			}
 
 			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++) {
 				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; } }
 
-		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 (outputNames == null) return;
+			if (outputMenuItems == null) return;
 
 			var animations = data.Animations;
 
-			animationNames.Clear();
-			menuItems.Clear();
+			outputNames.Clear();
+			outputMenuItems.Clear();
 
 			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) {
-				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;
 
 			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++) {
 				string name = animations.Items[i].Name;
 				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) {
 				eventNames.Add("");
-				menuItems.Add(new GUIContent(NoneString, SpineEditorUtilities.Icons.userEvent));
+				menuItems.Add(new GUIContent(NoneStringConstant, SpineEditorUtilities.Icons.userEvent));
 			}
 
 			foreach (var a in animations) {
@@ -315,12 +333,12 @@ namespace Spine.Unity.Editor {
 			var events = skeletonDataAsset.GetSkeletonData(false).Events;
 
 			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++) {
 				string name = events.Items[i].Name;
 				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;
 
 			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++) {
 				string name = constraints.Items[i].Name;
 				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;
 
 			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++) {
 				string name = constraints.Items[i].Name;
 				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;
 
 			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++) {
 				string name = constraints.Items[i].Name;
 				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("");
 			if (TargetAttribute.includeNone) {
 				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("");
 			}
 
@@ -465,7 +483,7 @@ namespace Spine.Unity.Editor {
 						if (targetAttribute.placeholdersOnly && !placeholderNames.Contains(attachmentPath)) {
 							menu.AddDisabledItem(new GUIContent(menuPath));
 						} 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("");
 
 			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++) {
 				string name = data.Bones.Items[i].Name;
 				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++) {
 				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();
@@ -548,7 +566,7 @@ namespace Spine.Unity.Editor {
 
 		static void HandleSelect (object val) {
 			var pair = (SpineDrawerValuePair)val;
-			pair.property.stringValue = pair.str;
+			pair.property.stringValue = pair.stringValue;
 			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);
 				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]);
-				if (skin != null) {
+				if (skin != null && skin != data.DefaultSkin) {
 					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 {
 			get {
 				if (grayMiniLabel == null) {
-					grayMiniLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
-					grayMiniLabel.alignment = TextAnchor.UpperLeft;
+					grayMiniLabel = new GUIStyle(EditorStyles.centeredGreyMiniLabel) {
+						alignment = TextAnchor.UpperLeft
+					};
 				}
 				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 { get { return skeletonDataAsset; } } // ISkeletonComponent
+		[SpineSkin(defaultAsEmptyString:true)]
 		public string initialSkinName;
 		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 = "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 = "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.
 		/// 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 = "", 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.dataField = dataField;
 			this.includeNone = includeNone;
 			this.fallbackToTextField = fallbackToTextField;
+			this.defaultAsEmptyString = defaultAsEmptyString;
 		}
 	}
+
 	public class SpineAnimation : SpineAttributeBase {
 		/// <summary>
 		/// Smart popup menu for Spine Animations