From f04a7b38ca4fb90f5ed5ddce47fafd8d1f221518 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Wed, 27 Apr 2022 20:47:24 +0200 Subject: [PATCH] [unity] Fixed exception after changing animation name with existing custom mix duration, closes #1874. Displaying animation/bone/etc name in red in Inspector fields when it no longer exists at the skeleton data. --- CHANGELOG.md | 1 + .../Asset Types/SkeletonDataAssetInspector.cs | 2 +- .../Editor/SpineAttributeDrawers.cs | 59 ++++++++++++++++++- .../Asset Types/SkeletonDataAsset.cs | 22 ++++++- 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc2f23ec..8888e77fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,7 @@ * Added another fadeout example component named `SkeletonRenderTextureFadeout` which takes over transparency fadeout when enabled. You can use this component as-is, attach it in disabled state and enable it to start a fadeout effect. * Timeline clips now offer an additional `Alpha` parameter for setting a custom constant mix alpha value other than 1.0, just as `TrackEntry.Alpha`. Defaults to 1.0. * `GetRemappedClone` copying from `Sprite` now provides additional `pmaCloneTextureFormat` and `pmaCloneMipmaps` parameters to explicitly specify the texture format of a newly created PMA texture. + * Spine property Inspector fields (`Animation Name`, `Bone Name`, `Slot` and similar) now display the name in red when the respective animation/bone/etc no longer exists at the skeleton data. This may be helpful when such items have been renamed or deleted. * **Breaking changes** diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs index bf3a76b56..0a1312791 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SkeletonDataAssetInspector.cs @@ -455,7 +455,7 @@ namespace Spine.Unity.Editor { } if (cc.changed) { - targetSkeletonDataAsset.FillStateData(); + targetSkeletonDataAsset.FillStateData(quiet: true); EditorUtility.SetDirty(targetSkeletonDataAsset); serializedObject.ApplyModifiedProperties(); } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs index 0a41cd479..7bfbbce03 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/SpineAttributeDrawers.cs @@ -61,11 +61,34 @@ namespace Spine.Unity.Editor { return noneLabel; } + static GUIStyle errorPopupStyle; + GUIStyle ErrorPopupStyle { + get { + if (errorPopupStyle == null) errorPopupStyle = new GUIStyle(EditorStyles.popup); + errorPopupStyle.normal.textColor = Color.red; + errorPopupStyle.hover.textColor = Color.red; + errorPopupStyle.focused.textColor = Color.red; + errorPopupStyle.active.textColor = Color.red; + return errorPopupStyle; + } + } + protected T TargetAttribute { get { return (T)attribute; } } protected SerializedProperty SerializedProperty { get; private set; } protected abstract Texture2D Icon { get; } + protected bool IsValueValid (SerializedProperty property) { + if (skeletonDataAsset != null) { + SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true); + if (skeletonData != null && !string.IsNullOrEmpty(property.stringValue)) + return IsValueValid(skeletonData, property); + } + return true; + } + + protected virtual bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { return true; } + public override void OnGUI (Rect position, SerializedProperty property, GUIContent label) { SerializedProperty = property; @@ -123,8 +146,10 @@ namespace Spine.Unity.Editor { position = EditorGUI.PrefixLabel(position, label); Texture2D image = Icon; + GUIStyle usedStyle = IsValueValid(property) ? EditorStyles.popup : ErrorPopupStyle; 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), usedStyle)) Selector(property); } @@ -174,6 +199,10 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.slot; } } + protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { + return skeletonData.FindSlot(property.stringValue) != null; + } + protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineSlot targetAttribute, SkeletonData data) { if (TargetAttribute.includeNone) menu.AddItem(new GUIContent(NoneString), !property.hasMultipleDifferentValues && string.IsNullOrEmpty(property.stringValue), HandleSelect, new SpineDrawerValuePair(string.Empty, property)); @@ -223,6 +252,10 @@ namespace Spine.Unity.Editor { internal override string NoneString { get { return TargetAttribute.defaultAsEmptyString ? DefaultSkinName : NoneStringConstant; } } + protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { + return skeletonData.FindSkin(property.stringValue) != null; + } + public static void GetSkinMenuItems (SkeletonData data, List outputNames, List outputMenuItems, bool includeNone = true) { if (data == null) return; if (outputNames == null) return; @@ -269,6 +302,10 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.animation; } } + protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { + return skeletonData.FindAnimation(property.stringValue) != null; + } + public static void GetAnimationMenuItems (SkeletonData data, List outputNames, List outputMenuItems, bool includeNone = true) { if (data == null) return; if (outputNames == null) return; @@ -311,6 +348,10 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.userEvent; } } + protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { + return skeletonData.FindEvent(property.stringValue) != null; + } + public static void GetEventMenuItems (SkeletonData data, List eventNames, List menuItems, bool includeNone = true) { if (data == null) return; @@ -356,6 +397,10 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintIK; } } + protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { + return skeletonData.FindIkConstraint(property.stringValue) != null; + } + protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineIkConstraint targetAttribute, SkeletonData data) { var constraints = skeletonDataAsset.GetSkeletonData(false).IkConstraints; @@ -376,6 +421,10 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintTransform; } } + protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { + return skeletonData.FindTransformConstraint(property.stringValue) != null; + } + protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpineTransformConstraint targetAttribute, SkeletonData data) { var constraints = skeletonDataAsset.GetSkeletonData(false).TransformConstraints; @@ -395,6 +444,10 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.constraintPath; } } + protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { + return skeletonData.FindPathConstraint(property.stringValue) != null; + } + protected override void PopulateMenu (GenericMenu menu, SerializedProperty property, SpinePathConstraint targetAttribute, SkeletonData data) { var constraints = skeletonDataAsset.GetSkeletonData(false).PathConstraints; @@ -516,6 +569,10 @@ namespace Spine.Unity.Editor { protected override Texture2D Icon { get { return SpineEditorUtilities.Icons.bone; } } + protected override bool IsValueValid (SkeletonData skeletonData, SerializedProperty property) { + return skeletonData.FindBone(property.stringValue) != null; + } + 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/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs index 1cbb76b15..778e50264 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs @@ -217,14 +217,30 @@ namespace Spine.Unity { FillStateData(); } - public void FillStateData () { + public void FillStateData (bool quiet = false) { if (stateData != null) { stateData.DefaultMix = defaultMix; for (int i = 0, n = fromAnimation.Length; i < n; i++) { - if (fromAnimation[i].Length == 0 || toAnimation[i].Length == 0) + string fromAnimationName = fromAnimation[i]; + string toAnimationName = toAnimation[i]; + if (fromAnimationName.Length == 0 || toAnimationName.Length == 0) continue; - stateData.SetMix(fromAnimation[i], toAnimation[i], duration[i]); +#if UNITY_EDITOR + if (skeletonData.FindAnimation(fromAnimationName) == null) { + if (!quiet) Debug.LogError( + string.Format("Custom Mix Durations: Animation '{0}' not found, was it renamed?", + fromAnimationName), this); + continue; + } + if (skeletonData.FindAnimation(toAnimationName) == null) { + if (!quiet) Debug.LogError( + string.Format("Custom Mix Durations: Animation '{0}' not found, was it renamed?", + toAnimationName), this); + continue; + } +#endif + stateData.SetMix(fromAnimationName, toAnimationName, duration[i]); } } }