A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.IO;
4using UnityEngine;
5using UnityEngine.U2D.Animation;
6using Object = UnityEngine.Object;
7
8namespace UnityEditor.U2D.Animation
9{
10 /// <summary>
11 /// Represents a Sprite Library's label.
12 /// </summary>
13 [Serializable]
14 public class SpriteLibraryLabel : ISpriteLibraryLabel
15 {
16 /// <summary>
17 /// Label's name.
18 /// </summary>
19 public string name => m_Name;
20
21 /// <summary>
22 /// Sprite associated with the label.
23 /// </summary>
24 public Sprite sprite => m_Sprite;
25
26 [SerializeField]
27 string m_Name;
28
29 [SerializeField]
30 Sprite m_Sprite;
31
32 /// <summary>
33 /// Constructs a new label.
34 /// </summary>
35 /// <param name="labelName">Label's name.</param>
36 /// <param name="labelSprite">Label's Sprite.</param>
37 public SpriteLibraryLabel(string labelName, Sprite labelSprite)
38 {
39 m_Name = labelName;
40 m_Sprite = labelSprite;
41 }
42 }
43
44 /// <summary>
45 /// Represents a Sprite Library's category.
46 /// </summary>
47 [Serializable]
48 public class SpriteLibraryCategory : ISpriteLibraryCategory
49 {
50 /// <summary>
51 /// Category's name.
52 /// </summary>
53 public string name => m_Name;
54
55 /// <summary>
56 /// List of labels in category.
57 /// </summary>
58 public IEnumerable<ISpriteLibraryLabel> labels => m_Labels;
59
60 [SerializeField]
61 List<SpriteLibraryLabel> m_Labels;
62
63 [SerializeField]
64 string m_Name;
65
66 /// <summary>
67 /// Constructs a new category.
68 /// </summary>
69 /// <param name="categoryName">Category's name.</param>
70 /// <param name="categoryLabels">Collection of labels in a category.</param>
71 public SpriteLibraryCategory(string categoryName, IEnumerable<SpriteLibraryLabel> categoryLabels)
72 {
73 m_Name = categoryName;
74 m_Labels = new List<SpriteLibraryLabel>(categoryLabels);
75 }
76 }
77
78 /// <summary>
79 /// Class used for creating new Sprite Library Source Assets.
80 /// </summary>
81 public static class SpriteLibrarySourceAssetFactory
82 {
83 /// <summary>
84 /// Sprite Library Source Asset's extension.
85 /// </summary>
86 public const string extension = SpriteLibrarySourceAsset.extension;
87
88 /// <summary>
89 /// Creates a new Sprite Library Source Asset at a given path.
90 /// </summary>
91 /// <param name="path">Save path. Must be within the Assets folder.</param>
92 /// <param name="categories">Collection of categories in the library.</param>
93 /// <param name="mainLibraryPath">A path to the main library. Null if there is no main library.</param>
94 /// <returns>A relative path to the Project with correct extension.</returns>
95 /// <exception cref="InvalidOperationException">Throws when the save path is invalid/</exception>
96 public static string Create(string path, IEnumerable<ISpriteLibraryCategory> categories, string mainLibraryPath = null)
97 {
98 if (string.IsNullOrEmpty(path))
99 throw new InvalidOperationException("Save path cannot be null or empty.");
100
101 var relativePath = GetRelativePath(path);
102 if (string.IsNullOrEmpty(relativePath))
103 throw new InvalidOperationException($"{nameof(LoadSpriteLibrarySourceAsset)} can only be saved in the Assets folder.");
104
105 relativePath = Path.ChangeExtension(relativePath, extension);
106
107 SpriteLibraryAsset mainLibrary = null;
108 if (!string.IsNullOrEmpty(mainLibraryPath))
109 {
110 mainLibrary = AssetDatabase.LoadAssetAtPath<SpriteLibraryAsset>(mainLibraryPath);
111 if (mainLibrary == null)
112 throw new InvalidOperationException($"No {nameof(SpriteLibraryAsset)} found at path: '{mainLibraryPath}'");
113 }
114
115 var asset = ScriptableObject.CreateInstance<SpriteLibrarySourceAsset>();
116 var categoryList = new List<SpriteLibCategoryOverride>();
117 if (categories != null)
118 {
119 foreach (var category in categories)
120 {
121 var spriteLibCategory = new SpriteLibCategoryOverride
122 {
123 name = category.name,
124 overrideEntries = new List<SpriteCategoryEntryOverride>()
125 };
126 foreach (var label in category.labels)
127 {
128 var spriteCategoryEntryOverride = new SpriteCategoryEntryOverride
129 {
130 name = label.name,
131 spriteOverride = label.sprite
132 };
133 spriteLibCategory.overrideEntries.Add(spriteCategoryEntryOverride);
134 }
135
136 categoryList.Add(spriteLibCategory);
137 }
138 }
139
140 if (mainLibrary != null)
141 {
142 asset.SetPrimaryLibraryGUID(AssetDatabase.GUIDFromAssetPath(mainLibraryPath).ToString());
143
144 var newCategories = mainLibrary.categories ?? new List<SpriteLibCategory>();
145
146 var existingCategories = new List<SpriteLibCategoryOverride>(categoryList);
147 categoryList.Clear();
148
149 // populate new primary
150 foreach (var newCategory in newCategories)
151 {
152 var labels = new List<SpriteCategoryEntryOverride>();
153 SpriteLibCategoryOverride existingCategory = null;
154 for (var i = 0; i < existingCategories.Count; i++)
155 {
156 var category = existingCategories[i];
157 if (category.name == newCategory.name)
158 {
159 existingCategory = category;
160 existingCategory.fromMain = true;
161 existingCategories.RemoveAt(i);
162 break;
163 }
164 }
165
166 var newEntries = newCategory.categoryList;
167 foreach (var newEntry in newEntries)
168 {
169 var sprite = newEntry.sprite;
170
171 labels.Add(new SpriteCategoryEntryOverride
172 {
173 name = newEntry.name,
174 sprite = sprite,
175 spriteOverride = sprite,
176 fromMain = true
177 });
178 }
179
180 var overrideCount = 0;
181 if (existingCategory != null)
182 {
183 foreach (var existingLabel in existingCategory.overrideEntries)
184 {
185 var foundLabel = false;
186 foreach (var newLabel in labels)
187 {
188 if (existingLabel.name == newLabel.name)
189 {
190 if (newLabel.spriteOverride != existingLabel.spriteOverride)
191 {
192 newLabel.spriteOverride = existingLabel.spriteOverride;
193 overrideCount++;
194 }
195
196 foundLabel = true;
197 break;
198 }
199 }
200
201 if (!foundLabel)
202 {
203 overrideCount++;
204 labels.Add(new SpriteCategoryEntryOverride
205 {
206 name = existingLabel.name,
207 sprite = existingLabel.sprite,
208 spriteOverride = existingLabel.spriteOverride,
209 fromMain = false
210 });
211 }
212 }
213 }
214
215 categoryList.Add(new SpriteLibCategoryOverride
216 {
217 name = newCategory.name,
218 overrideEntries = labels,
219 fromMain = true,
220 entryOverrideCount = overrideCount
221 });
222 }
223
224 foreach (var existingCategory in existingCategories)
225 {
226 var keepCategory = false;
227 if (existingCategory.fromMain)
228 {
229 for (var i = existingCategory.overrideEntries.Count; i-- > 0;)
230 {
231 var entry = existingCategory.overrideEntries[i];
232 if (!entry.fromMain || entry.sprite != entry.spriteOverride)
233 {
234 entry.fromMain = false;
235 entry.sprite = entry.spriteOverride;
236 keepCategory = true;
237 }
238 else
239 existingCategory.overrideEntries.RemoveAt(i);
240 }
241 }
242
243 if (!existingCategory.fromMain || keepCategory)
244 {
245 existingCategory.fromMain = false;
246 existingCategory.entryOverrideCount = 0;
247 categoryList.Add(existingCategory);
248 }
249 }
250 }
251
252 asset.SetLibrary(categoryList);
253
254 SpriteLibrarySourceAssetImporter.SaveSpriteLibrarySourceAsset(asset, relativePath);
255 Object.DestroyImmediate(asset);
256 return relativePath;
257 }
258
259 /// <summary>
260 /// Creates a new Sprite Library Source Asset at a given path.
261 /// </summary>
262 /// <param name="path">Save path. Must be within the Assets folder.</param>
263 /// <param name="spriteLibraryAsset">Sprite Library Asset to be saved.</param>
264 /// <param name="mainLibraryPath">A path to the main library. Null if there is no main library.</param>
265 /// <returns>A relative path to the Project with correct extension.</returns>
266 /// <exception cref="InvalidOperationException">Throws when the save path is invalid/</exception>
267 public static string Create(string path, SpriteLibraryAsset spriteLibraryAsset, string mainLibraryPath = null)
268 {
269 return Create(path, spriteLibraryAsset.categories, mainLibraryPath);
270 }
271
272 /// <summary>
273 /// Creates a new Sprite Library Source Asset at a given path.
274 /// </summary>
275 /// <param name="spriteLibraryAsset">Sprite Library Asset to be saved.</param>
276 /// <param name="path">Save path. Must be within the Assets folder.</param>
277 /// <param name="mainLibraryPath">A path to the main library. Null if there is no main library.</param>
278 /// <returns>A relative path to the Project with correct extension.</returns>
279 /// <exception cref="InvalidOperationException">Throws when the save path is invalid/</exception>
280 public static string SaveAsSourceAsset(this SpriteLibraryAsset spriteLibraryAsset, string path, string mainLibraryPath = null)
281 {
282 return Create(path, spriteLibraryAsset, mainLibraryPath);
283 }
284
285 internal static SpriteLibrarySourceAsset LoadSpriteLibrarySourceAsset(string path)
286 {
287 var loadedObjects = UnityEditorInternal.InternalEditorUtility.LoadSerializedFileAndForget(path);
288 foreach (var obj in loadedObjects)
289 {
290 if (obj is SpriteLibrarySourceAsset asset)
291 return asset;
292 }
293
294 return null;
295 }
296
297 static string GetRelativePath(string path)
298 {
299 if (string.IsNullOrWhiteSpace(path))
300 return null;
301
302 if (!path.StartsWith("Assets/") && !path.StartsWith(Application.dataPath))
303 return null;
304
305 var pathStartIndex = path.IndexOf("Assets");
306 return pathStartIndex == -1 ? null : path.Substring(pathStartIndex);
307 }
308 }
309}