From 28edc2dfa710217fa3446e7e500b0030d9020263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=80=9D=E6=B5=B7?= <1464576565@qq.com> Date: Mon, 20 Apr 2026 14:24:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=BE=E9=9B=86=E5=AF=BC?= =?UTF-8?q?=E5=85=A5=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Postprocessor/Atlas/AtlasEditorWindow.cs | 1 - .../Atlas/EditorSpriteSaveInfo.cs | 836 +++++++++++------- .../Atlas/EditorSpriteSaveInfo.cs.meta | 12 +- .../Atlas/SpritePostprocessor.cs | 72 +- .../Atlas/SpritePostprocessor.cs.meta | 12 +- 5 files changed, 580 insertions(+), 353 deletions(-) diff --git a/Editor/Postprocessor/Atlas/AtlasEditorWindow.cs b/Editor/Postprocessor/Atlas/AtlasEditorWindow.cs index e06b7fa..92a8efe 100644 --- a/Editor/Postprocessor/Atlas/AtlasEditorWindow.cs +++ b/Editor/Postprocessor/Atlas/AtlasEditorWindow.cs @@ -47,7 +47,6 @@ public class AtlasConfigWindow : EditorWindow if (EditorGUI.EndChangeCheck()) { AtlasConfiguration.Save(true); - AssetDatabase.Refresh(); } } diff --git a/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs b/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs index bdc0cad..52ee5cd 100644 --- a/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs +++ b/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs @@ -1,8 +1,7 @@ -#if UNITY_EDITOR +#if UNITY_EDITOR using System; using System.Collections.Generic; using System.IO; -using System.Linq; using UnityEditor; using UnityEditor.U2D; using UnityEngine; @@ -10,303 +9,432 @@ using UnityEngine.U2D; public static class EditorSpriteSaveInfo { - private static readonly HashSet _dirtyAtlasNames = new HashSet(); - private static readonly Dictionary> _atlasMap = new Dictionary>(); + private static readonly HashSet DirtyAtlasNames = new HashSet(StringComparer.Ordinal); + private static readonly Dictionary> AtlasMap = + new Dictionary>(StringComparer.Ordinal); + private static readonly List ProcessBuffer = new List(); + private static readonly List PendingV2ImporterPaths = new List(); + private static bool _initialized; + private static bool _isProcessing; + private static bool _isScanning; + private static bool _processScheduled; private static AtlasConfiguration Config => AtlasConfiguration.Instance; static EditorSpriteSaveInfo() { - EditorApplication.update += OnUpdate; Initialize(); } - private static void Initialize() + private static string NormalizedSourceRoot => NormalizePath(Config.sourceAtlasRoot).TrimEnd('/'); + + public static bool PrepareSpriteImporter(TextureImporter importer, string assetPath) { - if (_initialized) return; + if (importer == null || !ShouldProcess(assetPath)) + return false; - ScanExistingSprites(); - _initialized = true; + var modified = false; + if (importer.textureType != TextureImporterType.Sprite) + { + importer.textureType = TextureImporterType.Sprite; + modified = true; + } + + if (importer.spriteImportMode != SpriteImportMode.Single) + { + importer.spriteImportMode = SpriteImportMode.Single; + modified = true; + } + + var settings = new TextureImporterSettings(); + importer.ReadTextureSettings(settings); + if (settings.spriteGenerateFallbackPhysicsShape) + { + settings.spriteGenerateFallbackPhysicsShape = false; + importer.SetTextureSettings(settings); + modified = true; + } + + return modified; } - public static void ConvertToSprite(string assetPath) { - if (!ShouldProcess(assetPath)) return; + if (!ShouldProcess(assetPath)) + return; - #region Convert Sprite + var importer = AssetImporter.GetAtPath(assetPath) as TextureImporter; + if (importer == null) + return; - TextureImporter ti = AssetImporter.GetAtPath(assetPath) as TextureImporter; - if (ti == null) return; - bool modify = false; - if (ti.textureType != TextureImporterType.Sprite) - { - ti.textureType = TextureImporterType.Sprite; - ti.spriteImportMode = SpriteImportMode.Single; - modify = true; - } - - if (!string.IsNullOrEmpty(ti.spritePackingTag)) - { - ti.spritePackingTag = string.Empty; - modify = true; - } - - var setting = new TextureImporterSettings(); - ti.ReadTextureSettings(setting); - if (setting.spriteGenerateFallbackPhysicsShape) - { - setting.spriteGenerateFallbackPhysicsShape = false; - ti.SetTextureSettings(setting); - modify = true; - } - - if (modify) - { - ti.SaveAndReimport(); - } - - #endregion + if (PrepareSpriteImporter(importer, assetPath)) + importer.SaveAndReimport(); } public static void OnImportSprite(string assetPath) { - if (!ShouldProcess(assetPath)) return; + if (!ShouldProcess(assetPath)) + return; + EnsureInitialized(); var atlasName = GetAtlasName(assetPath); - if (string.IsNullOrEmpty(atlasName)) return; + if (string.IsNullOrEmpty(atlasName)) + return; - if (!_atlasMap.TryGetValue(atlasName, out var list)) - { - list = new List(); - _atlasMap[atlasName] = list; - } - - if (!list.Contains(assetPath)) - { - list.Add(assetPath); - MarkDirty(atlasName); - MarkParentAtlasesDirty(assetPath); - } + AddSpritePath(atlasName, assetPath); + MarkDirty(atlasName); + MarkParentAtlasesDirty(assetPath); + QueueProcessDirtyAtlases(); } - public static void OnDeleteSprite(string assetPath) { - if (!ShouldProcess(assetPath)) return; + if (!ShouldProcess(assetPath)) + return; + + EnsureInitialized(); var atlasName = GetAtlasName(assetPath); - if (string.IsNullOrEmpty(atlasName)) return; + if (string.IsNullOrEmpty(atlasName)) + return; - if (_atlasMap.TryGetValue(atlasName, out var list)) + if (AtlasMap.TryGetValue(atlasName, out var spritePaths)) { - if (list.Remove(assetPath)) - { - MarkDirty(atlasName); - MarkParentAtlasesDirty(assetPath); - } + spritePaths.Remove(assetPath); + if (spritePaths.Count == 0) + AtlasMap.Remove(atlasName); } + + MarkDirty(atlasName); + MarkParentAtlasesDirty(assetPath); + QueueProcessDirtyAtlases(); } [MenuItem("AlicizaX/Extension/图集工具/ForceGenerateAll")] public static void ForceGenerateAll() { - _atlasMap.Clear(); - ScanExistingSprites(); - _dirtyAtlasNames.UnionWith(_atlasMap.Keys); - ProcessDirtyAtlases(true); + RebuildCache(markDirty: true); + ProcessDirtyAtlases(force: true); } public static void ClearCache() { - _dirtyAtlasNames.Clear(); - _atlasMap.Clear(); - AssetDatabase.Refresh(); + DirtyAtlasNames.Clear(); + AtlasMap.Clear(); + ProcessBuffer.Clear(); + PendingV2ImporterPaths.Clear(); + _initialized = false; + _processScheduled = false; } public static void MarkParentAtlasesDirty(string assetPath) { - var currentPath = Path.GetDirectoryName(assetPath)?.Replace("\\", "/"); - var rootPath = Config.sourceAtlasRoot.Replace("\\", "/").TrimEnd('/'); - while (currentPath != null && currentPath.StartsWith(rootPath)) - { - var parentAtlasName = GetAtlasNameForDirectory(currentPath); - if (!string.IsNullOrEmpty(parentAtlasName)) - { - MarkDirty(parentAtlasName); - } + var currentPath = NormalizePath(Path.GetDirectoryName(assetPath)); + var rootPath = NormalizedSourceRoot; - currentPath = Path.GetDirectoryName(currentPath); + while (!string.IsNullOrEmpty(currentPath) && + currentPath.StartsWith(rootPath + "/", StringComparison.Ordinal)) + { + var atlasName = GetAtlasNameForDirectory(currentPath); + if (!string.IsNullOrEmpty(atlasName)) + MarkDirty(atlasName); + + currentPath = NormalizePath(Path.GetDirectoryName(currentPath)); } } - private static void OnUpdate() + public static bool ShouldProcess(string assetPath) { - if (_dirtyAtlasNames.Count > 0) - { - ProcessDirtyAtlases(); - } + return IsImageFile(assetPath) && !IsExcluded(assetPath) && IsUnderSourceRoot(assetPath); } - private static void ProcessDirtyAtlases(bool force = false) + private static void Initialize() { + if (_initialized) + return; + + RebuildCache(markDirty: false); + } + + private static void EnsureInitialized() + { + if (!_initialized) + Initialize(); + } + + private static void RebuildCache(bool markDirty) + { + DirtyAtlasNames.Clear(); + AtlasMap.Clear(); + + ScanExistingSprites(); + + if (markDirty) + { + foreach (var atlasName in AtlasMap.Keys) + DirtyAtlasNames.Add(atlasName); + + MarkExistingOutputAtlasesDirty(); + } + + _initialized = true; + } + + private static void ScanExistingSprites() + { + var sourceRoot = NormalizedSourceRoot; + if (string.IsNullOrEmpty(sourceRoot)) + return; + + _isScanning = true; try { - AssetDatabase.StartAssetEditing(); - - foreach (var atlasName in _dirtyAtlasNames.ToList()) + var guids = AssetDatabase.FindAssets("t:Sprite", new[] { sourceRoot }); + foreach (var guid in guids) { - if (force || ShouldUpdateAtlas(atlasName)) - { - GenerateAtlas(atlasName); - } + var path = AssetDatabase.GUIDToAssetPath(guid); + if (!ShouldProcess(path)) + continue; - _dirtyAtlasNames.Remove(atlasName); + var atlasName = GetAtlasName(path); + if (!string.IsNullOrEmpty(atlasName)) + AddSpritePath(atlasName, path); } } finally { - AssetDatabase.StopAssetEditing(); + _isScanning = false; + } + } + + private static void MarkExistingOutputAtlasesDirty() + { + var outputDir = NormalizePath(Config.outputAtlasDir).TrimEnd('/'); + if (string.IsNullOrEmpty(outputDir) || !AssetDatabase.IsValidFolder(outputDir)) + return; + + var guids = AssetDatabase.FindAssets("t:SpriteAtlas", new[] { outputDir }); + foreach (var guid in guids) + { + var path = AssetDatabase.GUIDToAssetPath(guid); + var atlasName = Path.GetFileNameWithoutExtension(path); + if (!string.IsNullOrEmpty(atlasName)) + DirtyAtlasNames.Add(atlasName); + } + } + + private static void QueueProcessDirtyAtlases() + { + if (_isScanning || _processScheduled || DirtyAtlasNames.Count == 0) + return; + + _processScheduled = true; + EditorApplication.delayCall += DelayedProcessDirtyAtlases; + } + + private static void DelayedProcessDirtyAtlases() + { + _processScheduled = false; + + if (_isProcessing || DirtyAtlasNames.Count == 0) + return; + + if (EditorApplication.isCompiling || EditorApplication.isUpdating) + { + QueueProcessDirtyAtlases(); + return; + } + + ProcessDirtyAtlases(); + } + + private static void ProcessDirtyAtlases(bool force = false) + { + if (DirtyAtlasNames.Count == 0) + return; + + EnsureOutputDirectory(); + + _isProcessing = true; + try + { + while (DirtyAtlasNames.Count > 0) + { + ProcessBuffer.Clear(); + ProcessBuffer.AddRange(DirtyAtlasNames); + DirtyAtlasNames.Clear(); + PendingV2ImporterPaths.Clear(); + + AssetDatabase.StartAssetEditing(); + try + { + foreach (var atlasName in ProcessBuffer) + { + if (force || ShouldUpdateAtlas(atlasName)) + GenerateAtlas(atlasName); + } + } + finally + { + AssetDatabase.StopAssetEditing(); + } + + ApplyPendingV2ImportSettings(); + } + AssetDatabase.SaveAssets(); - AssetDatabase.Refresh(); + } + finally + { + ProcessBuffer.Clear(); + PendingV2ImporterPaths.Clear(); + _isProcessing = false; + + if (DirtyAtlasNames.Count > 0) + QueueProcessDirtyAtlases(); } } private static void GenerateAtlas(string atlasName) { - var outputPath = $"{Config.outputAtlasDir}/{atlasName}.spriteatlas"; - SpriteAtlasAsset spriteAtlasAsset = default; - SpriteAtlas atlas = new SpriteAtlas(); - if (Config.enableV2) - { - outputPath = $"{Config.outputAtlasDir}/{atlasName}.spriteatlasv2"; - if (!File.Exists(outputPath)) - { - spriteAtlasAsset = new SpriteAtlasAsset(); - } - else - { - spriteAtlasAsset = SpriteAtlasAsset.Load(outputPath); - atlas = AssetDatabase.LoadAssetAtPath(outputPath); - if (atlas != null) - { - var olds = atlas.GetPackables(); - if (olds != null) spriteAtlasAsset.Remove(olds); - } - } - } + var outputPath = GetAtlasOutputPath(atlasName); + var packables = LoadValidSprites(atlasName); - - var sprites = LoadValidSprites(atlasName); - EnsureOutputDirectory(); - if (sprites.Count == 0) + if (packables.Count == 0) { DeleteAtlas(outputPath); + DeleteAtlas(GetLegacyAtlasOutputPath(atlasName)); return; } + DeleteAtlas(GetLegacyAtlasOutputPath(atlasName)); + + var spriteObjects = packables.ToArray(); if (Config.enableV2) { - spriteAtlasAsset.Add(sprites.ToArray()); - SpriteAtlasAsset.Save(spriteAtlasAsset, outputPath); - AssetDatabase.Refresh(); - EditorApplication.delayCall += () => - { -#if UNITY_2022_1_OR_NEWER - SpriteAtlasImporter sai = (SpriteAtlasImporter)AssetImporter.GetAtPath(outputPath); - ConfigureAtlasV2Settings(sai); -#else - ConfigureAtlasV2Settings(spriteAtlasAsset); - SpriteAtlasAsset.Save(spriteAtlasAsset, outputPath); -#endif - AssetDatabase.WriteImportSettingsIfDirty(outputPath); - AssetDatabase.Refresh(); - }; + GenerateAtlasV2(outputPath, spriteObjects); } else { - ConfigureAtlasSettings(atlas); - atlas.Add(sprites.ToArray()); - atlas.SetIsVariant(false); - AssetDatabase.CreateAsset(atlas, outputPath); + GenerateAtlasV1(outputPath, spriteObjects); } - if (Config.enableLogging) - Debug.Log($"Generated atlas: {atlasName} ({sprites.Count} sprites)"); + Debug.Log($"Generated atlas: {atlasName} ({spriteObjects.Length} sprites)"); } - private static List LoadValidSprites(string atlasName) + private static void GenerateAtlasV2(string outputPath, UnityEngine.Object[] spriteObjects) { - if (_atlasMap.TryGetValue(atlasName, out List spriteList)) + var atlas = AssetDatabase.LoadAssetAtPath(outputPath); + var spriteAtlasAsset = atlas == null ? new SpriteAtlasAsset() : SpriteAtlasAsset.Load(outputPath); + + if (atlas != null) { - var allSprites = new List(); + var oldPackables = atlas.GetPackables(); + if (oldPackables != null && oldPackables.Length > 0) + spriteAtlasAsset.Remove(oldPackables); + } - foreach (var assetPath in spriteList.Where(File.Exists)) +#if !UNITY_2022_1_OR_NEWER + ConfigureAtlasV2Settings(spriteAtlasAsset); +#endif + spriteAtlasAsset.Add(spriteObjects); + SpriteAtlasAsset.Save(spriteAtlasAsset, outputPath); + +#if UNITY_2022_1_OR_NEWER + PendingV2ImporterPaths.Add(outputPath); +#endif + } + + private static void GenerateAtlasV1(string outputPath, UnityEngine.Object[] spriteObjects) + { + var atlas = AssetDatabase.LoadAssetAtPath(outputPath); + if (atlas == null) + { + atlas = new SpriteAtlas(); + ConfigureAtlasSettings(atlas); + atlas.Add(spriteObjects); + atlas.SetIsVariant(false); + AssetDatabase.CreateAsset(atlas, outputPath); + return; + } + + var oldPackables = atlas.GetPackables(); + if (oldPackables != null && oldPackables.Length > 0) + atlas.Remove(oldPackables); + + ConfigureAtlasSettings(atlas); + atlas.Add(spriteObjects); + atlas.SetIsVariant(false); + EditorUtility.SetDirty(atlas); + } + + private static List LoadValidSprites(string atlasName) + { + var sprites = new List(); + if (!AtlasMap.TryGetValue(atlasName, out var spritePaths) || spritePaths.Count == 0) + return sprites; + + foreach (var assetPath in spritePaths) + { + var sprite = AssetDatabase.LoadAssetAtPath(assetPath); + if (sprite != null) { - // 加载所有子图 - var sprites = AssetDatabase.LoadAllAssetsAtPath(assetPath) - .OfType() - .Where(s => s != null) - .ToArray(); - - allSprites.AddRange(sprites); + sprites.Add(sprite); + continue; } - return allSprites; + var subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath); + foreach (var subAsset in subAssets) + { + if (subAsset is Sprite subSprite) + sprites.Add(subSprite); + } } - return new List(); + + return sprites; + } + + private static void ApplyPendingV2ImportSettings() + { +#if UNITY_2022_1_OR_NEWER + foreach (var outputPath in PendingV2ImporterPaths) + { + var importer = AssetImporter.GetAtPath(outputPath) as SpriteAtlasImporter; + if (importer == null) + continue; + + if (ConfigureAtlasV2Settings(importer)) + { + AssetDatabase.WriteImportSettingsIfDirty(outputPath); + importer.SaveAndReimport(); + } + else + { + AssetDatabase.ImportAsset(outputPath, ImportAssetOptions.ForceSynchronousImport); + } + } +#endif } #if UNITY_2022_1_OR_NEWER - private static void ConfigureAtlasV2Settings(SpriteAtlasImporter atlasImporter) + private static bool ConfigureAtlasV2Settings(SpriteAtlasImporter atlasImporter) { - void SetPlatform(string platform, TextureImporterFormat format) + var modified = false; + modified |= SetPlatform(atlasImporter, "Android", Config.androidFormat); + modified |= SetPlatform(atlasImporter, "iPhone", Config.iosFormat); + modified |= SetPlatform(atlasImporter, "WebGL", Config.webglFormat); + + var packingSettings = atlasImporter.packingSettings; + if (packingSettings.padding != Config.padding || + packingSettings.enableRotation != Config.enableRotation || + packingSettings.blockOffset != Config.blockOffset || + packingSettings.enableTightPacking != Config.tightPacking || + !packingSettings.enableAlphaDilation) { - var settings = atlasImporter.GetPlatformSettings(platform); - if (settings == null) return; - ; - settings.overridden = true; - settings.format = format; - settings.compressionQuality = Config.compressionQuality; - atlasImporter.SetPlatformSettings(settings); - } - - SetPlatform("Android", Config.androidFormat); - SetPlatform("iPhone", Config.iosFormat); - SetPlatform("WebGL", Config.webglFormat); - - var packingSettings = new SpriteAtlasPackingSettings - { - padding = Config.padding, - enableRotation = Config.enableRotation, - blockOffset = Config.blockOffset, - enableTightPacking = Config.tightPacking, - enableAlphaDilation = true - }; - atlasImporter.packingSettings = packingSettings; - } -#else - private static void ConfigureAtlasV2Settings(SpriteAtlasAsset spriteAtlasAsset) - { - void SetPlatform(string platform, TextureImporterFormat format) - { - var settings = spriteAtlasAsset.GetPlatformSettings(platform); - if (settings == null) return; - ; - settings.overridden = true; - settings.format = format; - settings.compressionQuality = Config.compressionQuality; - spriteAtlasAsset.SetPlatformSettings(settings); - } - - SetPlatform("Android", Config.androidFormat); - SetPlatform("iPhone", Config.iosFormat); - SetPlatform("WebGL", Config.webglFormat); - - var packingSettings = new SpriteAtlasPackingSettings + atlasImporter.packingSettings = new SpriteAtlasPackingSettings { padding = Config.padding, enableRotation = Config.enableRotation, @@ -314,138 +442,240 @@ public static class EditorSpriteSaveInfo enableTightPacking = Config.tightPacking, enableAlphaDilation = true }; - spriteAtlasAsset.SetPackingSettings(packingSettings); + modified = true; } -#endif - private static void ConfigureAtlasSettings(SpriteAtlas atlas) + return modified; + } + + private static bool SetPlatform(SpriteAtlasImporter atlasImporter, string platform, TextureImporterFormat format) { - void SetPlatform(string platform, TextureImporterFormat format) + var settings = atlasImporter.GetPlatformSettings(platform); + if (settings == null) + return false; + + if (settings.overridden && + settings.format == format && + settings.compressionQuality == Config.compressionQuality) { - var settings = atlas.GetPlatformSettings(platform); - settings.overridden = true; - settings.format = format; - settings.compressionQuality = Config.compressionQuality; - atlas.SetPlatformSettings(settings); + return false; } - SetPlatform("Android", Config.androidFormat); - SetPlatform("iPhone", Config.iosFormat); - SetPlatform("WebGL", Config.webglFormat); + settings.overridden = true; + settings.format = format; + settings.compressionQuality = Config.compressionQuality; + atlasImporter.SetPlatformSettings(settings); + return true; + } +#else + private static void ConfigureAtlasV2Settings(SpriteAtlasAsset spriteAtlasAsset) + { + SetPlatform(spriteAtlasAsset, "Android", Config.androidFormat); + SetPlatform(spriteAtlasAsset, "iPhone", Config.iosFormat); + SetPlatform(spriteAtlasAsset, "WebGL", Config.webglFormat); - var packingSettings = new SpriteAtlasPackingSettings + spriteAtlasAsset.SetPackingSettings(new SpriteAtlasPackingSettings { padding = Config.padding, enableRotation = Config.enableRotation, blockOffset = Config.blockOffset, enableTightPacking = Config.tightPacking, - }; - atlas.SetPackingSettings(packingSettings); + enableAlphaDilation = true + }); + } + + private static void SetPlatform(SpriteAtlasAsset spriteAtlasAsset, string platform, TextureImporterFormat format) + { + var settings = spriteAtlasAsset.GetPlatformSettings(platform); + if (settings == null) + return; + + settings.overridden = true; + settings.format = format; + settings.compressionQuality = Config.compressionQuality; + spriteAtlasAsset.SetPlatformSettings(settings); + } +#endif + + private static void ConfigureAtlasSettings(SpriteAtlas atlas) + { + SetPlatform(atlas, "Android", Config.androidFormat); + SetPlatform(atlas, "iPhone", Config.iosFormat); + SetPlatform(atlas, "WebGL", Config.webglFormat); + + atlas.SetPackingSettings(new SpriteAtlasPackingSettings + { + padding = Config.padding, + enableRotation = Config.enableRotation, + blockOffset = Config.blockOffset, + enableTightPacking = Config.tightPacking + }); + } + + private static void SetPlatform(SpriteAtlas atlas, string platform, TextureImporterFormat format) + { + var settings = atlas.GetPlatformSettings(platform); + settings.overridden = true; + settings.format = format; + settings.compressionQuality = Config.compressionQuality; + atlas.SetPlatformSettings(settings); } private static string GetAtlasName(string assetPath) { - var normalizedPath = assetPath.Replace("\\", "/"); - var rootPath = Config.sourceAtlasRoot.Replace("\\", "/").TrimEnd('/'); + var normalizedPath = NormalizePath(assetPath); + var rootPath = NormalizedSourceRoot; - if (!normalizedPath.StartsWith(rootPath + "/")) return null; + if (!normalizedPath.StartsWith(rootPath + "/", StringComparison.Ordinal)) + return null; - var relativePath = normalizedPath - .Substring(rootPath.Length + 1) - .Split('/'); + var relativePath = normalizedPath.Substring(rootPath.Length + 1).Split('/'); + if (relativePath.Length < 2) + return null; - if (relativePath.Length < 2) return null; - - var directories = relativePath.Take(relativePath.Length - 1); - var atlasNamePart = string.Join("_", directories); + var atlasNamePart = string.Join("_", relativePath, 0, relativePath.Length - 1); var rootFolderName = Path.GetFileName(rootPath); - return $"{rootFolderName}_{atlasNamePart}"; } - private static bool ShouldProcess(string assetPath) - { - return IsImageFile(assetPath) && !IsExcluded(assetPath); - } - - private static bool IsExcluded(string path) - { - return Config.excludeFolders.Any(path.StartsWith) || - Config.excludeKeywords.Any(k => path.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0); - } - - private static bool IsImageFile(string path) - { - var ext = Path.GetExtension(path).ToLower(); - return ext == ".png" || ext == ".jpg" || ext == ".jpeg"; - } - - private static void MarkDirty(string atlasName) - { - _dirtyAtlasNames.Add(atlasName); - } - - private static bool ShouldUpdateAtlas(string atlasName) - { - // var outputPath = $"{Config.outputAtlasDir}/{atlasName}.spriteatlas"; - return true; - } - - private static DateTime GetLatestSpriteTime(string atlasName) - { - return _atlasMap[atlasName] - .Select(p => new FileInfo(p).LastWriteTime) - .DefaultIfEmpty() - .Max(); - } - - private static void DeleteAtlas(string path) - { - if (File.Exists(path)) - { - AssetDatabase.DeleteAsset(path); - if (Config.enableLogging) - Debug.Log($"Deleted empty atlas: {Path.GetFileName(path)}"); - } - } - - private static void EnsureOutputDirectory() - { - if (!Directory.Exists(Config.outputAtlasDir)) - { - Directory.CreateDirectory(Config.outputAtlasDir); - AssetDatabase.Refresh(); - } - } - - private static void ScanExistingSprites() - { - var guids = AssetDatabase.FindAssets("t:Sprite", new[] { Config.sourceAtlasRoot }); - foreach (var guid in guids) - { - var path = AssetDatabase.GUIDToAssetPath(guid); - if (ShouldProcess(path)) - { - OnImportSprite(path); - } - } - } - private static string GetAtlasNameForDirectory(string directoryPath) { - var normalizedPath = directoryPath.Replace("\\", "/"); - var rootPath = Config.sourceAtlasRoot.Replace("\\", "/").TrimEnd('/'); + var normalizedPath = NormalizePath(directoryPath); + var rootPath = NormalizedSourceRoot; - if (!normalizedPath.StartsWith(rootPath + "/")) return null; + if (!normalizedPath.StartsWith(rootPath + "/", StringComparison.Ordinal)) + return null; - var relativePath = normalizedPath - .Substring(rootPath.Length + 1) - .Split('/'); + var relativePath = normalizedPath.Substring(rootPath.Length + 1).Split('/'); + if (relativePath.Length == 0) + return null; var atlasNamePart = string.Join("_", relativePath); var rootFolderName = Path.GetFileName(rootPath); - return $"{rootFolderName}_{atlasNamePart}"; } + + private static void AddSpritePath(string atlasName, string assetPath) + { + if (!AtlasMap.TryGetValue(atlasName, out var spritePaths)) + { + spritePaths = new HashSet(StringComparer.Ordinal); + AtlasMap[atlasName] = spritePaths; + } + + spritePaths.Add(assetPath); + } + + private static bool IsExcluded(string path) + { + var normalizedPath = NormalizePath(path); + var excludeFolders = Config.excludeFolders; + if (excludeFolders != null) + { + foreach (var folder in excludeFolders) + { + if (!string.IsNullOrEmpty(folder) && + normalizedPath.StartsWith(NormalizePath(folder), StringComparison.Ordinal)) + { + return true; + } + } + } + + var excludeKeywords = Config.excludeKeywords; + if (excludeKeywords != null) + { + foreach (var keyword in excludeKeywords) + { + if (!string.IsNullOrEmpty(keyword) && + normalizedPath.IndexOf(keyword, StringComparison.OrdinalIgnoreCase) >= 0) + { + return true; + } + } + } + + return false; + } + + private static bool IsImageFile(string path) + { + var extension = Path.GetExtension(path); + if (string.IsNullOrEmpty(extension)) + return false; + + switch (extension.ToLowerInvariant()) + { + case ".png": + case ".jpg": + case ".jpeg": + return true; + default: + return false; + } + } + + private static bool IsUnderSourceRoot(string assetPath) + { + var normalizedPath = NormalizePath(assetPath); + var rootPath = NormalizedSourceRoot; + return !string.IsNullOrEmpty(rootPath) && + normalizedPath.StartsWith(rootPath + "/", StringComparison.Ordinal); + } + + private static void MarkDirty(string atlasName) + { + if (!string.IsNullOrEmpty(atlasName)) + DirtyAtlasNames.Add(atlasName); + } + + private static bool ShouldUpdateAtlas(string atlasName) + { + return !string.IsNullOrEmpty(atlasName); + } + + private static void DeleteAtlas(string assetPath) + { + if (!string.IsNullOrEmpty(assetPath) && !string.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(assetPath))) + AssetDatabase.DeleteAsset(assetPath); + } + + private static void EnsureOutputDirectory() + { + var outputPath = NormalizePath(Config.outputAtlasDir).TrimEnd('/'); + if (string.IsNullOrEmpty(outputPath) || AssetDatabase.IsValidFolder(outputPath)) + return; + + var pathParts = outputPath.Split('/'); + if (pathParts.Length == 0 || pathParts[0] != "Assets") + return; + + var currentPath = pathParts[0]; + for (var i = 1; i < pathParts.Length; i++) + { + var nextPath = $"{currentPath}/{pathParts[i]}"; + if (!AssetDatabase.IsValidFolder(nextPath)) + AssetDatabase.CreateFolder(currentPath, pathParts[i]); + + currentPath = nextPath; + } + } + + private static string GetAtlasOutputPath(string atlasName) + { + var extension = Config.enableV2 ? ".spriteatlasv2" : ".spriteatlas"; + return $"{NormalizePath(Config.outputAtlasDir).TrimEnd('/')}/{atlasName}{extension}"; + } + + private static string GetLegacyAtlasOutputPath(string atlasName) + { + var legacyExtension = Config.enableV2 ? ".spriteatlas" : ".spriteatlasv2"; + return $"{NormalizePath(Config.outputAtlasDir).TrimEnd('/')}/{atlasName}{legacyExtension}"; + } + + private static string NormalizePath(string path) + { + return string.IsNullOrEmpty(path) ? string.Empty : path.Replace("\\", "/"); + } } #endif diff --git a/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs.meta b/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs.meta index c78628d..3cdd142 100644 --- a/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs.meta +++ b/Editor/Postprocessor/Atlas/EditorSpriteSaveInfo.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 +fileFormatVersion: 2 guid: 6e0aa32db2943f742a839708e928fda4 -timeCreated: 1738748262 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/Postprocessor/Atlas/SpritePostprocessor.cs b/Editor/Postprocessor/Atlas/SpritePostprocessor.cs index 06e6ab0..5e7e2ee 100644 --- a/Editor/Postprocessor/Atlas/SpritePostprocessor.cs +++ b/Editor/Postprocessor/Atlas/SpritePostprocessor.cs @@ -1,9 +1,20 @@ -using System.Linq; using UnityEditor; using UnityEngine; public class SpritePostprocessor : AssetPostprocessor { + private void OnPreprocessTexture() + { + var config = AtlasConfiguration.Instance; + if (!config.autoGenerate) + return; + + if (!ShouldProcessAsset(assetPath)) + return; + + EditorSpriteSaveInfo.PrepareSpriteImporter(assetImporter as TextureImporter, assetPath); + } + private static void OnPostprocessAllAssets( string[] importedAssets, string[] deletedAssets, @@ -11,8 +22,8 @@ public class SpritePostprocessor : AssetPostprocessor string[] movedFromAssetPaths) { var config = AtlasConfiguration.Instance; - - if (!config.autoGenerate) return; + if (!config.autoGenerate) + return; try { @@ -27,10 +38,6 @@ public class SpritePostprocessor : AssetPostprocessor { Debug.LogError($"Atlas processing error: {e.Message}\n{e.StackTrace}"); } - finally - { - AssetDatabase.Refresh(); - } } private static void ProcessAssetChanges( @@ -39,14 +46,14 @@ public class SpritePostprocessor : AssetPostprocessor string[] movedAssets, string[] movedFromPaths) { - ProcessAssets(importedAssets, (path) => + ProcessAssets(importedAssets, path => { EditorSpriteSaveInfo.ConvertToSprite(path); EditorSpriteSaveInfo.OnImportSprite(path); LogProcessed("[Added]", path); }); - ProcessAssets(deletedAssets, (path) => + ProcessAssets(deletedAssets, path => { EditorSpriteSaveInfo.OnDeleteSprite(path); LogProcessed("[Deleted]", path); @@ -57,77 +64,52 @@ public class SpritePostprocessor : AssetPostprocessor private static void ProcessAssets(string[] assets, System.Action processor) { - if (assets == null) return; + if (assets == null) + return; foreach (var asset in assets) { if (ShouldProcessAsset(asset)) - { processor?.Invoke(asset); - } } } private static void ProcessMovedAssets(string[] oldPaths, string[] newPaths) { - if (oldPaths == null || newPaths == null) return; + if (oldPaths == null || newPaths == null) + return; - for (int i = 0; i < oldPaths.Length; i++) + for (var i = 0; i < oldPaths.Length; i++) { if (ShouldProcessAsset(oldPaths[i])) { EditorSpriteSaveInfo.OnDeleteSprite(oldPaths[i]); LogProcessed("[Moved From]", oldPaths[i]); - EditorSpriteSaveInfo.MarkParentAtlasesDirty(oldPaths[i]); } if (ShouldProcessAsset(newPaths[i])) { + EditorSpriteSaveInfo.ConvertToSprite(newPaths[i]); EditorSpriteSaveInfo.OnImportSprite(newPaths[i]); LogProcessed("[Moved To]", newPaths[i]); - EditorSpriteSaveInfo.MarkParentAtlasesDirty(newPaths[i]); } } } private static bool ShouldProcessAsset(string assetPath) { - var config = AtlasConfiguration.Instance; + if (string.IsNullOrEmpty(assetPath)) + return false; - if (string.IsNullOrEmpty(assetPath)) return false; - if (assetPath.StartsWith("Packages/")) return false; + if (assetPath.StartsWith("Packages/")) + return false; - if (!assetPath.StartsWith(config.sourceAtlasRoot)) return false; - if (config.excludeFolders.Any(assetPath.StartsWith)) return false; - - if (!IsValidImageFile(assetPath)) return false; - - foreach (var keyword in config.excludeKeywords) - { - if (assetPath.IndexOf(keyword, System.StringComparison.OrdinalIgnoreCase) >= 0) - return false; - } - - return true; - } - - private static bool IsValidImageFile(string path) - { - var ext = System.IO.Path.GetExtension(path).ToLower(); - return ext switch - { - ".png" => true, - ".jpg" => true, - ".jpeg" => true, - _ => false - }; + return EditorSpriteSaveInfo.ShouldProcess(assetPath); } private static void LogProcessed(string operation, string path) { if (AtlasConfiguration.Instance.enableLogging) - { Debug.Log($"{operation} {System.IO.Path.GetFileName(path)}\nPath: {path}"); - } } } diff --git a/Editor/Postprocessor/Atlas/SpritePostprocessor.cs.meta b/Editor/Postprocessor/Atlas/SpritePostprocessor.cs.meta index 5015164..9a45e12 100644 --- a/Editor/Postprocessor/Atlas/SpritePostprocessor.cs.meta +++ b/Editor/Postprocessor/Atlas/SpritePostprocessor.cs.meta @@ -1,3 +1,11 @@ -fileFormatVersion: 2 +fileFormatVersion: 2 guid: e5d969d76e1a36445810f61bafc4f7db -timeCreated: 1738748276 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: