|
@@ -0,0 +1,394 @@
|
|
|
+/******************************************************************************
|
|
|
+ * 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.
|
|
|
+ *****************************************************************************/
|
|
|
+
|
|
|
+#if UNITY_2018_2_OR_NEWER
|
|
|
+#define EXPOSES_SPRITE_ATLAS_UTILITIES
|
|
|
+#endif
|
|
|
+
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.IO;
|
|
|
+using UnityEngine;
|
|
|
+using Spine;
|
|
|
+using UnityEngine.U2D;
|
|
|
+
|
|
|
+#if UNITY_EDITOR
|
|
|
+using UnityEditor;
|
|
|
+using System.Reflection;
|
|
|
+#endif
|
|
|
+
|
|
|
+namespace Spine.Unity {
|
|
|
+ /// <summary>Loads and stores a Spine atlas and list of materials.</summary>
|
|
|
+ [CreateAssetMenu(fileName = "New Spine SpriteAtlas Asset", menuName = "Spine/Spine SpriteAtlas Asset")]
|
|
|
+ public class SpineSpriteAtlasAsset : AtlasAssetBase {
|
|
|
+ public SpriteAtlas spriteAtlasFile;
|
|
|
+ public Material[] materials;
|
|
|
+ protected Atlas atlas;
|
|
|
+ public bool updateRegionsInPlayMode;
|
|
|
+
|
|
|
+ [System.Serializable]
|
|
|
+ protected class SavedRegionInfo {
|
|
|
+ public float x, y, width, height;
|
|
|
+ public SpritePackingRotation packingRotation;
|
|
|
+ }
|
|
|
+ [SerializeField] protected SavedRegionInfo[] savedRegions;
|
|
|
+
|
|
|
+ public override bool IsLoaded { get { return this.atlas != null; } }
|
|
|
+
|
|
|
+ public override IEnumerable<Material> Materials { get { return materials; } }
|
|
|
+ public override int MaterialCount { get { return materials == null ? 0 : materials.Length; } }
|
|
|
+ public override Material PrimaryMaterial { get { return materials[0]; } }
|
|
|
+
|
|
|
+ #if UNITY_EDITOR
|
|
|
+ static MethodInfo GetPackedSpritesMethod, GetPreviewTexturesMethod;
|
|
|
+ #if !EXPOSES_SPRITE_ATLAS_UTILITIES
|
|
|
+ static MethodInfo PackAtlasesMethod;
|
|
|
+ #endif
|
|
|
+ #endif
|
|
|
+
|
|
|
+ #region Runtime Instantiation
|
|
|
+ /// <summary>
|
|
|
+ /// Creates a runtime AtlasAsset</summary>
|
|
|
+ public static SpineSpriteAtlasAsset CreateRuntimeInstance (SpriteAtlas spriteAtlasFile, Material[] materials, bool initialize) {
|
|
|
+ SpineSpriteAtlasAsset atlasAsset = ScriptableObject.CreateInstance<SpineSpriteAtlasAsset>();
|
|
|
+ atlasAsset.Reset();
|
|
|
+ atlasAsset.spriteAtlasFile = spriteAtlasFile;
|
|
|
+ atlasAsset.materials = materials;
|
|
|
+
|
|
|
+ if (initialize)
|
|
|
+ atlasAsset.GetAtlas();
|
|
|
+
|
|
|
+ return atlasAsset;
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ void Reset () {
|
|
|
+ Clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ public override void Clear () {
|
|
|
+ atlas = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <returns>The atlas or null if it could not be loaded.</returns>
|
|
|
+ public override Atlas GetAtlas () {
|
|
|
+ if (spriteAtlasFile == null) {
|
|
|
+ Debug.LogError("SpriteAtlas file not set for SpineSpriteAtlasAsset: " + name, this);
|
|
|
+ Clear();
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (materials == null || materials.Length == 0) {
|
|
|
+ Debug.LogError("Materials not set for SpineSpriteAtlasAsset: " + name, this);
|
|
|
+ Clear();
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (atlas != null) return atlas;
|
|
|
+
|
|
|
+ try {
|
|
|
+ atlas = LoadAtlas(spriteAtlasFile);
|
|
|
+ return atlas;
|
|
|
+ } catch (Exception ex) {
|
|
|
+ Debug.LogError("Error analyzing SpriteAtlas for SpineSpriteAtlasAsset: " + name + "\n" + ex.Message + "\n" + ex.StackTrace, this);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected void AssignRegionsFromSavedRegions (Sprite[] sprites, Atlas usedAtlas) {
|
|
|
+
|
|
|
+ if (savedRegions == null || savedRegions.Length != sprites.Length)
|
|
|
+ return;
|
|
|
+
|
|
|
+ int i = 0;
|
|
|
+ foreach (var region in usedAtlas) {
|
|
|
+ var savedRegion = savedRegions[i];
|
|
|
+ var page = region.page;
|
|
|
+
|
|
|
+ region.degrees = savedRegion.packingRotation == SpritePackingRotation.None ? 0 : 90;
|
|
|
+ region.rotate = region.degrees != 0;
|
|
|
+
|
|
|
+ float x = savedRegion.x;
|
|
|
+ float y = savedRegion.y;
|
|
|
+ float width = savedRegion.width;
|
|
|
+ float height = savedRegion.height;
|
|
|
+
|
|
|
+ region.u = x / (float)page.width;
|
|
|
+ region.v = y / (float)page.height;
|
|
|
+ if (region.rotate) {
|
|
|
+ region.u2 = (x + height) / (float)page.width;
|
|
|
+ region.v2 = (y + width) / (float)page.height;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ region.u2 = (x + width) / (float)page.width;
|
|
|
+ region.v2 = (y + height) / (float)page.height;
|
|
|
+ }
|
|
|
+ region.x = (int)x;
|
|
|
+ region.y = (int)y;
|
|
|
+ region.width = Math.Abs((int)width);
|
|
|
+ region.height = Math.Abs((int)height);
|
|
|
+
|
|
|
+ // flip upside down
|
|
|
+ var temp = region.v;
|
|
|
+ region.v = region.v2;
|
|
|
+ region.v2 = temp;
|
|
|
+
|
|
|
+ region.originalWidth = (int)width;
|
|
|
+ region.originalHeight = (int)height;
|
|
|
+
|
|
|
+ // note: currently sprite pivot offsets are ignored.
|
|
|
+ // var sprite = sprites[i];
|
|
|
+ region.offsetX = 0;//sprite.pivot.x;
|
|
|
+ region.offsetY = 0;//sprite.pivot.y;
|
|
|
+
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Atlas LoadAtlas (UnityEngine.U2D.SpriteAtlas spriteAtlas) {
|
|
|
+
|
|
|
+ List<AtlasPage> pages = new List<AtlasPage>();
|
|
|
+ List<AtlasRegion> regions = new List<AtlasRegion>();
|
|
|
+
|
|
|
+ Sprite[] sprites = new UnityEngine.Sprite[spriteAtlas.spriteCount];
|
|
|
+ spriteAtlas.GetSprites(sprites);
|
|
|
+ if (sprites.Length == 0)
|
|
|
+ return new Atlas(pages, regions);
|
|
|
+
|
|
|
+ Texture2D texture = null;
|
|
|
+ #if UNITY_EDITOR
|
|
|
+ if (!Application.isPlaying)
|
|
|
+ texture = AccessPackedTextureEditor(spriteAtlas);
|
|
|
+ else
|
|
|
+ #endif
|
|
|
+ texture = AccessPackedTexture(sprites);
|
|
|
+
|
|
|
+ Material material = materials[0];
|
|
|
+
|
|
|
+ Spine.AtlasPage page = new AtlasPage();
|
|
|
+ page.name = spriteAtlas.name;
|
|
|
+ page.width = texture.width;
|
|
|
+ page.height = texture.height;
|
|
|
+ page.format = Spine.Format.RGBA8888;
|
|
|
+
|
|
|
+ page.minFilter = TextureFilter.Linear;
|
|
|
+ page.magFilter = TextureFilter.Linear;
|
|
|
+ page.uWrap = TextureWrap.ClampToEdge;
|
|
|
+ page.vWrap = TextureWrap.ClampToEdge;
|
|
|
+ page.rendererObject = material;
|
|
|
+ pages.Add(page);
|
|
|
+
|
|
|
+ sprites = AccessPackedSprites(spriteAtlas);
|
|
|
+
|
|
|
+ int i = 0;
|
|
|
+ for ( ; i < sprites.Length; ++i) {
|
|
|
+ var sprite = sprites[i];
|
|
|
+ AtlasRegion region = new AtlasRegion();
|
|
|
+ region.name = sprite.name.Replace("(Clone)", "");
|
|
|
+ region.page = page;
|
|
|
+ region.degrees = sprite.packingRotation == SpritePackingRotation.None ? 0 : 90;
|
|
|
+ region.rotate = region.degrees != 0;
|
|
|
+
|
|
|
+ region.u2 = 1;
|
|
|
+ region.v2 = 1;
|
|
|
+ region.width = page.width;
|
|
|
+ region.height = page.height;
|
|
|
+ region.originalWidth = page.width;
|
|
|
+ region.originalHeight = page.height;
|
|
|
+
|
|
|
+ region.index = i;
|
|
|
+ regions.Add(region);
|
|
|
+ }
|
|
|
+
|
|
|
+ var atlas = new Atlas(pages, regions);
|
|
|
+ AssignRegionsFromSavedRegions(sprites, atlas);
|
|
|
+
|
|
|
+ return atlas;
|
|
|
+ }
|
|
|
+
|
|
|
+#if UNITY_EDITOR
|
|
|
+ public static void UpdateByStartingEditorPlayMode () {
|
|
|
+ EditorApplication.isPlaying = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool AnySpriteAtlasNeedsRegionsLoaded () {
|
|
|
+ string[] guids = UnityEditor.AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset");
|
|
|
+ foreach (var guid in guids) {
|
|
|
+ string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
|
|
+ if (!string.IsNullOrEmpty(path)) {
|
|
|
+ var atlasAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<SpineSpriteAtlasAsset>(path);
|
|
|
+ if (atlasAsset) {
|
|
|
+ if (atlasAsset.RegionsNeedLoading)
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void UpdateWhenEditorPlayModeStarted () {
|
|
|
+ if (!EditorApplication.isPlaying)
|
|
|
+ return;
|
|
|
+
|
|
|
+ EditorApplication.update -= UpdateWhenEditorPlayModeStarted;
|
|
|
+ string[] guids = UnityEditor.AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset");
|
|
|
+ if (guids.Length == 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ Debug.Log("Updating SpineSpriteAtlasAssets");
|
|
|
+ foreach (var guid in guids) {
|
|
|
+ string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
|
|
|
+ if (!string.IsNullOrEmpty(path)) {
|
|
|
+ var atlasAsset = UnityEditor.AssetDatabase.LoadAssetAtPath<SpineSpriteAtlasAsset>(path);
|
|
|
+ if (atlasAsset) {
|
|
|
+ atlasAsset.atlas = atlasAsset.LoadAtlas(atlasAsset.spriteAtlasFile);
|
|
|
+ atlasAsset.LoadRegionsInEditorPlayMode();
|
|
|
+ Debug.Log(string.Format("Updated regions of '{0}'", atlasAsset.name), atlasAsset);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ EditorApplication.isPlaying = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public bool RegionsNeedLoading {
|
|
|
+ get { return savedRegions == null || savedRegions.Length == 0 || updateRegionsInPlayMode; }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void LoadRegionsInEditorPlayMode () {
|
|
|
+
|
|
|
+ Sprite[] sprites = null;
|
|
|
+ System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
|
|
|
+ var method = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
+ if (method != null) {
|
|
|
+ object retval = method.Invoke(null, new object[] { spriteAtlasFile });
|
|
|
+ var spritesArray = retval as Sprite[];
|
|
|
+ if (spritesArray != null && spritesArray.Length > 0) {
|
|
|
+ sprites = spritesArray;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (sprites == null) {
|
|
|
+ sprites = new UnityEngine.Sprite[spriteAtlasFile.spriteCount];
|
|
|
+ spriteAtlasFile.GetSprites(sprites);
|
|
|
+ }
|
|
|
+ if (sprites.Length == 0) {
|
|
|
+ Debug.LogWarning(string.Format("SpriteAtlas '{0}' contains no sprites. Please make sure all assigned images are set to import type 'Sprite'.", spriteAtlasFile.name), spriteAtlasFile);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ else if (sprites[0].packingMode == SpritePackingMode.Tight) {
|
|
|
+ Debug.LogError(string.Format("SpriteAtlas '{0}': Tight packing is not supported. Please disable 'Tight Packing' in the SpriteAtlas Inspector.", spriteAtlasFile.name), spriteAtlasFile);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (savedRegions == null || savedRegions.Length != sprites.Length)
|
|
|
+ savedRegions = new SavedRegionInfo[sprites.Length];
|
|
|
+
|
|
|
+ int i = 0;
|
|
|
+ foreach (var region in atlas) {
|
|
|
+ var sprite = sprites[i];
|
|
|
+ var rect = sprite.textureRect;
|
|
|
+ float x = rect.min.x;
|
|
|
+ float y = rect.min.y;
|
|
|
+ float width = rect.width;
|
|
|
+ float height = rect.height;
|
|
|
+
|
|
|
+ var savedRegion = new SavedRegionInfo();
|
|
|
+ savedRegion.x = x;
|
|
|
+ savedRegion.y = y;
|
|
|
+ savedRegion.width = width;
|
|
|
+ savedRegion.height = height;
|
|
|
+ savedRegion.packingRotation = sprite.packingRotation;
|
|
|
+ savedRegions[i] = savedRegion;
|
|
|
+
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ updateRegionsInPlayMode = false;
|
|
|
+ AssignRegionsFromSavedRegions(sprites, atlas);
|
|
|
+ EditorUtility.SetDirty(this);
|
|
|
+ AssetDatabase.SaveAssets();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Texture2D AccessPackedTextureEditor (SpriteAtlas spriteAtlas) {
|
|
|
+ #if EXPOSES_SPRITE_ATLAS_UTILITIES
|
|
|
+ UnityEditor.U2D.SpriteAtlasUtility.PackAtlases(new SpriteAtlas[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget);
|
|
|
+ #else
|
|
|
+ /*if (PackAtlasesMethod == null) {
|
|
|
+ System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasUtility,UnityEditor");
|
|
|
+ PackAtlasesMethod = T.GetMethod("PackAtlases", BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
+ }
|
|
|
+ if (PackAtlasesMethod != null) {
|
|
|
+ PackAtlasesMethod.Invoke(null, new object[] { new SpriteAtlas[] { spriteAtlas }, EditorUserBuildSettings.activeBuildTarget });
|
|
|
+ }*/
|
|
|
+ #endif
|
|
|
+ if (GetPreviewTexturesMethod == null) {
|
|
|
+ System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
|
|
|
+ GetPreviewTexturesMethod = T.GetMethod("GetPreviewTextures", BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
+ }
|
|
|
+ if (GetPreviewTexturesMethod != null) {
|
|
|
+ object retval = GetPreviewTexturesMethod.Invoke(null, new object[] { spriteAtlas });
|
|
|
+ var textures = retval as Texture2D[];
|
|
|
+ if (textures.Length > 0)
|
|
|
+ return textures[0];
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ public static Texture2D AccessPackedTexture (Sprite[] sprites) {
|
|
|
+ return sprites[0].texture;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public static Sprite[] AccessPackedSprites (UnityEngine.U2D.SpriteAtlas spriteAtlas) {
|
|
|
+ Sprite[] sprites = null;
|
|
|
+#if UNITY_EDITOR
|
|
|
+ if (!Application.isPlaying) {
|
|
|
+
|
|
|
+ if (GetPackedSpritesMethod == null) {
|
|
|
+ System.Type T = Type.GetType("UnityEditor.U2D.SpriteAtlasExtensions,UnityEditor");
|
|
|
+ GetPackedSpritesMethod = T.GetMethod("GetPackedSprites", BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
+ }
|
|
|
+ if (GetPackedSpritesMethod != null) {
|
|
|
+ object retval = GetPackedSpritesMethod.Invoke(null, new object[] { spriteAtlas });
|
|
|
+ var spritesArray = retval as Sprite[];
|
|
|
+ if (spritesArray != null && spritesArray.Length > 0) {
|
|
|
+ sprites = spritesArray;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ if (sprites == null) {
|
|
|
+ sprites = new UnityEngine.Sprite[spriteAtlas.spriteCount];
|
|
|
+ spriteAtlas.GetSprites(sprites);
|
|
|
+ if (sprites.Length == 0)
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return sprites;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|