/****************************************************************************** * Spine Runtimes License Agreement * Last updated January 1, 2020. Replaces all prior versions. * * Copyright (c) 2013-2020, Esoteric Software LLC * * Integration of the Spine Runtimes into software or otherwise creating * derivative works of the Spine Runtimes is permitted under the terms and * conditions of Section 2 of the Spine Editor License Agreement: * http://esotericsoftware.com/spine-editor-license * * Otherwise, it is permitted to integrate the Spine Runtimes into software * or otherwise create derivative works of the Spine Runtimes (collectively, * "Products"), provided that each user of the Products must obtain their own * Spine Editor license and redistribution of the Products in any form must * include this license and copyright notice. * * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ using System; using System.Collections.Generic; using System.IO; using UnityEngine; using CompatibilityProblemInfo = Spine.Unity.SkeletonDataCompatibility.CompatibilityProblemInfo; namespace Spine.Unity { [CreateAssetMenu(fileName = "New SkeletonDataAsset", menuName = "Spine/SkeletonData Asset")] public class SkeletonDataAsset : ScriptableObject { #region Inspector public AtlasAssetBase[] atlasAssets = new AtlasAssetBase[0]; #if SPINE_TK2D public tk2dSpriteCollectionData spriteCollection; public float scale = 1f; #else public float scale = 0.01f; #endif public TextAsset skeletonJSON; public bool isUpgradingBlendModeMaterials = false; public BlendModeMaterials blendModeMaterials = new BlendModeMaterials(); [Tooltip("Use SkeletonDataModifierAssets to apply changes to the SkeletonData after being loaded, such as apply blend mode Materials to Attachments under slots with special blend modes.")] public List skeletonDataModifiers = new List(); [SpineAnimation(includeNone: false)] public string[] fromAnimation = new string[0]; [SpineAnimation(includeNone: false)] public string[] toAnimation = new string[0]; public float[] duration = new float[0]; public float defaultMix; public RuntimeAnimatorController controller; public bool IsLoaded { get { return this.skeletonData != null; } } void Reset () { Clear(); } #endregion SkeletonData skeletonData; AnimationStateData stateData; #region Runtime Instantiation /// /// Creates a runtime SkeletonDataAsset. public static SkeletonDataAsset CreateRuntimeInstance (TextAsset skeletonDataFile, AtlasAssetBase atlasAsset, bool initialize, float scale = 0.01f) { return CreateRuntimeInstance(skeletonDataFile, new [] {atlasAsset}, initialize, scale); } /// /// Creates a runtime SkeletonDataAsset. public static SkeletonDataAsset CreateRuntimeInstance (TextAsset skeletonDataFile, AtlasAssetBase[] atlasAssets, bool initialize, float scale = 0.01f) { SkeletonDataAsset skeletonDataAsset = ScriptableObject.CreateInstance(); skeletonDataAsset.Clear(); skeletonDataAsset.skeletonJSON = skeletonDataFile; skeletonDataAsset.atlasAssets = atlasAssets; skeletonDataAsset.scale = scale; if (initialize) skeletonDataAsset.GetSkeletonData(true); return skeletonDataAsset; } #endregion /// Clears the loaded SkeletonData and AnimationStateData. Use this to force a reload for the next time GetSkeletonData is called. public void Clear () { skeletonData = null; stateData = null; } public AnimationStateData GetAnimationStateData () { if (stateData != null) return stateData; GetSkeletonData(false); return stateData; } /// Loads, caches and returns the SkeletonData from the skeleton data file. Returns the cached SkeletonData after the first time it is called. Pass false to prevent direct errors from being logged. public SkeletonData GetSkeletonData (bool quiet) { if (skeletonJSON == null) { if (!quiet) Debug.LogError("Skeleton JSON file not set for SkeletonData asset: " + name, this); Clear(); return null; } // Disabled to support attachmentless/skinless SkeletonData. // if (atlasAssets == null) { // atlasAssets = new AtlasAsset[0]; // if (!quiet) // Debug.LogError("Atlas not set for SkeletonData asset: " + name, this); // Clear(); // return null; // } // #if !SPINE_TK2D // if (atlasAssets.Length == 0) { // Clear(); // return null; // } // #else // if (atlasAssets.Length == 0 && spriteCollection == null) { // Clear(); // return null; // } // #endif if (skeletonData != null) return skeletonData; AttachmentLoader attachmentLoader; float skeletonDataScale; Atlas[] atlasArray = this.GetAtlasArray(); #if !SPINE_TK2D attachmentLoader = (atlasArray.Length == 0) ? (AttachmentLoader)new RegionlessAttachmentLoader() : (AttachmentLoader)new AtlasAttachmentLoader(atlasArray); skeletonDataScale = scale; #else if (spriteCollection != null) { attachmentLoader = new Spine.Unity.TK2D.SpriteCollectionAttachmentLoader(spriteCollection); skeletonDataScale = (1.0f / (spriteCollection.invOrthoSize * spriteCollection.halfTargetHeight) * scale); } else { if (atlasArray.Length == 0) { Reset(); if (!quiet) Debug.LogError("Atlas not set for SkeletonData asset: " + name, this); return null; } attachmentLoader = new AtlasAttachmentLoader(atlasArray); skeletonDataScale = scale; } #endif bool hasBinaryExtension = skeletonJSON.name.ToLower().Contains(".skel"); SkeletonData loadedSkeletonData = null; try { 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, skeletonJSON); } #if UNITY_EDITOR if (loadedSkeletonData == null && !quiet && skeletonJSON != null) { 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); return null; } } #endif if (loadedSkeletonData == null) return null; if (skeletonDataModifiers != null) { foreach (var modifier in skeletonDataModifiers) { if (modifier != null && !(isUpgradingBlendModeMaterials && modifier is BlendModeMaterialsAsset)) { modifier.Apply(loadedSkeletonData); } } } if (!isUpgradingBlendModeMaterials) blendModeMaterials.ApplyMaterials(loadedSkeletonData); this.InitializeWithData(loadedSkeletonData); return skeletonData; } internal void InitializeWithData (SkeletonData sd) { this.skeletonData = sd; this.stateData = new AnimationStateData(skeletonData); FillStateData(); } public void FillStateData () { 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) continue; stateData.SetMix(fromAnimation[i], toAnimation[i], duration[i]); } } } internal Atlas[] GetAtlasArray () { var returnList = new System.Collections.Generic.List(atlasAssets.Length); for (int i = 0; i < atlasAssets.Length; i++) { var aa = atlasAssets[i]; if (aa == null) continue; var a = aa.GetAtlas(); if (a == null) continue; returnList.Add(a); } return returnList.ToArray(); } internal static SkeletonData ReadSkeletonData (byte[] bytes, AttachmentLoader attachmentLoader, float scale) { using (var input = new MemoryStream(bytes)) { var binary = new SkeletonBinary(attachmentLoader) { Scale = scale }; return binary.ReadSkeletonData(input); } } internal static SkeletonData ReadSkeletonData (string text, AttachmentLoader attachmentLoader, float scale) { var input = new StringReader(text); var json = new SkeletonJson(attachmentLoader) { Scale = scale }; return json.ReadSkeletonData(input); } } }