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 dd10b61e8..c885cb67d 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 @@ -625,9 +625,12 @@ namespace Spine.Unity.Editor { warnings.Add("Missing Skeleton JSON"); } else { var fieldValue = (TextAsset)skeletonJSON.objectReferenceValue; - - if (!AssetUtility.IsSpineData(fieldValue, out compatibilityProblemInfo)) { - warnings.Add("Skeleton data file is not a valid Spine JSON or binary file."); + string problemDescription = null; + if (!AssetUtility.IsSpineData(fieldValue, out compatibilityProblemInfo, ref problemDescription)) { + if (problemDescription != null) + warnings.Add(problemDescription); + else + warnings.Add("Skeleton data file is not a valid Spine JSON or binary file."); } else { #if SPINE_TK2D bool searchForSpineAtlasAssets = (compatibilityProblemInfo == null); @@ -644,11 +647,12 @@ namespace Spine.Unity.Editor { var actualAtlasAssets = targetSkeletonDataAsset.atlasAssets; for (int i = 0; i < actualAtlasAssets.Length; i++) { - if (targetSkeletonDataAsset.atlasAssets[i] == null) { + if (actualAtlasAssets[i] == null) { detectedNullAtlasEntry = true; break; } else { - atlasList.Add(actualAtlasAssets[i].GetAtlas()); + if (actualAtlasAssets[i].MaterialCount > 0) + atlasList.Add(actualAtlasAssets[i].GetAtlas()); } } @@ -658,8 +662,9 @@ namespace Spine.Unity.Editor { List missingPaths = null; if (atlasAssets.arraySize > 0) { missingPaths = AssetUtility.GetRequiredAtlasRegions(AssetDatabase.GetAssetPath(skeletonJSON.objectReferenceValue)); - foreach (var atlas in atlasList) { + if (atlas == null) + continue; for (int i = 0; i < missingPaths.Count; i++) { if (atlas.FindRegion(missingPaths[i]) != null) { missingPaths.RemoveAt(i); diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs index 15de6daac..8ae548b3a 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/AssetUtility.cs @@ -58,7 +58,18 @@ using System.Reflection; using CompatibilityProblemInfo = Spine.Unity.SkeletonDataCompatibility.CompatibilityProblemInfo; namespace Spine.Unity.Editor { - using PathAndProblemInfo = System.Collections.Generic.KeyValuePair; + + public class PathAndProblemInfo { + public string path; + public CompatibilityProblemInfo compatibilityProblems; + public string otherProblemDescription; + + public PathAndProblemInfo (string path, CompatibilityProblemInfo compatibilityInfo, string otherProblemDescription) { + this.path = path; + this.compatibilityProblems = compatibilityInfo; + this.otherProblemDescription = otherProblemDescription; + } + } public static class AssetUtility { @@ -276,17 +287,26 @@ namespace Spine.Unity.Editor { case ".jpg": imagePaths.Add(str); break; - case ".json": + case ".json": { var jsonAsset = AssetDatabase.LoadAssetAtPath(str); - if (jsonAsset != null && IsSpineData(jsonAsset, out compatibilityProblemInfo)) - skeletonPaths.Add(new PathAndProblemInfo(str, compatibilityProblemInfo)); + string problemDescription = null; + if (jsonAsset != null && IsSpineData(jsonAsset, out compatibilityProblemInfo, ref problemDescription)) + skeletonPaths.Add(new PathAndProblemInfo(str, compatibilityProblemInfo, problemDescription)); + if (problemDescription != null) + Debug.LogError(problemDescription, jsonAsset); break; - case ".bytes": + } + case ".bytes": { if (str.ToLower().EndsWith(".skel.bytes", System.StringComparison.Ordinal)) { - if (IsSpineData(AssetDatabase.LoadAssetAtPath(str), out compatibilityProblemInfo)) - skeletonPaths.Add(new PathAndProblemInfo(str, compatibilityProblemInfo)); + var binaryAsset = AssetDatabase.LoadAssetAtPath(str); + string problemDescription = null; + if (IsSpineData(binaryAsset, out compatibilityProblemInfo, ref problemDescription)) + skeletonPaths.Add(new PathAndProblemInfo(str, compatibilityProblemInfo, problemDescription)); + if (problemDescription != null) + Debug.LogError(problemDescription, binaryAsset); } break; + } } } @@ -304,8 +324,9 @@ namespace Spine.Unity.Editor { // Import skeletons and match them with atlases. bool abortSkeletonImport = false; foreach (var skeletonPathEntry in skeletonPaths) { - string skeletonPath = skeletonPathEntry.Key; - var compatibilityProblems = skeletonPathEntry.Value; + string skeletonPath = skeletonPathEntry.path; + var compatibilityProblems = skeletonPathEntry.compatibilityProblems; + string otherProblemDescription = skeletonPathEntry.otherProblemDescription; if (skeletonPath.StartsWith("Packages")) continue; if (!reimport && CheckForValidSkeletonData(skeletonPath)) { @@ -318,6 +339,9 @@ namespace Spine.Unity.Editor { IngestIncompatibleSpineProject(loadedAsset, compatibilityProblems); continue; } + if (otherProblemDescription != null) { + continue; + } string dir = Path.GetDirectoryName(skeletonPath).Replace('\\', '/'); @@ -378,8 +402,12 @@ namespace Spine.Unity.Editor { if (usedSkeletonPath == null) continue; - if (skeletonPaths.FindIndex(p => { return p.Key == usedSkeletonPath; } ) < 0) { - skeletonPaths.Add(new PathAndProblemInfo(usedSkeletonPath, null)); + if (skeletonPaths.FindIndex(p => { return p.path == usedSkeletonPath; } ) < 0) { + string problemDescription = null; + CompatibilityProblemInfo compatibilityProblemInfo = null; + TextAsset textAsset = AssetDatabase.LoadAssetAtPath(usedSkeletonPath); + if (textAsset != null && IsSpineData(textAsset, out compatibilityProblemInfo, ref problemDescription)) + skeletonPaths.Add(new PathAndProblemInfo(usedSkeletonPath, compatibilityProblemInfo, problemDescription)); } } } @@ -427,7 +455,8 @@ namespace Spine.Unity.Editor { } SkeletonData skeletonData = skeletonDataAsset.GetSkeletonData(true); - BlendModeMaterialsUtility.UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData); + if (skeletonData != null) + BlendModeMaterialsUtility.UpdateBlendModeMaterials(skeletonDataAsset, ref skeletonData); string currentHash = skeletonData != null ? skeletonData.Hash : null; @@ -831,11 +860,10 @@ namespace Spine.Unity.Editor { internal static SkeletonDataAsset IngestIncompatibleSpineProject(TextAsset spineJson, CompatibilityProblemInfo compatibilityProblemInfo) { - string filePath = GetSkeletonDataAssetFilePath(spineJson); - if (spineJson == null) return null; + string filePath = GetSkeletonDataAssetFilePath(spineJson); SkeletonDataAsset skeletonDataAsset = (SkeletonDataAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(SkeletonDataAsset)); if (skeletonDataAsset == null) { skeletonDataAsset = SkeletonDataAsset.CreateInstance(); @@ -921,14 +949,14 @@ namespace Spine.Unity.Editor { if (skeletonDataAsset != null && skeletonDataAsset.skeletonJSON == textAsset) return true; } - return false; } - public static bool IsSpineData (TextAsset asset, out CompatibilityProblemInfo compatibilityProblemInfo) { - SkeletonDataCompatibility.VersionInfo fileVersion = SkeletonDataCompatibility.GetVersionInfo(asset); + public static bool IsSpineData (TextAsset asset, out CompatibilityProblemInfo compatibilityProblemInfo, ref string problemDescription) { + bool isSpineSkeletonData; + SkeletonDataCompatibility.VersionInfo fileVersion = SkeletonDataCompatibility.GetVersionInfo(asset, out isSpineSkeletonData, ref problemDescription); compatibilityProblemInfo = SkeletonDataCompatibility.GetCompatibilityProblemInfo(fileVersion); - return fileVersion != null; + return isSpineSkeletonData; } #endregion 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 769cc319f..746d2a4a3 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 @@ -163,22 +163,29 @@ namespace Spine.Unity { } #endif - bool isBinary = skeletonJSON.name.ToLower().Contains(".skel"); + bool hasBinaryExtension = skeletonJSON.name.ToLower().Contains(".skel"); SkeletonData loadedSkeletonData = null; try { - if (isBinary) + if (hasBinaryExtension) loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.bytes, attachmentLoader, skeletonDataScale); else loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.text, attachmentLoader, skeletonDataScale); } catch (Exception ex) { if (!quiet) - Debug.LogError("Error reading skeleton JSON file for SkeletonData asset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this); + Debug.LogError("Error reading skeleton JSON file for SkeletonData asset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, skeletonJSON); } #if UNITY_EDITOR if (loadedSkeletonData == null && !quiet && skeletonJSON != null) { - SkeletonDataCompatibility.VersionInfo fileVersion = SkeletonDataCompatibility.GetVersionInfo(skeletonJSON); + string problemDescription = null; + bool isSpineSkeletonData; + SkeletonDataCompatibility.VersionInfo fileVersion = SkeletonDataCompatibility.GetVersionInfo(skeletonJSON, out isSpineSkeletonData, ref problemDescription); + if (problemDescription != null) { + if (!quiet) + Debug.LogError(problemDescription, skeletonJSON); + return null; + } CompatibilityProblemInfo compatibilityProblemInfo = SkeletonDataCompatibility.GetCompatibilityProblemInfo(fileVersion); if (compatibilityProblemInfo != null) { SkeletonDataCompatibility.DisplayCompatibilityProblem(compatibilityProblemInfo.DescriptionString(), skeletonJSON); @@ -250,7 +257,6 @@ namespace Spine.Unity { }; return json.ReadSkeletonData(input); } - } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataCompatibility.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataCompatibility.cs index 545ba0d27..4eaf95062 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataCompatibility.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataCompatibility.cs @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.IO; using UnityEngine; +using System; #if UNITY_EDITOR using System.Globalization; using System.Text.RegularExpressions; @@ -63,8 +64,12 @@ namespace Spine.Unity { public class CompatibilityProblemInfo { public VersionInfo actualVersion; public int[][] compatibleVersions; + public string explicitProblemDescription = null; public string DescriptionString () { + if (!string.IsNullOrEmpty(explicitProblemDescription)) + return explicitProblemDescription; + string compatibleVersionString = ""; string optionalOr = null; foreach (int[] version in compatibleVersions) { @@ -77,12 +82,28 @@ namespace Spine.Unity { } #if UNITY_EDITOR - public static VersionInfo GetVersionInfo (TextAsset asset) { + public static VersionInfo GetVersionInfo (TextAsset asset, out bool isSpineSkeletonData, ref string problemDescription) { + isSpineSkeletonData = false; if (asset == null) return null; VersionInfo fileVersion = new VersionInfo(); - fileVersion.sourceType = asset.name.Contains(".skel") ? SourceType.Binary : SourceType.Json; + bool hasBinaryExtension = asset.name.Contains(".skel"); + fileVersion.sourceType = hasBinaryExtension ? SourceType.Binary : SourceType.Json; + + bool isJsonFileByContent = IsJsonFile(asset); + if (hasBinaryExtension == isJsonFileByContent) { + if (hasBinaryExtension) { + problemDescription = string.Format("Failed to read '{0}'. Extension is '.skel.bytes' but content looks like a '.json' file.\n" + + "Did you choose the wrong extension upon export?\n", asset.name); + } + else { + problemDescription = string.Format("Failed to read '{0}'. Extension is '.json' but content looks like binary 'skel.bytes' file.\n" + + "Did you choose the wrong extension upon export?\n", asset.name); + } + isSpineSkeletonData = false; + return null; + } if (fileVersion.sourceType == SourceType.Binary) { try { @@ -91,8 +112,8 @@ namespace Spine.Unity { } } catch (System.Exception e) { - Debug.LogError(string.Format("Failed to read '{0}'. It is likely not a binary Spine SkeletonData file.\n{1}", - asset.name, e), asset); + problemDescription = string.Format("Failed to read '{0}'. It is likely not a binary Spine SkeletonData file.\n{1}", asset.name, e); + isSpineSkeletonData = false; return null; } } @@ -104,13 +125,15 @@ namespace Spine.Unity { else { object obj = Json.Deserialize(new StringReader(asset.text)); if (obj == null) { - Debug.LogError(string.Format("'{0}' is not valid JSON.", asset.name), asset); + problemDescription = string.Format("'{0}' is not valid JSON.", asset.name); + isSpineSkeletonData = false; return null; } var root = obj as Dictionary; if (root == null) { - Debug.LogError(string.Format("'{0}' is not compatible JSON. Parser returned an incorrect type while parsing version info.", asset.name), asset); + problemDescription = string.Format("'{0}' is not compatible JSON. Parser returned an incorrect type while parsing version info.", asset.name); + isSpineSkeletonData = false; return null; } @@ -124,7 +147,8 @@ namespace Spine.Unity { } if (string.IsNullOrEmpty(fileVersion.rawVersion)) { - // very likely not a Spine skeleton json file at all. + // very likely not a Spine skeleton json file at all. Could be another valid json file, don't report errors. + isSpineSkeletonData = false; return null; } @@ -134,16 +158,39 @@ namespace Spine.Unity { int.Parse(versionSplit[1], CultureInfo.InvariantCulture) }; } catch (System.Exception e) { - Debug.LogError(string.Format("Failed to read version info at skeleton '{0}'. It is likely not a valid Spine SkeletonData file.\n{1}", - asset.name, e), asset); + problemDescription = string.Format("Failed to read version info at skeleton '{0}'. It is likely not a valid Spine SkeletonData file.\n{1}", asset.name, e); + isSpineSkeletonData = false; return null; } + isSpineSkeletonData = true; return fileVersion; } + public static bool IsJsonFile (TextAsset file) { + string fileText = file.text; + const int maxCharsToCheck = 256; + int numCharsToCheck = Math.Min(fileText.Length, maxCharsToCheck); + if (fileText.IndexOf("\"skeleton\"", 0, numCharsToCheck) != -1 || + fileText.IndexOf("\"hash\"", 0, numCharsToCheck) != -1 || + fileText.IndexOf("\"spine\"", 0, numCharsToCheck) != -1) + return true; + + int jsonCharCount = 0; + const string jsonChars = "{}:\","; + for (int i = 0; i < numCharsToCheck; ++i) { + char c = fileText[i]; + if (jsonChars.IndexOf(c) != -1 || char.IsWhiteSpace(c)) + ++jsonCharCount; + } + if (jsonCharCount > numCharsToCheck / 10) + return true; + return false; + } + public static CompatibilityProblemInfo GetCompatibilityProblemInfo (VersionInfo fileVersion) { - if (fileVersion == null) - return null; + if (fileVersion == null) { + return null; // it's most likely not a Spine skeleton file, e.g. another json file. don't report problems. + } CompatibilityProblemInfo info = new CompatibilityProblemInfo(); info.actualVersion = fileVersion; diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs index 6dff61cb4..46dcfe76f 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonMecanim.cs @@ -142,6 +142,9 @@ namespace Spine.Unity { [System.Serializable] public class MecanimTranslator { + + const float WeightEpsilon = 0.0001f; + #region Inspector public bool autoReset = true; public bool useCustomMixMode = true; @@ -220,7 +223,7 @@ namespace Spine.Unity { private bool ApplyAnimation (Skeleton skeleton, AnimatorClipInfo info, AnimatorStateInfo stateInfo, int layerIndex, float layerWeight, MixBlend layerBlendMode, bool useClipWeight1 = false) { float weight = info.weight * layerWeight; - if (weight == 0) + if (weight < WeightEpsilon) return false; var clip = GetAnimation(info.clip); @@ -244,7 +247,7 @@ namespace Spine.Unity { float clipWeight = interpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; float weight = clipWeight * layerWeight; - if (weight == 0) + if (weight < WeightEpsilon) return false; var clip = GetAnimation(info.clip); @@ -320,7 +323,7 @@ namespace Spine.Unity { for (int c = 0; c < clipInfoCount; c++) { var info = clipInfo[c]; - float weight = info.weight * layerWeight; if (weight == 0) continue; + float weight = info.weight * layerWeight; if (weight < WeightEpsilon) continue; var clip = GetAnimation(info.clip); if (clip != null) previousAnimations.Add(clip); @@ -329,7 +332,7 @@ namespace Spine.Unity { if (hasNext) { for (int c = 0; c < nextClipInfoCount; c++) { var info = nextClipInfo[c]; - float weight = info.weight * layerWeight; if (weight == 0) continue; + float weight = info.weight * layerWeight; if (weight < WeightEpsilon) continue; var clip = GetAnimation(info.clip); if (clip != null) previousAnimations.Add(clip); @@ -341,7 +344,7 @@ namespace Spine.Unity { { var info = interruptingClipInfo[c]; float clipWeight = shallInterpolateWeightTo1 ? (info.weight + 1.0f) * 0.5f : info.weight; - float weight = clipWeight * layerWeight; if (weight == 0) continue; + float weight = clipWeight * layerWeight; if (weight < WeightEpsilon) continue; var clip = GetAnimation(info.clip); if (clip != null) previousAnimations.Add(clip); diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs index c68f50e65..fc29fab9e 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs @@ -363,7 +363,7 @@ namespace Spine.Unity { #if UNITY_EDITOR if (!Application.isPlaying) { string errorMessage = null; - if (quiet || MaterialChecks.IsMaterialSetupProblematic(this, ref errorMessage)) + if (!quiet && MaterialChecks.IsMaterialSetupProblematic(this, ref errorMessage)) Debug.LogWarningFormat(this, "Problematic material setup at {0}: {1}", this.name, errorMessage); } #endif