优化图集导入模块
This commit is contained in:
parent
8d9b4a32ce
commit
28edc2dfa7
@ -47,7 +47,6 @@ public class AtlasConfigWindow : EditorWindow
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
AtlasConfiguration.Save(true);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<string> _dirtyAtlasNames = new HashSet<string>();
|
||||
private static readonly Dictionary<string, List<string>> _atlasMap = new Dictionary<string, List<string>>();
|
||||
private static readonly HashSet<string> DirtyAtlasNames = new HashSet<string>(StringComparer.Ordinal);
|
||||
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 _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<string>();
|
||||
_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<SpriteAtlas>(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<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);
|
||||
|
||||
if (atlas != null)
|
||||
{
|
||||
var allSprites = new List<Sprite>();
|
||||
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<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)
|
||||
{
|
||||
// 加载所有子图
|
||||
var sprites = AssetDatabase.LoadAllAssetsAtPath(assetPath)
|
||||
.OfType<Sprite>()
|
||||
.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<Sprite>();
|
||||
|
||||
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<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
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
fileFormatVersion: 2
|
||||
guid: 6e0aa32db2943f742a839708e928fda4
|
||||
timeCreated: 1738748262
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@ -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<string> 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
fileFormatVersion: 2
|
||||
guid: e5d969d76e1a36445810f61bafc4f7db
|
||||
timeCreated: 1738748276
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user