using System; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.U2D.Animation; using Object = UnityEngine.Object; namespace UnityEditor.U2D.Animation { /// /// Represents a Sprite Library's label. /// [Serializable] public class SpriteLibraryLabel : ISpriteLibraryLabel { /// /// Label's name. /// public string name => m_Name; /// /// Sprite associated with the label. /// public Sprite sprite => m_Sprite; [SerializeField] string m_Name; [SerializeField] Sprite m_Sprite; /// /// Constructs a new label. /// /// Label's name. /// Label's Sprite. public SpriteLibraryLabel(string labelName, Sprite labelSprite) { m_Name = labelName; m_Sprite = labelSprite; } } /// /// Represents a Sprite Library's category. /// [Serializable] public class SpriteLibraryCategory : ISpriteLibraryCategory { /// /// Category's name. /// public string name => m_Name; /// /// List of labels in category. /// public IEnumerable labels => m_Labels; [SerializeField] List m_Labels; [SerializeField] string m_Name; /// /// Constructs a new category. /// /// Category's name. /// Collection of labels in a category. public SpriteLibraryCategory(string categoryName, IEnumerable categoryLabels) { m_Name = categoryName; m_Labels = new List(categoryLabels); } } /// /// Class used for creating new Sprite Library Source Assets. /// public static class SpriteLibrarySourceAssetFactory { /// /// Sprite Library Source Asset's extension. /// public const string extension = SpriteLibrarySourceAsset.extension; /// /// Creates a new Sprite Library Source Asset at a given path. /// /// Save path. Must be within the Assets folder. /// Collection of categories in the library. /// A path to the main library. Null if there is no main library. /// A relative path to the Project with correct extension. /// Throws when the save path is invalid/ public static string Create(string path, IEnumerable categories, string mainLibraryPath = null) { if (string.IsNullOrEmpty(path)) throw new InvalidOperationException("Save path cannot be null or empty."); var relativePath = GetRelativePath(path); if (string.IsNullOrEmpty(relativePath)) throw new InvalidOperationException($"{nameof(LoadSpriteLibrarySourceAsset)} can only be saved in the Assets folder."); relativePath = Path.ChangeExtension(relativePath, extension); SpriteLibraryAsset mainLibrary = null; if (!string.IsNullOrEmpty(mainLibraryPath)) { mainLibrary = AssetDatabase.LoadAssetAtPath(mainLibraryPath); if (mainLibrary == null) throw new InvalidOperationException($"No {nameof(SpriteLibraryAsset)} found at path: '{mainLibraryPath}'"); } var asset = ScriptableObject.CreateInstance(); var categoryList = new List(); if (categories != null) { foreach (var category in categories) { var spriteLibCategory = new SpriteLibCategoryOverride { name = category.name, overrideEntries = new List() }; foreach (var label in category.labels) { var spriteCategoryEntryOverride = new SpriteCategoryEntryOverride { name = label.name, spriteOverride = label.sprite }; spriteLibCategory.overrideEntries.Add(spriteCategoryEntryOverride); } categoryList.Add(spriteLibCategory); } } if (mainLibrary != null) { asset.SetPrimaryLibraryGUID(AssetDatabase.GUIDFromAssetPath(mainLibraryPath).ToString()); var newCategories = mainLibrary.categories ?? new List(); var existingCategories = new List(categoryList); categoryList.Clear(); // populate new primary foreach (var newCategory in newCategories) { var labels = new List(); SpriteLibCategoryOverride existingCategory = null; for (var i = 0; i < existingCategories.Count; i++) { var category = existingCategories[i]; if (category.name == newCategory.name) { existingCategory = category; existingCategory.fromMain = true; existingCategories.RemoveAt(i); break; } } var newEntries = newCategory.categoryList; foreach (var newEntry in newEntries) { var sprite = newEntry.sprite; labels.Add(new SpriteCategoryEntryOverride { name = newEntry.name, sprite = sprite, spriteOverride = sprite, fromMain = true }); } var overrideCount = 0; if (existingCategory != null) { foreach (var existingLabel in existingCategory.overrideEntries) { var foundLabel = false; foreach (var newLabel in labels) { if (existingLabel.name == newLabel.name) { if (newLabel.spriteOverride != existingLabel.spriteOverride) { newLabel.spriteOverride = existingLabel.spriteOverride; overrideCount++; } foundLabel = true; break; } } if (!foundLabel) { overrideCount++; labels.Add(new SpriteCategoryEntryOverride { name = existingLabel.name, sprite = existingLabel.sprite, spriteOverride = existingLabel.spriteOverride, fromMain = false }); } } } categoryList.Add(new SpriteLibCategoryOverride { name = newCategory.name, overrideEntries = labels, fromMain = true, entryOverrideCount = overrideCount }); } foreach (var existingCategory in existingCategories) { var keepCategory = false; if (existingCategory.fromMain) { for (var i = existingCategory.overrideEntries.Count; i-- > 0;) { var entry = existingCategory.overrideEntries[i]; if (!entry.fromMain || entry.sprite != entry.spriteOverride) { entry.fromMain = false; entry.sprite = entry.spriteOverride; keepCategory = true; } else existingCategory.overrideEntries.RemoveAt(i); } } if (!existingCategory.fromMain || keepCategory) { existingCategory.fromMain = false; existingCategory.entryOverrideCount = 0; categoryList.Add(existingCategory); } } } asset.SetLibrary(categoryList); SpriteLibrarySourceAssetImporter.SaveSpriteLibrarySourceAsset(asset, relativePath); Object.DestroyImmediate(asset); return relativePath; } /// /// Creates a new Sprite Library Source Asset at a given path. /// /// Save path. Must be within the Assets folder. /// Sprite Library Asset to be saved. /// A path to the main library. Null if there is no main library. /// A relative path to the Project with correct extension. /// Throws when the save path is invalid/ public static string Create(string path, SpriteLibraryAsset spriteLibraryAsset, string mainLibraryPath = null) { return Create(path, spriteLibraryAsset.categories, mainLibraryPath); } /// /// Creates a new Sprite Library Source Asset at a given path. /// /// Sprite Library Asset to be saved. /// Save path. Must be within the Assets folder. /// A path to the main library. Null if there is no main library. /// A relative path to the Project with correct extension. /// Throws when the save path is invalid/ public static string SaveAsSourceAsset(this SpriteLibraryAsset spriteLibraryAsset, string path, string mainLibraryPath = null) { return Create(path, spriteLibraryAsset, mainLibraryPath); } internal static SpriteLibrarySourceAsset LoadSpriteLibrarySourceAsset(string path) { var loadedObjects = UnityEditorInternal.InternalEditorUtility.LoadSerializedFileAndForget(path); foreach (var obj in loadedObjects) { if (obj is SpriteLibrarySourceAsset asset) return asset; } return null; } static string GetRelativePath(string path) { if (string.IsNullOrWhiteSpace(path)) return null; if (!path.StartsWith("Assets/") && !path.StartsWith(Application.dataPath)) return null; var pathStartIndex = path.IndexOf("Assets"); return pathStartIndex == -1 ? null : path.Substring(pathStartIndex); } } }