/****************************************************************************** * Spine Runtimes License Agreement * Last updated July 28, 2023. Replaces all prior versions. * * Copyright (c) 2013-2023, 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_1_OR_NEWER #define HAS_BUILD_PROCESS_WITH_REPORT #endif #if UNITY_2021_2_OR_NEWER #define HAS_BUILD_PLAYER_PROCESSOR #endif #if UNITY_2020_2_OR_NEWER #define HAS_ON_POSTPROCESS_PREFAB #endif #if (UNITY_2020_3 && !(UNITY_2020_3_1 || UNITY_2020_3_2 || UNITY_2020_3_3 || UNITY_2020_3_4 || UNITY_2020_3_5 || UNITY_2020_3_6 || UNITY_2020_3_7 || UNITY_2020_3_8 || UNITY_2020_3_9 || UNITY_2020_3_10 || UNITY_2020_3_11 || UNITY_2020_3_12 || UNITY_2020_3_13 || UNITY_2020_3_14 || UNITY_2020_3_15)) #define UNITY_2020_3_16_OR_NEWER #endif #if (UNITY_2021_1 && !(UNITY_2021_1_1 || UNITY_2021_1_2 || UNITY_2021_1_3 || UNITY_2021_1_4 || UNITY_2021_1_5 || UNITY_2021_1_6 || UNITY_2021_1_7 || UNITY_2021_1_8 || UNITY_2021_1_9 || UNITY_2021_1_10 || UNITY_2021_1_11 || UNITY_2021_1_12 || UNITY_2021_1_13 || UNITY_2021_1_14 || UNITY_2021_1_15 || UNITY_2021_1_16)) #define UNITY_2021_1_17_OR_NEWER #endif #if UNITY_2020_3_16_OR_NEWER || UNITY_2021_1_17_OR_NEWER #define HAS_SAVE_ASSET_IF_DIRTY #endif #define SPINE_OPTIONAL_ON_DEMAND_LOADING using System.Collections.Generic; using UnityEditor; using UnityEditor.Build; using UnityEngine; #if HAS_BUILD_PROCESS_WITH_REPORT using UnityEditor.Build.Reporting; #endif namespace Spine.Unity.Editor { public class SpineBuildProcessor { internal static bool isBuilding = false; #if HAS_ON_POSTPROCESS_PREFAB static List prefabsToRestore = new List(); #endif #if SPINE_OPTIONAL_ON_DEMAND_LOADING static List textureLoadersToRestore = new List(); #endif static Dictionary spriteAtlasTexturesToRestore = new Dictionary(); internal static void PreprocessBuild () { isBuilding = true; #if HAS_ON_POSTPROCESS_PREFAB if (SpineEditorUtilities.Preferences.removePrefabPreviewMeshes) PreprocessSpinePrefabMeshes(); #endif #if SPINE_OPTIONAL_ON_DEMAND_LOADING PreprocessOnDemandTextureLoaders(); #endif PreprocessSpriteAtlases(); } internal static void PostprocessBuild () { isBuilding = false; #if HAS_ON_POSTPROCESS_PREFAB if (SpineEditorUtilities.Preferences.removePrefabPreviewMeshes) PostprocessSpinePrefabMeshes(); #endif #if SPINE_OPTIONAL_ON_DEMAND_LOADING PostprocessOnDemandTextureLoaders(); #endif PostprocessSpriteAtlases(); } #if HAS_ON_POSTPROCESS_PREFAB internal static void PreprocessSpinePrefabMeshes () { BuildUtilities.IsInSkeletonAssetBuildPreProcessing = true; try { AssetDatabase.StartAssetEditing(); prefabsToRestore.Clear(); string[] prefabAssets = AssetDatabase.FindAssets("t:Prefab"); foreach (string asset in prefabAssets) { string assetPath = AssetDatabase.GUIDToAssetPath(asset); if (!AssetUtility.AssetCanBeModified(assetPath)) continue; GameObject prefabGameObject = AssetDatabase.LoadAssetAtPath(assetPath); if (SpineEditorUtilities.CleanupSpinePrefabMesh(prefabGameObject)) { #if HAS_SAVE_ASSET_IF_DIRTY AssetDatabase.SaveAssetIfDirty(prefabGameObject); #endif prefabsToRestore.Add(assetPath); } } EditorUtility.UnloadUnusedAssetsImmediate(); AssetDatabase.StopAssetEditing(); #if !HAS_SAVE_ASSET_IF_DIRTY if (prefabAssets.Length > 0) AssetDatabase.SaveAssets(); #endif } finally { BuildUtilities.IsInSkeletonAssetBuildPreProcessing = false; } } internal static void PostprocessSpinePrefabMeshes () { BuildUtilities.IsInSkeletonAssetBuildPostProcessing = true; try { foreach (string assetPath in prefabsToRestore) { GameObject g = AssetDatabase.LoadAssetAtPath(assetPath); SpineEditorUtilities.SetupSpinePrefabMesh(g, null); #if HAS_SAVE_ASSET_IF_DIRTY AssetDatabase.SaveAssetIfDirty(g); #endif } #if !HAS_SAVE_ASSET_IF_DIRTY if (prefabsToRestore.Count > 0) AssetDatabase.SaveAssets(); #endif prefabsToRestore.Clear(); } finally { BuildUtilities.IsInSkeletonAssetBuildPostProcessing = false; } } #endif #if SPINE_OPTIONAL_ON_DEMAND_LOADING internal static void PreprocessOnDemandTextureLoaders () { BuildUtilities.IsInSkeletonAssetBuildPreProcessing = true; try { AssetDatabase.StartAssetEditing(); textureLoadersToRestore.Clear(); string[] loaderAssets = AssetDatabase.FindAssets("t:OnDemandTextureLoader"); foreach (string loaderAsset in loaderAssets) { string assetPath = AssetDatabase.GUIDToAssetPath(loaderAsset); OnDemandTextureLoader loader = AssetDatabase.LoadAssetAtPath(assetPath); bool isLoaderUsed = loader.atlasAsset && loader.atlasAsset.OnDemandTextureLoader == loader && loader.atlasAsset.TextureLoadingMode == AtlasAssetBase.LoadingMode.OnDemand; if (isLoaderUsed) { IEnumerable modifiedMaterials; textureLoadersToRestore.Add(assetPath); loader.AssignPlaceholderTextures(out modifiedMaterials); #if HAS_SAVE_ASSET_IF_DIRTY foreach (Material material in modifiedMaterials) { AssetDatabase.SaveAssetIfDirty(material); } #endif } } EditorUtility.UnloadUnusedAssetsImmediate(); AssetDatabase.StopAssetEditing(); #if !HAS_SAVE_ASSET_IF_DIRTY if (textureLoadersToRestore.Count > 0) AssetDatabase.SaveAssets(); #endif } finally { BuildUtilities.IsInSkeletonAssetBuildPreProcessing = false; } } internal static void PostprocessOnDemandTextureLoaders () { BuildUtilities.IsInSkeletonAssetBuildPostProcessing = true; try { foreach (string assetPath in textureLoadersToRestore) { OnDemandTextureLoader loader = AssetDatabase.LoadAssetAtPath(assetPath); IEnumerable modifiedMaterials; loader.AssignTargetTextures(out modifiedMaterials); #if HAS_SAVE_ASSET_IF_DIRTY foreach (Material material in modifiedMaterials) { AssetDatabase.SaveAssetIfDirty(material); } #endif } #if !HAS_SAVE_ASSET_IF_DIRTY if (textureLoadersToRestore.Count > 0) AssetDatabase.SaveAssets(); #endif textureLoadersToRestore.Clear(); } finally { BuildUtilities.IsInSkeletonAssetBuildPostProcessing = false; } } #endif internal static void PreprocessSpriteAtlases () { BuildUtilities.IsInSpriteAtlasBuildPreProcessing = true; try { AssetDatabase.StartAssetEditing(); spriteAtlasTexturesToRestore.Clear(); string[] spriteAtlasAssets = AssetDatabase.FindAssets("t:SpineSpriteAtlasAsset"); foreach (string asset in spriteAtlasAssets) { string assetPath = AssetDatabase.GUIDToAssetPath(asset); if (!AssetUtility.AssetCanBeModified(assetPath)) continue; SpineSpriteAtlasAsset atlasAsset = AssetDatabase.LoadAssetAtPath(assetPath); if (atlasAsset && atlasAsset.materials.Length > 0) { spriteAtlasTexturesToRestore[assetPath] = AssetDatabase.GetAssetPath(atlasAsset.materials[0].mainTexture); atlasAsset.materials[0].mainTexture = null; } #if HAS_SAVE_ASSET_IF_DIRTY AssetDatabase.SaveAssetIfDirty(atlasAsset); #endif } EditorUtility.UnloadUnusedAssetsImmediate(); AssetDatabase.StopAssetEditing(); #if !HAS_SAVE_ASSET_IF_DIRTY if (spriteAtlasAssets.Length > 0) AssetDatabase.SaveAssets(); #endif } finally { BuildUtilities.IsInSpriteAtlasBuildPreProcessing = false; } } internal static void PostprocessSpriteAtlases () { BuildUtilities.IsInSpriteAtlasBuildPostProcessing = true; try { foreach (KeyValuePair pair in spriteAtlasTexturesToRestore) { string assetPath = pair.Key; SpineSpriteAtlasAsset atlasAsset = AssetDatabase.LoadAssetAtPath(assetPath); if (atlasAsset && atlasAsset.materials.Length > 0) { Texture atlasTexture = AssetDatabase.LoadAssetAtPath(pair.Value); atlasAsset.materials[0].mainTexture = atlasTexture; } #if HAS_SAVE_ASSET_IF_DIRTY AssetDatabase.SaveAssetIfDirty(atlasAsset); #endif } #if !HAS_SAVE_ASSET_IF_DIRTY if (spriteAtlasTexturesToRestore.Count > 0) AssetDatabase.SaveAssets(); #endif spriteAtlasTexturesToRestore.Clear(); } finally { BuildUtilities.IsInSpriteAtlasBuildPostProcessing = false; } } } #if HAS_BUILD_PLAYER_PROCESSOR /// /// Build Preprocessor for Unity 2021.2 and newer. /// Unfortunately BuildPlayerProcessors seem to be executed before IPreprocessBuildWithReport regardless of /// callbackOrder, thus requiring use of this base class to call pre-build hooks before Addressables or /// Asset Bundles are built. /// public class SpineBuildPreprocessor : UnityEditor.Build.BuildPlayerProcessor { public override int callbackOrder { get { return -2000; } } public override void PrepareForBuild (BuildPlayerContext buildPlayerContext) { SpineBuildProcessor.PreprocessBuild(); } } #elif HAS_BUILD_PROCESS_WITH_REPORT public class SpineBuildPreprocessor : IPreprocessBuildWithReport { public int callbackOrder { get { return -2000; } } void IPreprocessBuildWithReport.OnPreprocessBuild (BuildReport report) { SpineBuildProcessor.PreprocessBuild(); } } #else public class SpineBuildPreprocessor : IPreprocessBuild { public int callbackOrder { get { return -2000; } } void IPreprocessBuild.OnPreprocessBuild (BuildTarget target, string path) { SpineBuildProcessor.PreprocessBuild(); } } #endif public class SpineBuildPostprocessor : #if HAS_BUILD_PROCESS_WITH_REPORT IPostprocessBuildWithReport #else IPostprocessBuild #endif { public int callbackOrder { get { return 2000; } } #if HAS_BUILD_PROCESS_WITH_REPORT void IPostprocessBuildWithReport.OnPostprocessBuild (BuildReport report) { SpineBuildProcessor.PostprocessBuild(); } #else void IPostprocessBuild.OnPostprocessBuild (BuildTarget target, string path) { SpineBuildProcessor.PostprocessBuild(); } #endif } }