diff --git a/spine-c/src/spine/Skeleton.c b/spine-c/src/spine/Skeleton.c index a193d2bf7..4af6db685 100644 --- a/spine-c/src/spine/Skeleton.c +++ b/spine-c/src/spine/Skeleton.c @@ -188,7 +188,8 @@ static void _sortPathConstraintAttachmentBones(_spSkeleton* const internal, spAt int i = 0; while (i < pathBonesCount) { int boneCount = pathBones[i++]; - for (int n = i + boneCount; i < n; i++) + int n; + for (n = i + boneCount; i < n; i++) _sortBone(internal, bones[pathBones[i]]); } } diff --git a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java index 3b3d451a1..98e398107 100644 --- a/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java +++ b/spine-libgdx/spine-libgdx/src/com/esotericsoftware/spine/Bone.java @@ -153,28 +153,25 @@ public class Bone implements Updatable { break; } case noRotationOrReflection: { - float psx = (float)Math.sqrt(pa * pa + pc * pc), psy, prx; - if (psx > 0.0001f) { - psy = Math.abs((pa * pd - pb * pc) / psx); + float s = pa * pa + pc * pc, prx; + if (s > 0.0001f) { + s = Math.abs(pa * pd - pb * pc) / s; + pb = pc * s; + pd = pa * s; prx = atan2(pc, pa) * radDeg; } else { - psx = 0; - psy = (float)Math.sqrt(pb * pb + pd * pd); + pa = 0; + pc = 0; prx = 90 - atan2(pd, pb) * radDeg; } - float cos = cosDeg(prx), sin = sinDeg(prx); - pa = cos * psx; - pb = -sin * psy; - pc = sin * psx; - pd = cos * psy; float rx = rotation + shearX - prx; float ry = rotation + shearY - prx + 90; float la = cosDeg(rx) * scaleX; float lb = cosDeg(ry) * scaleY; float lc = sinDeg(rx) * scaleX; float ld = sinDeg(ry) * scaleY; - a = pa * la + pb * lc; - b = pa * lb + pb * ld; + a = pa * la - pb * lc; + b = pa * lb - pb * ld; c = pc * la + pd * lc; d = pc * lb + pd * ld; break; diff --git a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java index 63cadcfe2..34a8c6245 100644 --- a/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java +++ b/spine-libgdx/spine-skeletonviewer/src/com/esotericsoftware/spine/SkeletonViewer.java @@ -430,7 +430,7 @@ public class SkeletonViewer extends ApplicationAdapter { stage.addActor(window); { - Table table = new Table(skin); + Table table = new Table(); table.setFillParent(true); table.setTouchable(Touchable.disabled); stage.addActor(table); @@ -438,6 +438,16 @@ public class SkeletonViewer extends ApplicationAdapter { table.add(toasts); } + { + Table table = new Table(); + table.setFillParent(true); + table.setTouchable(Touchable.disabled); + stage.addActor(table); + table.pad(10).top().right(); + table.add(new Label("", skin, "default", Color.LIGHT_GRAY)); // Version. + } + + // Events. window.addListener(new InputListener() { public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) { event.cancel(); diff --git a/spine-lua/SkeletonJson.lua b/spine-lua/SkeletonJson.lua index 697fec741..16c2cf10c 100644 --- a/spine-lua/SkeletonJson.lua +++ b/spine-lua/SkeletonJson.lua @@ -563,7 +563,7 @@ function SkeletonJson.new (attachmentLoader) end local frameIndex = 0 for i,valueMap in ipairs(values) do - timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1) getValue(valueMap, "shearMix", 1)) + timeline:setFrame(frameIndex, valueMap.time, getValue(valueMap, "rotateMix", 1), getValue(valueMap, "translateMix", 1), getValue(valueMap, "scaleMix", 1), getValue(valueMap, "shearMix", 1)) readCurve(valueMap, timeline, frameIndex) frameIndex = frameIndex + 1 end diff --git a/spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs b/spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs index 07daab329..e66986163 100644 --- a/spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs +++ b/spine-unity/Assets/spine-unity/Asset Types/AtlasAsset.cs @@ -38,14 +38,14 @@ namespace Spine.Unity { public class AtlasAsset : ScriptableObject { public TextAsset atlasFile; public Material[] materials; - private Atlas atlas; + protected Atlas atlas; - public void Reset () { + public virtual void Reset () { atlas = null; } /// The atlas or null if it could not be loaded. - public Atlas GetAtlas () { + public virtual Atlas GetAtlas () { if (atlasFile == null) { Debug.LogError("Atlas file not set for atlas asset: " + name, this); Reset(); diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs index 6c5faac14..f79c67ed6 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimationInspector.cs @@ -35,9 +35,11 @@ using Spine; namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonAnimation))] + [CanEditMultipleObjects] public class SkeletonAnimationInspector : SkeletonRendererInspector { protected SerializedProperty animationName, loop, timeScale, autoReset; protected bool wasAnimationNameChanged; + protected bool requireRepaint; protected override void OnEnable () { base.OnEnable(); @@ -46,64 +48,93 @@ namespace Spine.Unity.Editor { timeScale = serializedObject.FindProperty("timeScale"); } - protected override void DrawInspectorGUI () { - base.DrawInspectorGUI(); + protected override void DrawInspectorGUI (bool multi) { + base.DrawInspectorGUI(multi); + if (!TargetIsValid) return; + bool sameData = SpineInspectorUtility.TargetsUseSameData(serializedObject); - SkeletonAnimation component = (SkeletonAnimation)target; - if (!component.valid) + // Try to reflect the animation name on the scene object. + { + if (multi) + foreach (var o in targets) + TrySetAnimation(o); + else + TrySetAnimation(target); + } + + EditorGUILayout.Space(); + + if (multi && !sameData) + EditorGUILayout.DelayedTextField(animationName); + else { + EditorGUI.BeginChangeCheck(); + EditorGUILayout.PropertyField(animationName); + wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update. + } + + EditorGUILayout.PropertyField(loop); + + EditorGUILayout.PropertyField(timeScale); + if (multi) { + foreach (var o in targets) { + var component = o as SkeletonAnimation; + component.timeScale = Mathf.Max(component.timeScale, 0); + } + } else { + var component = (SkeletonAnimation)target; + component.timeScale = Mathf.Max(component.timeScale, 0); + } + + if (!isInspectingPrefab) { + if (requireRepaint) { + SceneView.RepaintAll(); + requireRepaint = false; + } + + DrawSkeletonUtilityButton(multi); + } + } + + protected void TrySetAnimation (Object o) { + var skeletonAnimation = o as SkeletonAnimation; + if (skeletonAnimation == null) return; + if (!skeletonAnimation.valid) return; if (!isInspectingPrefab) { if (wasAnimationNameChanged) { if (!Application.isPlaying) { - if (component.state != null) component.state.ClearTrack(0); - component.skeleton.SetToSetupPose(); + if (skeletonAnimation.state != null) skeletonAnimation.state.ClearTrack(0); + skeletonAnimation.skeleton.SetToSetupPose(); } - Spine.Animation animationToUse = component.skeleton.Data.FindAnimation(animationName.stringValue); + Spine.Animation animationToUse = skeletonAnimation.skeleton.Data.FindAnimation(animationName.stringValue); if (!Application.isPlaying) { - if (animationToUse != null) animationToUse.Apply(component.skeleton, 0f, 0f, false, null); - component.Update(); - component.LateUpdate(); - SceneView.RepaintAll(); + if (animationToUse != null) animationToUse.Apply(skeletonAnimation.skeleton, 0f, 0f, false, null); + skeletonAnimation.Update(); + skeletonAnimation.LateUpdate(); + requireRepaint = true; } else { if (animationToUse != null) - component.state.SetAnimation(0, animationToUse, loop.boolValue); + skeletonAnimation.state.SetAnimation(0, animationToUse, loop.boolValue); else - component.state.ClearTrack(0); + skeletonAnimation.state.ClearTrack(0); } wasAnimationNameChanged = false; } // Reflect animationName serialized property in the inspector even if SetAnimation API was used. - if (Application.isPlaying) { - TrackEntry current = component.state.GetCurrent(0); + bool multi = animationName.serializedObject.isEditingMultipleObjects; + if (!multi && Application.isPlaying) { + TrackEntry current = skeletonAnimation.state.GetCurrent(0); if (current != null) { - if (component.AnimationName != animationName.stringValue) + if (skeletonAnimation.AnimationName != animationName.stringValue) animationName.stringValue = current.Animation.Name; } } } - - EditorGUILayout.Space(); - EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(animationName); - wasAnimationNameChanged |= EditorGUI.EndChangeCheck(); // Value used in the next update. - - EditorGUILayout.PropertyField(loop); - EditorGUILayout.PropertyField(timeScale); - component.timeScale = Mathf.Max(component.timeScale, 0); - - EditorGUILayout.Space(); - - if (!isInspectingPrefab) { - if (component.GetComponent() == null) { - if (GUILayout.Button(new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility), GUILayout.Height(30))) - component.gameObject.AddComponent(); - } - } } } } diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs index 369249e10..8204cd800 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonAnimatorInspector.cs @@ -31,10 +31,10 @@ // Contributed by: Mitch Thompson using UnityEditor; -using UnityEngine; namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonAnimator))] + [CanEditMultipleObjects] public class SkeletonAnimatorInspector : SkeletonRendererInspector { protected SerializedProperty layerMixModes; protected override void OnEnable () { @@ -42,22 +42,14 @@ namespace Spine.Unity.Editor { layerMixModes = serializedObject.FindProperty("layerMixModes"); } - protected override void DrawInspectorGUI () { - base.DrawInspectorGUI(); + protected override void DrawInspectorGUI (bool multi) { + base.DrawInspectorGUI(multi); EditorGUILayout.PropertyField(layerMixModes, true); - var component = (SkeletonAnimator)target; - if (!component.valid) - return; - EditorGUILayout.Space(); + if (!TargetIsValid) return; - if (!isInspectingPrefab) { - if (component.GetComponent() == null) { - if (GUILayout.Button(new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility), GUILayout.Height(30))) { - component.gameObject.AddComponent(); - } - } - } + if (!isInspectingPrefab) + DrawSkeletonUtilityButton(multi); } } } diff --git a/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs b/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs index 37d81745f..f106870d3 100644 --- a/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs +++ b/spine-unity/Assets/spine-unity/Editor/SkeletonRendererInspector.cs @@ -31,17 +31,34 @@ #define NO_PREFAB_MESH using UnityEditor; +using System.Collections.Generic; using UnityEngine; namespace Spine.Unity.Editor { [CustomEditor(typeof(SkeletonRenderer))] + [CanEditMultipleObjects] public class SkeletonRendererInspector : UnityEditor.Editor { protected static bool advancedFoldout; protected SerializedProperty skeletonDataAsset, initialSkinName, normals, tangents, meshes, immutableTriangles, separatorSlotNames, frontFacing, zSpacing, pmaVertexColors; protected SpineInspectorUtility.SerializedSortingProperties sortingProperties; protected bool isInspectingPrefab; - protected MeshFilter meshFilter; + + protected bool TargetIsValid { + get { + if (serializedObject.isEditingMultipleObjects) { + foreach (var o in targets) { + var component = (SkeletonRenderer)o; + if (!component.valid) + return false; + } + return true; + } else { + var component = (SkeletonRenderer)target; + return component.valid; + } + } + } protected virtual void OnEnable () { isInspectingPrefab = (PrefabUtility.GetPrefabType(target) == PrefabType.Prefab); @@ -60,8 +77,8 @@ namespace Spine.Unity.Editor { frontFacing = serializedObject.FindProperty("frontFacing"); zSpacing = serializedObject.FindProperty("zSpacing"); - var renderer = ((SkeletonRenderer)target).GetComponent(); - sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(renderer); + SerializedObject rso = SpineInspectorUtility.GetRenderersSerializedObject(serializedObject); + sortingProperties = new SpineInspectorUtility.SerializedSortingProperties(rso); } public static void ReapplySeparatorSlotNames (SkeletonRenderer skeletonRenderer) { @@ -76,62 +93,105 @@ namespace Spine.Unity.Editor { var slot = skeleton.FindSlot(separatorSlotNames[i]); if (slot != null) { separatorSlots.Add(slot); - //Debug.Log(slot + " added as separator."); } else { Debug.LogWarning(separatorSlotNames[i] + " is not a slot in " + skeletonRenderer.skeletonDataAsset.skeletonJSON.name); } } - - //Debug.Log("Reapplied Separator Slot Names. Count is now: " + separatorSlots.Count); } - protected virtual void DrawInspectorGUI () { - // JOHN: todo: support multiediting. - SkeletonRenderer component = (SkeletonRenderer)target; - - using (new EditorGUILayout.HorizontalScope()) { - EditorGUILayout.PropertyField(skeletonDataAsset); - const string ReloadButtonLabel = "Reload"; - float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20; - if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) { - if (component.skeletonDataAsset != null) { - foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { - if (aa != null) - aa.Reset(); + protected virtual void DrawInspectorGUI (bool multi) { + bool valid = TargetIsValid; + if (multi) { + using (new EditorGUILayout.HorizontalScope()) { + EditorGUILayout.PropertyField(skeletonDataAsset); + const string ReloadButtonLabel = "Reload"; + float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20; + if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) { + foreach (var c in targets) { + var component = c as SkeletonRenderer; + if (component.skeletonDataAsset != null) { + foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { + if (aa != null) + aa.Reset(); + } + component.skeletonDataAsset.Reset(); + } + component.Initialize(true); } - component.skeletonDataAsset.Reset(); } + } + + foreach (var c in targets) { + var component = c as SkeletonRenderer; + if (!component.valid) { + component.Initialize(true); + component.LateUpdate(); + if (!component.valid) + continue; + } + + #if NO_PREFAB_MESH + if (isInspectingPrefab) { + MeshFilter meshFilter = component.GetComponent(); + if (meshFilter != null) + meshFilter.sharedMesh = null; + } + #endif + } + + if (valid) + EditorGUILayout.PropertyField(initialSkinName); + } else { + var component = (SkeletonRenderer)target; + + using (new EditorGUILayout.HorizontalScope()) { + EditorGUILayout.PropertyField(skeletonDataAsset); + if (valid) { + const string ReloadButtonLabel = "Reload"; + float reloadWidth = GUI.skin.label.CalcSize(new GUIContent(ReloadButtonLabel)).x + 20; + if (GUILayout.Button(ReloadButtonLabel, GUILayout.Width(reloadWidth))) { + if (component.skeletonDataAsset != null) { + foreach (AtlasAsset aa in component.skeletonDataAsset.atlasAssets) { + if (aa != null) + aa.Reset(); + } + component.skeletonDataAsset.Reset(); + } + component.Initialize(true); + } + } + } + + if (!component.valid) { component.Initialize(true); + component.LateUpdate(); + if (!component.valid) { + EditorGUILayout.HelpBox("Skeleton Data Asset required", MessageType.Warning); + return; + } } - } - if (!component.valid) { - component.Initialize(true); - component.LateUpdate(); - if (!component.valid) - return; - } - - #if NO_PREFAB_MESH - if (meshFilter == null) - meshFilter = component.GetComponent(); - - if (isInspectingPrefab) - meshFilter.sharedMesh = null; - #endif - - // Initial skin name. - { - string[] skins = new string[component.skeleton.Data.Skins.Count]; - int skinIndex = 0; - for (int i = 0; i < skins.Length; i++) { - string skinNameString = component.skeleton.Data.Skins.Items[i].Name; - skins[i] = skinNameString; - if (skinNameString == initialSkinName.stringValue) - skinIndex = i; + #if NO_PREFAB_MESH + if (isInspectingPrefab) { + MeshFilter meshFilter = component.GetComponent(); + if (meshFilter != null) + meshFilter.sharedMesh = null; + } + #endif + + // Initial skin name. + if (valid) { + string[] skins = new string[component.skeleton.Data.Skins.Count]; + int skinIndex = 0; + for (int i = 0; i < skins.Length; i++) { + string skinNameString = component.skeleton.Data.Skins.Items[i].Name; + skins[i] = skinNameString; + if (skinNameString == initialSkinName.stringValue) + skinIndex = i; + } + skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins); + initialSkinName.stringValue = skins[skinIndex]; } - skinIndex = EditorGUILayout.Popup("Initial Skin", skinIndex, skins); - initialSkinName.stringValue = skins[skinIndex]; } EditorGUILayout.Space(); @@ -139,6 +199,8 @@ namespace Spine.Unity.Editor { // Sorting Layers SpineInspectorUtility.SortingPropertyFields(sortingProperties, applyModifiedProperties: true); + if (!valid) return; + // More Render Options... using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { EditorGUI.indentLevel++; @@ -183,14 +245,47 @@ namespace Spine.Unity.Editor { } } + public void DrawSkeletonUtilityButton (bool multi) { + var buttonContent = new GUIContent("Add Skeleton Utility", SpineEditorUtilities.Icons.skeletonUtility); + if (multi) { + // Support multi-edit SkeletonUtility button. + // EditorGUILayout.Space(); + // bool addSkeletonUtility = GUILayout.Button(buttonContent, GUILayout.Height(30)); + // foreach (var t in targets) { + // var component = t as SkeletonAnimation; + // if (addSkeletonUtility && component.GetComponent() == null) + // component.gameObject.AddComponent(); + // } + } else { + EditorGUILayout.Space(); + var component = (SkeletonAnimation)target; + if (component.GetComponent() == null) { + if (GUILayout.Button(buttonContent, GUILayout.Height(30))) + component.gameObject.AddComponent(); + } + } + } + override public void OnInspectorGUI () { //serializedObject.Update(); - DrawInspectorGUI(); + bool multi = serializedObject.isEditingMultipleObjects; + DrawInspectorGUI(multi); if (serializedObject.ApplyModifiedProperties() || (UnityEngine.Event.current.type == EventType.ValidateCommand && UnityEngine.Event.current.commandName == "UndoRedoPerformed") ) { - if (!Application.isPlaying) - ((SkeletonRenderer)target).Initialize(true); + if (!Application.isPlaying) { + if (multi) { + foreach (var o in targets) { + var sr = o as SkeletonRenderer; + sr.Initialize(true); + } + } else { + ((SkeletonRenderer)target).Initialize(true); + } + + } + + } } diff --git a/spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs b/spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs index d773176e4..219488a91 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineAttributeDrawers.cs @@ -53,8 +53,11 @@ namespace Spine.Unity.Editor { internal const string NoneLabel = ""; protected T TargetAttribute { get { return (T)attribute; } } + protected SerializedProperty SerializedProperty { get; private set; } public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { + SerializedProperty = property; + if (property.propertyType != SerializedPropertyType.String) { EditorGUI.LabelField(position, "ERROR:", "May only apply to type string"); return; @@ -87,7 +90,7 @@ namespace Spine.Unity.Editor { position = EditorGUI.PrefixLabel(position, label); - var propertyStringValue = property.stringValue; + var propertyStringValue = (property.hasMultipleDifferentValues) ? SpineInspectorUtility.EmDash : property.stringValue; if (GUI.Button(position, string.IsNullOrEmpty(propertyStringValue) ? NoneLabel : propertyStringValue, EditorStyles.popup)) Selector(property); @@ -302,7 +305,6 @@ namespace Spine.Unity.Editor { [CustomPropertyDrawer(typeof(SpineBone))] public class SpineBoneDrawer : SpineTreeItemDrawerBase { - protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineBone targetAttribute, SkeletonData data) { menu.AddDisabledItem(new GUIContent(skeletonDataAsset.name)); menu.AddSeparator(""); diff --git a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs index ee46f4ab9..db41f7d4b 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineEditorUtilities.cs @@ -1162,7 +1162,7 @@ namespace Spine.Unity.Editor { var skeletonInfo = (Dictionary)root["skeleton"]; object jv; skeletonInfo.TryGetValue("spine", out jv); - string jsonVersion = (jv == null) ? (string)jv : null; + string jsonVersion = jv as string; if (!string.IsNullOrEmpty(jsonVersion)) { string[] jsonVersionSplit = jsonVersion.Split('.'); bool match = false; diff --git a/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs b/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs index 6839d7a37..d96e5bd1c 100644 --- a/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs +++ b/spine-unity/Assets/spine-unity/Editor/SpineInspectorUtility.cs @@ -30,6 +30,7 @@ using UnityEngine; using UnityEditor; +using System.Collections.Generic; using System.Reflection; namespace Spine.Unity.Editor { @@ -43,6 +44,10 @@ namespace Spine.Unity.Editor { return n == 1 ? "" : "s"; } + public static string EmDash { + get { return "\u2014"; } + } + public static void PropertyFieldWideLabel (SerializedProperty property, GUIContent label = null, float minimumLabelWidth = 150) { using (new EditorGUILayout.HorizontalScope()) { GUILayout.Label(label ?? new GUIContent(property.displayName, property.tooltip), GUILayout.MinWidth(minimumLabelWidth)); @@ -70,25 +75,72 @@ namespace Spine.Unity.Editor { public SerializedProperty sortingLayerID; public SerializedProperty sortingOrder; - public SerializedSortingProperties (Renderer r) { - renderer = new SerializedObject(r); + public SerializedSortingProperties (Renderer r) : this(new SerializedObject(r)) {} + public SerializedSortingProperties (Object[] renderers) : this(new SerializedObject(renderers)) {} + public SerializedSortingProperties (SerializedObject rendererSerializedObject) { + renderer = rendererSerializedObject; sortingLayerID = renderer.FindProperty("m_SortingLayerID"); sortingOrder = renderer.FindProperty("m_SortingOrder"); } public void ApplyModifiedProperties () { renderer.ApplyModifiedProperties(); + this.SetDirty(); } + + internal void SetDirty () { + if (renderer.isEditingMultipleObjects) + foreach (var o in renderer.targetObjects) + EditorUtility.SetDirty(o); + else + EditorUtility.SetDirty(renderer.targetObject); + } + } + + public static SerializedObject GetRenderersSerializedObject (SerializedObject serializedObject) { + if (serializedObject.isEditingMultipleObjects) { + var renderers = new List(); + foreach (var o in serializedObject.targetObjects) { + var component = o as Component; + if (component != null) { + var renderer = component.GetComponent(); + if (renderer != null) + renderers.Add(renderer); + } + } + return new SerializedObject(renderers.ToArray()); + } else { + var component = serializedObject.targetObject as Component; + if (component != null) { + var renderer = component.GetComponent(); + if (renderer != null) + return new SerializedObject(renderer); + } + } + + return null; + } + + public static bool TargetsUseSameData (SerializedObject so) { + bool multi = so.isEditingMultipleObjects; + if (multi) { + int n = so.targetObjects.Length; + var first = so.targetObjects[0] as SkeletonRenderer; + for (int i = 1; i < n; i++) { + var sr = so.targetObjects[i] as SkeletonRenderer; + if (sr != null && sr.skeletonDataAsset != first.skeletonDataAsset) + return false; + } + } + return true; } public static void SortingPropertyFields (SerializedSortingProperties prop, bool applyModifiedProperties) { if (applyModifiedProperties) { EditorGUI.BeginChangeCheck(); SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder); - if(EditorGUI.EndChangeCheck()) { + if(EditorGUI.EndChangeCheck()) prop.ApplyModifiedProperties(); - EditorUtility.SetDirty(prop.renderer.targetObject); - } } else { SortingPropertyFields(prop.sortingLayerID, prop.sortingOrder); } diff --git a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs index c75a3e09e..2c096fa00 100644 --- a/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs +++ b/spine-unity/Assets/spine-unity/Modules/SkeletonGraphic/Editor/SkeletonGraphicInspector.cs @@ -40,6 +40,7 @@ namespace Spine.Unity.Editor { [InitializeOnLoad] [CustomEditor(typeof(SkeletonGraphic))] + [CanEditMultipleObjects] public class SkeletonGraphicInspector : UnityEditor.Editor { SerializedProperty material_, color_; SerializedProperty skeletonDataAsset_, initialSkinName_; diff --git a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs index daacc7fed..3b32920a8 100644 --- a/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs +++ b/spine-unity/Assets/spine-unity/SkeletonUtility/Editor/SkeletonUtilityInspector.cs @@ -114,6 +114,8 @@ namespace Spine.Unity.Editor { skeleton = skeletonRenderer.skeleton; } + if (!skeletonRenderer.valid) return; + UpdateAttachments(); isPrefab |= PrefabUtility.GetPrefabType(this.target) == PrefabType.Prefab; } @@ -142,7 +144,6 @@ namespace Spine.Unity.Editor { void UpdateAttachments () { attachmentTable = new Dictionary>(); - Skin skin = skeleton.Skin ?? skeletonRenderer.skeletonDataAsset.GetSkeletonData(true).DefaultSkin; for (int i = skeleton.Slots.Count-1; i >= 0; i--) { List attachments = new List(); @@ -176,20 +177,22 @@ namespace Spine.Unity.Editor { return; } + if (!skeletonRenderer.valid) { + GUILayout.Label(new GUIContent("Spine Component invalid. Check Skeleton Data Asset.", SpineEditorUtilities.Icons.warning)); + return; + } + skeletonUtility.boneRoot = (Transform)EditorGUILayout.ObjectField("Bone Root", skeletonUtility.boneRoot, typeof(Transform), true); - GUILayout.BeginHorizontal(); - EditorGUI.BeginDisabledGroup(skeletonUtility.boneRoot != null); - { - if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24))) - SpawnHierarchyContextMenu(); + using (new GUILayout.HorizontalScope()) { + using (new EditorGUI.DisabledGroupScope(skeletonUtility.boneRoot != null)) { + if (GUILayout.Button(new GUIContent("Spawn Hierarchy", SpineEditorUtilities.Icons.skeleton), GUILayout.Width(150), GUILayout.Height(24))) + SpawnHierarchyContextMenu(); + } + + // if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24))) + // skeletonUtility.SpawnSubRenderers(true); } - EditorGUI.EndDisabledGroup(); - - // if (GUILayout.Button(new GUIContent("Spawn Submeshes", SpineEditorUtilities.Icons.subMeshRenderer), GUILayout.Width(150), GUILayout.Height(24))) - // skeletonUtility.SpawnSubRenderers(true); - - GUILayout.EndHorizontal(); EditorGUI.BeginChangeCheck(); skeleton.FlipX = EditorGUILayout.ToggleLeft("Flip X", skeleton.FlipX);