优化图集导入模块

This commit is contained in:
陈思海 2026-04-20 14:24:54 +08:00
parent 8d9b4a32ce
commit 28edc2dfa7
5 changed files with 580 additions and 353 deletions

View File

@ -47,7 +47,6 @@ public class AtlasConfigWindow : EditorWindow
if (EditorGUI.EndChangeCheck()) if (EditorGUI.EndChangeCheck())
{ {
AtlasConfiguration.Save(true); AtlasConfiguration.Save(true);
AssetDatabase.Refresh();
} }
} }

View File

@ -1,8 +1,7 @@
#if UNITY_EDITOR #if UNITY_EDITOR
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEditor.U2D; using UnityEditor.U2D;
using UnityEngine; using UnityEngine;
@ -10,275 +9,432 @@ using UnityEngine.U2D;
public static class EditorSpriteSaveInfo public static class EditorSpriteSaveInfo
{ {
private static readonly HashSet<string> _dirtyAtlasNames = new HashSet<string>(); private static readonly HashSet<string> DirtyAtlasNames = new HashSet<string>(StringComparer.Ordinal);
private static readonly Dictionary<string, List<string>> _atlasMap = new Dictionary<string, List<string>>(); private static readonly Dictionary<string, HashSet<string>> AtlasMap =
new Dictionary<string, HashSet<string>>(StringComparer.Ordinal);
private static readonly List<string> ProcessBuffer = new List<string>();
private static readonly List<string> PendingV2ImporterPaths = new List<string>();
private static bool _initialized; private static bool _initialized;
private static bool _isProcessing;
private static bool _isScanning;
private static bool _processScheduled;
private static AtlasConfiguration Config => AtlasConfiguration.Instance; private static AtlasConfiguration Config => AtlasConfiguration.Instance;
static EditorSpriteSaveInfo() static EditorSpriteSaveInfo()
{ {
EditorApplication.update += OnUpdate;
Initialize(); Initialize();
} }
private static void Initialize() private static string NormalizedSourceRoot => NormalizePath(Config.sourceAtlasRoot).TrimEnd('/');
{
if (_initialized) return;
ScanExistingSprites(); public static bool PrepareSpriteImporter(TextureImporter importer, string assetPath)
_initialized = true; {
if (importer == null || !ShouldProcess(assetPath))
return false;
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) 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 (PrepareSpriteImporter(importer, assetPath))
if (ti == null) return; importer.SaveAndReimport();
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
} }
public static void OnImportSprite(string assetPath) public static void OnImportSprite(string assetPath)
{ {
if (!ShouldProcess(assetPath)) return; if (!ShouldProcess(assetPath))
return;
EnsureInitialized();
var atlasName = GetAtlasName(assetPath); var atlasName = GetAtlasName(assetPath);
if (string.IsNullOrEmpty(atlasName)) return; if (string.IsNullOrEmpty(atlasName))
return;
if (!_atlasMap.TryGetValue(atlasName, out var list)) AddSpritePath(atlasName, assetPath);
{
list = new List<string>();
_atlasMap[atlasName] = list;
}
if (!list.Contains(assetPath))
{
list.Add(assetPath);
MarkDirty(atlasName); MarkDirty(atlasName);
MarkParentAtlasesDirty(assetPath); MarkParentAtlasesDirty(assetPath);
QueueProcessDirtyAtlases();
} }
}
public static void OnDeleteSprite(string assetPath) public static void OnDeleteSprite(string assetPath)
{ {
if (!ShouldProcess(assetPath)) return; if (!ShouldProcess(assetPath))
return;
EnsureInitialized();
var atlasName = GetAtlasName(assetPath); 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))
{ {
spritePaths.Remove(assetPath);
if (spritePaths.Count == 0)
AtlasMap.Remove(atlasName);
}
MarkDirty(atlasName); MarkDirty(atlasName);
MarkParentAtlasesDirty(assetPath); MarkParentAtlasesDirty(assetPath);
} QueueProcessDirtyAtlases();
}
} }
[MenuItem("AlicizaX/Extension/图集工具/ForceGenerateAll")] [MenuItem("AlicizaX/Extension/图集工具/ForceGenerateAll")]
public static void ForceGenerateAll() public static void ForceGenerateAll()
{ {
_atlasMap.Clear(); RebuildCache(markDirty: true);
ScanExistingSprites(); ProcessDirtyAtlases(force: true);
_dirtyAtlasNames.UnionWith(_atlasMap.Keys);
ProcessDirtyAtlases(true);
} }
public static void ClearCache() public static void ClearCache()
{ {
_dirtyAtlasNames.Clear(); DirtyAtlasNames.Clear();
_atlasMap.Clear(); AtlasMap.Clear();
AssetDatabase.Refresh(); ProcessBuffer.Clear();
PendingV2ImporterPaths.Clear();
_initialized = false;
_processScheduled = false;
} }
public static void MarkParentAtlasesDirty(string assetPath) public static void MarkParentAtlasesDirty(string assetPath)
{ {
var currentPath = Path.GetDirectoryName(assetPath)?.Replace("\\", "/"); var currentPath = NormalizePath(Path.GetDirectoryName(assetPath));
var rootPath = Config.sourceAtlasRoot.Replace("\\", "/").TrimEnd('/'); var rootPath = NormalizedSourceRoot;
while (currentPath != null && currentPath.StartsWith(rootPath))
{
var parentAtlasName = GetAtlasNameForDirectory(currentPath);
if (!string.IsNullOrEmpty(parentAtlasName))
{
MarkDirty(parentAtlasName);
}
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) return IsImageFile(assetPath) && !IsExcluded(assetPath) && IsUnderSourceRoot(assetPath);
}
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
{
var guids = AssetDatabase.FindAssets("t:Sprite", new[] { sourceRoot });
foreach (var guid in guids)
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (!ShouldProcess(path))
continue;
var atlasName = GetAtlasName(path);
if (!string.IsNullOrEmpty(atlasName))
AddSpritePath(atlasName, path);
}
}
finally
{
_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(); ProcessDirtyAtlases();
} }
}
private static void ProcessDirtyAtlases(bool force = false) private static void ProcessDirtyAtlases(bool force = false)
{ {
if (DirtyAtlasNames.Count == 0)
return;
EnsureOutputDirectory();
_isProcessing = true;
try try
{ {
AssetDatabase.StartAssetEditing(); while (DirtyAtlasNames.Count > 0)
{
ProcessBuffer.Clear();
ProcessBuffer.AddRange(DirtyAtlasNames);
DirtyAtlasNames.Clear();
PendingV2ImporterPaths.Clear();
foreach (var atlasName in _dirtyAtlasNames.ToList()) AssetDatabase.StartAssetEditing();
try
{
foreach (var atlasName in ProcessBuffer)
{ {
if (force || ShouldUpdateAtlas(atlasName)) if (force || ShouldUpdateAtlas(atlasName))
{
GenerateAtlas(atlasName); GenerateAtlas(atlasName);
} }
_dirtyAtlasNames.Remove(atlasName);
}
} }
finally finally
{ {
AssetDatabase.StopAssetEditing(); AssetDatabase.StopAssetEditing();
}
ApplyPendingV2ImportSettings();
}
AssetDatabase.SaveAssets(); AssetDatabase.SaveAssets();
AssetDatabase.Refresh(); }
finally
{
ProcessBuffer.Clear();
PendingV2ImporterPaths.Clear();
_isProcessing = false;
if (DirtyAtlasNames.Count > 0)
QueueProcessDirtyAtlases();
} }
} }
private static void GenerateAtlas(string atlasName) private static void GenerateAtlas(string atlasName)
{ {
var outputPath = $"{Config.outputAtlasDir}/{atlasName}.spriteatlas"; var outputPath = GetAtlasOutputPath(atlasName);
SpriteAtlasAsset spriteAtlasAsset = default; var packables = LoadValidSprites(atlasName);
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<SpriteAtlas>(outputPath);
if (atlas != null)
{
var olds = atlas.GetPackables();
if (olds != null) spriteAtlasAsset.Remove(olds);
}
}
}
if (packables.Count == 0)
var sprites = LoadValidSprites(atlasName);
EnsureOutputDirectory();
if (sprites.Count == 0)
{ {
DeleteAtlas(outputPath); DeleteAtlas(outputPath);
DeleteAtlas(GetLegacyAtlasOutputPath(atlasName));
return; return;
} }
DeleteAtlas(GetLegacyAtlasOutputPath(atlasName));
var spriteObjects = packables.ToArray();
if (Config.enableV2) if (Config.enableV2)
{ {
spriteAtlasAsset.Add(sprites.ToArray()); GenerateAtlasV2(outputPath, spriteObjects);
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();
};
} }
else else
{ {
ConfigureAtlasSettings(atlas); GenerateAtlasV1(outputPath, spriteObjects);
atlas.Add(sprites.ToArray());
atlas.SetIsVariant(false);
AssetDatabase.CreateAsset(atlas, outputPath);
} }
if (Config.enableLogging) if (Config.enableLogging)
Debug.Log($"Generated atlas: {atlasName} ({sprites.Count} sprites)"); Debug.Log($"Generated atlas: {atlasName} ({spriteObjects.Length} sprites)");
} }
private static List<Sprite> LoadValidSprites(string atlasName) private static void GenerateAtlasV2(string outputPath, UnityEngine.Object[] spriteObjects)
{ {
if (_atlasMap.TryGetValue(atlasName, out List<string> spriteList)) var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(outputPath);
{ var spriteAtlasAsset = atlas == null ? new SpriteAtlasAsset() : SpriteAtlasAsset.Load(outputPath);
var allSprites = new List<Sprite>();
foreach (var assetPath in spriteList.Where(File.Exists)) if (atlas != null)
{ {
// 加载所有子图 var oldPackables = atlas.GetPackables();
var sprites = AssetDatabase.LoadAllAssetsAtPath(assetPath) if (oldPackables != null && oldPackables.Length > 0)
.OfType<Sprite>() spriteAtlasAsset.Remove(oldPackables);
.Where(s => s != null)
.ToArray();
allSprites.AddRange(sprites);
} }
return allSprites; #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
} }
return new List<Sprite>();
private static void GenerateAtlasV1(string outputPath, UnityEngine.Object[] spriteObjects)
{
var atlas = AssetDatabase.LoadAssetAtPath<SpriteAtlas>(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<UnityEngine.Object> LoadValidSprites(string atlasName)
{
var sprites = new List<UnityEngine.Object>();
if (!AtlasMap.TryGetValue(atlasName, out var spritePaths) || spritePaths.Count == 0)
return sprites;
foreach (var assetPath in spritePaths)
{
var sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
if (sprite != null)
{
sprites.Add(sprite);
continue;
}
var subAssets = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath);
foreach (var subAsset in subAssets)
{
if (subAsset is Sprite subSprite)
sprites.Add(subSprite);
}
}
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 #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); atlasImporter.packingSettings = new SpriteAtlasPackingSettings
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, padding = Config.padding,
enableRotation = Config.enableRotation, enableRotation = Config.enableRotation,
@ -286,41 +442,77 @@ public static class EditorSpriteSaveInfo
enableTightPacking = Config.tightPacking, enableTightPacking = Config.tightPacking,
enableAlphaDilation = true enableAlphaDilation = true
}; };
atlasImporter.packingSettings = packingSettings; modified = true;
}
return modified;
}
private static bool SetPlatform(SpriteAtlasImporter atlasImporter, string platform, TextureImporterFormat format)
{
var settings = atlasImporter.GetPlatformSettings(platform);
if (settings == null)
return false;
if (settings.overridden &&
settings.format == format &&
settings.compressionQuality == Config.compressionQuality)
{
return false;
}
settings.overridden = true;
settings.format = format;
settings.compressionQuality = Config.compressionQuality;
atlasImporter.SetPlatformSettings(settings);
return true;
} }
#else #else
private static void ConfigureAtlasV2Settings(SpriteAtlasAsset spriteAtlasAsset) private static void ConfigureAtlasV2Settings(SpriteAtlasAsset spriteAtlasAsset)
{ {
void SetPlatform(string platform, TextureImporterFormat format) SetPlatform(spriteAtlasAsset, "Android", Config.androidFormat);
SetPlatform(spriteAtlasAsset, "iPhone", Config.iosFormat);
SetPlatform(spriteAtlasAsset, "WebGL", Config.webglFormat);
spriteAtlasAsset.SetPackingSettings(new SpriteAtlasPackingSettings
{
padding = Config.padding,
enableRotation = Config.enableRotation,
blockOffset = Config.blockOffset,
enableTightPacking = Config.tightPacking,
enableAlphaDilation = true
});
}
private static void SetPlatform(SpriteAtlasAsset spriteAtlasAsset, string platform, TextureImporterFormat format)
{ {
var settings = spriteAtlasAsset.GetPlatformSettings(platform); var settings = spriteAtlasAsset.GetPlatformSettings(platform);
if (settings == null) return; if (settings == null)
; return;
settings.overridden = true; settings.overridden = true;
settings.format = format; settings.format = format;
settings.compressionQuality = Config.compressionQuality; settings.compressionQuality = Config.compressionQuality;
spriteAtlasAsset.SetPlatformSettings(settings); spriteAtlasAsset.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
};
spriteAtlasAsset.SetPackingSettings(packingSettings);
}
#endif #endif
private static void ConfigureAtlasSettings(SpriteAtlas atlas) private static void ConfigureAtlasSettings(SpriteAtlas atlas)
{ {
void SetPlatform(string platform, TextureImporterFormat format) 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); var settings = atlas.GetPlatformSettings(platform);
settings.overridden = true; settings.overridden = true;
@ -329,123 +521,161 @@ public static class EditorSpriteSaveInfo
atlas.SetPlatformSettings(settings); atlas.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,
};
atlas.SetPackingSettings(packingSettings);
}
private static string GetAtlasName(string assetPath) private static string GetAtlasName(string assetPath)
{ {
var normalizedPath = assetPath.Replace("\\", "/"); var normalizedPath = NormalizePath(assetPath);
var rootPath = Config.sourceAtlasRoot.Replace("\\", "/").TrimEnd('/'); var rootPath = NormalizedSourceRoot;
if (!normalizedPath.StartsWith(rootPath + "/")) return null; if (!normalizedPath.StartsWith(rootPath + "/", StringComparison.Ordinal))
return null;
var relativePath = normalizedPath var relativePath = normalizedPath.Substring(rootPath.Length + 1).Split('/');
.Substring(rootPath.Length + 1) if (relativePath.Length < 2)
.Split('/'); return null;
if (relativePath.Length < 2) return null; var atlasNamePart = string.Join("_", relativePath, 0, relativePath.Length - 1);
var directories = relativePath.Take(relativePath.Length - 1);
var atlasNamePart = string.Join("_", directories);
var rootFolderName = Path.GetFileName(rootPath); var rootFolderName = Path.GetFileName(rootPath);
return $"{rootFolderName}_{atlasNamePart}"; 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) private static string GetAtlasNameForDirectory(string directoryPath)
{ {
var normalizedPath = directoryPath.Replace("\\", "/"); var normalizedPath = NormalizePath(directoryPath);
var rootPath = Config.sourceAtlasRoot.Replace("\\", "/").TrimEnd('/'); var rootPath = NormalizedSourceRoot;
if (!normalizedPath.StartsWith(rootPath + "/")) return null; if (!normalizedPath.StartsWith(rootPath + "/", StringComparison.Ordinal))
return null;
var relativePath = normalizedPath var relativePath = normalizedPath.Substring(rootPath.Length + 1).Split('/');
.Substring(rootPath.Length + 1) if (relativePath.Length == 0)
.Split('/'); return null;
var atlasNamePart = string.Join("_", relativePath); var atlasNamePart = string.Join("_", relativePath);
var rootFolderName = Path.GetFileName(rootPath); var rootFolderName = Path.GetFileName(rootPath);
return $"{rootFolderName}_{atlasNamePart}"; return $"{rootFolderName}_{atlasNamePart}";
} }
private static void AddSpritePath(string atlasName, string assetPath)
{
if (!AtlasMap.TryGetValue(atlasName, out var spritePaths))
{
spritePaths = new HashSet<string>(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 #endif

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 6e0aa32db2943f742a839708e928fda4 guid: 6e0aa32db2943f742a839708e928fda4
timeCreated: 1738748262 MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,9 +1,20 @@
using System.Linq;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
public class SpritePostprocessor : AssetPostprocessor 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( private static void OnPostprocessAllAssets(
string[] importedAssets, string[] importedAssets,
string[] deletedAssets, string[] deletedAssets,
@ -11,8 +22,8 @@ public class SpritePostprocessor : AssetPostprocessor
string[] movedFromAssetPaths) string[] movedFromAssetPaths)
{ {
var config = AtlasConfiguration.Instance; var config = AtlasConfiguration.Instance;
if (!config.autoGenerate)
if (!config.autoGenerate) return; return;
try try
{ {
@ -27,10 +38,6 @@ public class SpritePostprocessor : AssetPostprocessor
{ {
Debug.LogError($"Atlas processing error: {e.Message}\n{e.StackTrace}"); Debug.LogError($"Atlas processing error: {e.Message}\n{e.StackTrace}");
} }
finally
{
AssetDatabase.Refresh();
}
} }
private static void ProcessAssetChanges( private static void ProcessAssetChanges(
@ -39,14 +46,14 @@ public class SpritePostprocessor : AssetPostprocessor
string[] movedAssets, string[] movedAssets,
string[] movedFromPaths) string[] movedFromPaths)
{ {
ProcessAssets(importedAssets, (path) => ProcessAssets(importedAssets, path =>
{ {
EditorSpriteSaveInfo.ConvertToSprite(path); EditorSpriteSaveInfo.ConvertToSprite(path);
EditorSpriteSaveInfo.OnImportSprite(path); EditorSpriteSaveInfo.OnImportSprite(path);
LogProcessed("[Added]", path); LogProcessed("[Added]", path);
}); });
ProcessAssets(deletedAssets, (path) => ProcessAssets(deletedAssets, path =>
{ {
EditorSpriteSaveInfo.OnDeleteSprite(path); EditorSpriteSaveInfo.OnDeleteSprite(path);
LogProcessed("[Deleted]", path); LogProcessed("[Deleted]", path);
@ -57,77 +64,52 @@ public class SpritePostprocessor : AssetPostprocessor
private static void ProcessAssets(string[] assets, System.Action<string> processor) private static void ProcessAssets(string[] assets, System.Action<string> processor)
{ {
if (assets == null) return; if (assets == null)
return;
foreach (var asset in assets) foreach (var asset in assets)
{ {
if (ShouldProcessAsset(asset)) if (ShouldProcessAsset(asset))
{
processor?.Invoke(asset); processor?.Invoke(asset);
} }
} }
}
private static void ProcessMovedAssets(string[] oldPaths, string[] newPaths) 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])) if (ShouldProcessAsset(oldPaths[i]))
{ {
EditorSpriteSaveInfo.OnDeleteSprite(oldPaths[i]); EditorSpriteSaveInfo.OnDeleteSprite(oldPaths[i]);
LogProcessed("[Moved From]", oldPaths[i]); LogProcessed("[Moved From]", oldPaths[i]);
EditorSpriteSaveInfo.MarkParentAtlasesDirty(oldPaths[i]);
} }
if (ShouldProcessAsset(newPaths[i])) if (ShouldProcessAsset(newPaths[i]))
{ {
EditorSpriteSaveInfo.ConvertToSprite(newPaths[i]);
EditorSpriteSaveInfo.OnImportSprite(newPaths[i]); EditorSpriteSaveInfo.OnImportSprite(newPaths[i]);
LogProcessed("[Moved To]", newPaths[i]); LogProcessed("[Moved To]", newPaths[i]);
EditorSpriteSaveInfo.MarkParentAtlasesDirty(newPaths[i]);
} }
} }
} }
private static bool ShouldProcessAsset(string assetPath) private static bool ShouldProcessAsset(string assetPath)
{ {
var config = AtlasConfiguration.Instance; if (string.IsNullOrEmpty(assetPath))
if (string.IsNullOrEmpty(assetPath)) 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 false;
}
return true; if (assetPath.StartsWith("Packages/"))
} return false;
private static bool IsValidImageFile(string path) return EditorSpriteSaveInfo.ShouldProcess(assetPath);
{
var ext = System.IO.Path.GetExtension(path).ToLower();
return ext switch
{
".png" => true,
".jpg" => true,
".jpeg" => true,
_ => false
};
} }
private static void LogProcessed(string operation, string path) private static void LogProcessed(string operation, string path)
{ {
if (AtlasConfiguration.Instance.enableLogging) if (AtlasConfiguration.Instance.enableLogging)
{
Debug.Log($"{operation} {System.IO.Path.GetFileName(path)}\nPath: {path}"); Debug.Log($"{operation} {System.IO.Path.GetFileName(path)}\nPath: {path}");
} }
} }
}

View File

@ -1,3 +1,11 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: e5d969d76e1a36445810f61bafc4f7db guid: e5d969d76e1a36445810f61bafc4f7db
timeCreated: 1738748276 MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: