← Files UnityARCHIVED FILE

skills/manage-sprite-atlas/resources/spriteatlasprebuildgenerator.cs

7.9 KB · Oct 3, 2026 · 06:34 UTC

↓ Download file

// [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<string> generatedAtlasPaths = new List<string>();

    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<Sprite>(path))
            .Where(s => s != null)
            .Cast<Object>()
            .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<SpriteAtlas>(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<Sprite>(path))
            .Where(s => s != null && s.name.StartsWith(namePrefix))
            .Cast<Object>()
            .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<SpriteAtlas>(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);
    }
}

SHA-256: c590f3d8ff89da10030233a4c769ccea049f9bcf271337decd600de4c69edeeb