// [UNITY-SKILL:SPRITEATLAS] // [TYPE:PREBUILD] // Generated by SpriteAtlas V2 Skill // This script automatically generates SpriteAtlas assets during prebuild using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; using UnityEditor.U2D; using UnityEngine; using UnityEngine.U2D; using System.Linq; using System.Collections.Generic; #if UNITY_ADDRESSABLES using UnityEditor.AddressableAssets; using UnityEditor.AddressableAssets.Settings; #endif public class SpriteAtlasPrebuildGenerator : IPreprocessBuildWithReport { public int callbackOrder => 0; private List generatedAtlasPaths = new List(); public void OnPreprocessBuild(BuildReport report) { Debug.Log("[SpriteAtlas] Generating atlases for late-binding via Addressables..."); // CRITICAL: Enable Sprite Packer mode EditorSettings.spritePackerMode = SpritePackerMode.SpriteAtlasV2; generatedAtlasPaths.Clear(); // Customize these paths - includeInBuild is FALSE for Addressables GenerateAtlasByFolder("Assets/Art/UI", "Assets/Atlases/UI_Atlas.spriteatlasv2", includeInBuild: false); GenerateAtlasByFolder("Assets/Art/Characters", "Assets/Atlases/Characters_Atlas.spriteatlasv2", includeInBuild: false); GenerateAtlasByNaming("Assets/Art/Items", "item_", "Assets/Atlases/Items_Atlas.spriteatlasv2", includeInBuild: false); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); // Setup Addressables for all generated atlases SetupAddressables(); } private void SetupAddressables() { #if UNITY_ADDRESSABLES var settings = AddressableAssetSettingsDefaultObject.Settings; if (settings == null) { Debug.LogError("[SpriteAtlas] Addressables is not initialized. Please install and setup Addressables package."); return; } var group = settings.DefaultGroup; if (group == null) { Debug.LogError("[SpriteAtlas] Default Addressables group not found."); return; } foreach (var atlasPath in generatedAtlasPaths) { var guid = AssetDatabase.AssetPathToGUID(atlasPath); if (string.IsNullOrEmpty(guid)) { Debug.LogWarning($"[SpriteAtlas] Could not find GUID for {atlasPath}"); continue; } // Check if already exists var existingEntry = settings.FindAssetEntry(guid); if (existingEntry != null) { Debug.Log($"[SpriteAtlas] Atlas already addressable: {atlasPath}"); continue; } // Create addressable entry var entry = settings.CreateOrMoveEntry(guid, group, false, false); var atlasName = System.IO.Path.GetFileNameWithoutExtension(atlasPath); entry.address = atlasName; // Use atlas name as address Debug.Log($"[SpriteAtlas] Added to Addressables: {atlasName} at {atlasPath}"); } settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryAdded, null, true); #else Debug.LogError("[SpriteAtlas] Addressables package is not installed. Please install 'com.unity.addressables' package."); #endif } // CRITICAL: Validate sprite is from project Assets folder only private bool IsValidProjectSprite(string assetPath) { return assetPath.StartsWith("Assets/") && !assetPath.Contains("/Packages/"); } private void GenerateAtlasByFolder(string spriteFolder, string atlasPath, bool includeInBuild) { // Find all sprites in folder - with path validation string[] guids = AssetDatabase.FindAssets("t:Sprite", new[] { spriteFolder }); Object[] sprites = guids .Select(g => AssetDatabase.GUIDToAssetPath(g)) .Where(path => IsValidProjectSprite(path)) .Select(path => AssetDatabase.LoadAssetAtPath(path)) .Where(s => s != null) .Cast() .ToArray(); if (sprites.Length == 0) { Debug.LogWarning($"[SpriteAtlas] No sprites found in {spriteFolder}"); return; } // Create or load existing atlas SpriteAtlasAsset atlasAsset; if (System.IO.File.Exists(atlasPath)) { atlasAsset = SpriteAtlasAsset.Load(atlasPath); var spriteAtlas = AssetDatabase.LoadAssetAtPath(atlasPath); atlasAsset.Remove(spriteAtlas.GetPackables()); // Clear existing } else { atlasAsset = new SpriteAtlasAsset(); System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(atlasPath)); } // Add sprites and save atlasAsset.Add(sprites); SpriteAtlasAsset.Save(atlasAsset, atlasPath); AssetDatabase.ImportAsset(atlasPath); // Configure via importer ConfigureAtlasImporter(atlasPath, includeInBuild); // Track for Addressables setup generatedAtlasPaths.Add(atlasPath); Debug.Log($"[SpriteAtlas] Generated {atlasPath} with {sprites.Length} sprites (includeInBuild={includeInBuild})"); } private void GenerateAtlasByNaming(string searchFolder, string namePrefix, string atlasPath, bool includeInBuild) { // Find sprites matching naming convention - with path validation string[] guids = AssetDatabase.FindAssets("t:Sprite", new[] { searchFolder }); Object[] sprites = guids .Select(g => AssetDatabase.GUIDToAssetPath(g)) .Where(path => IsValidProjectSprite(path)) .Select(path => AssetDatabase.LoadAssetAtPath(path)) .Where(s => s != null && s.name.StartsWith(namePrefix)) .Cast() .ToArray(); if (sprites.Length == 0) return; // Create or update atlas (same pattern as folder-based) SpriteAtlasAsset atlasAsset = System.IO.File.Exists(atlasPath) ? SpriteAtlasAsset.Load(atlasPath) : new SpriteAtlasAsset(); if (System.IO.File.Exists(atlasPath)) { var spriteAtlas = AssetDatabase.LoadAssetAtPath(atlasPath); atlasAsset.Remove(spriteAtlas.GetPackables()); } atlasAsset.Add(sprites); SpriteAtlasAsset.Save(atlasAsset, atlasPath); AssetDatabase.ImportAsset(atlasPath); ConfigureAtlasImporter(atlasPath, includeInBuild); // Track for Addressables setup generatedAtlasPaths.Add(atlasPath); } private void ConfigureAtlasImporter(string atlasPath, bool includeInBuild) { SpriteAtlasImporter importer = AssetImporter.GetAtPath(atlasPath) as SpriteAtlasImporter; if (importer == null) return; // Texture settings var textureSettings = importer.textureSettings; textureSettings.filterMode = FilterMode.Bilinear; textureSettings.generateMipMaps = false; importer.textureSettings = textureSettings; // Packing settings var packingSettings = importer.packingSettings; packingSettings.padding = 4; packingSettings.enableAlphaDilation = true; importer.packingSettings = packingSettings; // Platform settings SetPlatformSettings(importer, "Android", TextureImporterFormat.ASTC_6x6); SetPlatformSettings(importer, "iOS", TextureImporterFormat.ASTC_4x4); // Set build inclusion based on delivery mechanism importer.includeInBuild = includeInBuild; importer.SaveAndReimport(); } private void SetPlatformSettings(SpriteAtlasImporter importer, string platformName, TextureImporterFormat format) { var settings = new TextureImporterPlatformSettings { name = platformName, overridden = true, maxTextureSize = 2048, format = format }; importer.SetPlatformSettings(settings); } }