com.wallstop-studios.unity-helpers 2.0.0-rc68 → 2.0.0-rc70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/Editor/AnimationCopier.cs +875 -93
  2. package/Editor/AnimationCreator.cs +840 -137
  3. package/Editor/AnimationEventEditor.cs +4 -4
  4. package/Editor/AnimatorControllerCopier.cs +3 -3
  5. package/Editor/Extensions/UnityExtensions.cs +26 -0
  6. package/Editor/Extensions/UnityExtensions.cs.meta +3 -0
  7. package/Editor/Extensions.meta +3 -0
  8. package/Editor/FitTextureSizeWindow.cs +371 -0
  9. package/Editor/PrefabChecker.cs +716 -0
  10. package/Editor/SpriteAtlasGenerator.cs +598 -0
  11. package/Editor/SpriteAtlasGenerator.cs.meta +3 -0
  12. package/Editor/SpriteCropper.cs +407 -0
  13. package/Editor/SpriteCropper.cs.meta +3 -0
  14. package/Editor/SpriteSettingsApplier.cs +756 -92
  15. package/Editor/TextureResizerWizard.cs +3 -3
  16. package/Editor/TextureSettingsApplier.cs +9 -9
  17. package/Editor/WShowIfPropertyDrawer.cs +2 -2
  18. package/Runtime/Core/Attributes/EnumDisplayNameAttribute.cs +15 -0
  19. package/Runtime/Core/Attributes/EnumDisplayNameAttribute.cs.meta +3 -0
  20. package/Runtime/Core/Attributes/ValidateAssignmentAttribute.cs +1 -1
  21. package/Runtime/Core/Extension/EnumExtensions.cs +176 -1
  22. package/Runtime/Core/Extension/UnityExtensions.cs +1 -1
  23. package/Runtime/Core/Helper/Partials/LogHelpers.cs +1 -1
  24. package/Runtime/Core/Helper/Partials/MathHelpers.cs +1 -1
  25. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +3 -4
  26. package/Runtime/Tags/Attribute.cs +205 -0
  27. package/Runtime/Tags/Attribute.cs.meta +3 -0
  28. package/Runtime/Tags/AttributeEffect.cs +276 -0
  29. package/Runtime/Tags/AttributeEffect.cs.meta +3 -0
  30. package/Runtime/Tags/AttributeModification.cs +51 -0
  31. package/Runtime/Tags/AttributeModification.cs.meta +3 -0
  32. package/Runtime/Tags/AttributeUtilities.cs +209 -0
  33. package/Runtime/Tags/AttributeUtilities.cs.meta +3 -0
  34. package/Runtime/Tags/AttributesComponent.cs +163 -0
  35. package/Runtime/Tags/AttributesComponent.cs.meta +3 -0
  36. package/Runtime/Tags/CosmeticEffectComponent.cs +50 -0
  37. package/Runtime/Tags/CosmeticEffectComponent.cs.meta +3 -0
  38. package/Runtime/Tags/CosmeticEffectData.cs +63 -0
  39. package/Runtime/Tags/CosmeticEffectData.cs.meta +3 -0
  40. package/Runtime/Tags/EffectHandle.cs +63 -0
  41. package/Runtime/Tags/EffectHandle.cs.meta +3 -0
  42. package/Runtime/Tags/EffectHandler.cs +380 -0
  43. package/Runtime/Tags/EffectHandler.cs.meta +3 -0
  44. package/Runtime/Tags/ModificationAction.cs +9 -0
  45. package/Runtime/Tags/ModificationAction.cs.meta +3 -0
  46. package/Runtime/Tags/ModifierDurationType.cs +13 -0
  47. package/Runtime/Tags/ModifierDurationType.cs.meta +3 -0
  48. package/Runtime/{Utils → Tags}/TagHandler.cs +42 -5
  49. package/Runtime/Tags.meta +3 -0
  50. package/Tests/Runtime/DataStructures/BalancedKDTreeTests.cs +1 -1
  51. package/Tests/Runtime/DataStructures/CyclicBufferTests.cs +1 -1
  52. package/Tests/Runtime/DataStructures/QuadTreeTests.cs +1 -1
  53. package/Tests/Runtime/DataStructures/SpatialTreeTests.cs +1 -1
  54. package/Tests/Runtime/DataStructures/UnbalancedKDTreeTests.cs +1 -1
  55. package/Tests/Runtime/Extensions/DictionaryExtensionTests.cs +1 -1
  56. package/Tests/Runtime/Extensions/EnumExtensionTests.cs +1 -1
  57. package/Tests/Runtime/Extensions/IListExtensionTests.cs +1 -1
  58. package/Tests/Runtime/Extensions/LoggingExtensionTests.cs +1 -1
  59. package/Tests/Runtime/Extensions/RandomExtensionTests.cs +1 -1
  60. package/Tests/Runtime/Extensions/StringExtensionTests.cs +1 -1
  61. package/Tests/Runtime/Helper/ObjectHelperTests.cs +1 -0
  62. package/Tests/Runtime/Helper/WallMathTests.cs +1 -1
  63. package/Tests/Runtime/Performance/KDTreePerformanceTests.cs +1 -1
  64. package/Tests/Runtime/Performance/QuadTreePerformanceTests.cs +1 -1
  65. package/Tests/Runtime/Performance/SpatialTreePerformanceTest.cs +1 -1
  66. package/Tests/Runtime/Performance/UnbalancedKDTreeTests.cs +1 -1
  67. package/Tests/Runtime/Random/RandomTestBase.cs +2 -2
  68. package/Tests/Runtime/Serialization/JsonSerializationTest.cs +1 -1
  69. package/package.json +1 -1
  70. package/Editor/FitTextureSizeWizard.cs +0 -147
  71. package/Editor/PrefabCheckWizard.cs +0 -167
  72. /package/Editor/{FitTextureSizeWizard.cs.meta → FitTextureSizeWindow.cs.meta} +0 -0
  73. /package/Editor/{PrefabCheckWizard.cs.meta → PrefabChecker.cs.meta} +0 -0
  74. /package/Runtime/{Utils → Tags}/TagHandler.cs.meta +0 -0
@@ -0,0 +1,598 @@
1
+ namespace WallstopStudios.UnityHelpers.Editor
2
+ {
3
+ #if UNITY_EDITOR
4
+ using System;
5
+ using System.Collections.Generic;
6
+ using System.IO;
7
+ using System.Linq;
8
+ using System.Text.RegularExpressions;
9
+ using Core.Extension;
10
+ using Extensions;
11
+ using UnityEditor;
12
+ using UnityEditor.U2D;
13
+ using UnityEngine;
14
+ using UnityEngine.U2D;
15
+ using Object = UnityEngine.Object;
16
+
17
+ public sealed class SpriteAtlasGenerator : EditorWindow
18
+ {
19
+ private const string Name = "Sprite Atlas Generator";
20
+ private const string DefaultPlatformName = "DefaultTexturePlatform";
21
+
22
+ [SerializeField]
23
+ private Object[] _sourceFolders = Array.Empty<Object>();
24
+
25
+ [SerializeField]
26
+ private string _nameRegex = ".*";
27
+
28
+ [SerializeField]
29
+ private string _outputFolder = "Assets/Sprites/Atlases";
30
+
31
+ [SerializeField]
32
+ private int _crunchCompression = -1;
33
+
34
+ [SerializeField]
35
+ private TextureImporterCompression _compressionLevel =
36
+ TextureImporterCompression.Compressed;
37
+
38
+ private int _matchCount;
39
+ private int _totalCount;
40
+
41
+ [MenuItem("Tools/Wallstop Studios/Unity Helpers/" + Name)]
42
+ public static void ShowWindow() => GetWindow<SpriteAtlasGenerator>("Atlas Generator");
43
+
44
+ private void OnEnable()
45
+ {
46
+ if (_sourceFolders is { Length: > 0 })
47
+ {
48
+ return;
49
+ }
50
+
51
+ Object defaultFolder = AssetDatabase.LoadAssetAtPath<Object>("Assets/Sprites");
52
+ if (defaultFolder != null)
53
+ {
54
+ _sourceFolders = new[] { defaultFolder };
55
+ }
56
+ }
57
+
58
+ private void OnGUI()
59
+ {
60
+ GUILayout.Label("Source Folders", EditorStyles.boldLabel);
61
+ SerializedObject so = new(this);
62
+ so.Update();
63
+ EditorGUILayout.PropertyField(so.FindProperty(nameof(_sourceFolders)), true);
64
+ so.ApplyModifiedProperties();
65
+ GUILayout.Space(8);
66
+ EditorGUILayout.LabelField("Sprite Name Regex");
67
+ _nameRegex = EditorGUILayout.TextField(_nameRegex);
68
+
69
+ GUILayout.Space(4);
70
+
71
+ if (GUILayout.Button("Calculate Matches"))
72
+ {
73
+ UpdateMatchCounts();
74
+ }
75
+
76
+ EditorGUILayout.LabelField(
77
+ $"Matches: {_matchCount} Non-matches: {_totalCount - _matchCount}"
78
+ );
79
+
80
+ GUILayout.Space(4);
81
+ EditorGUILayout.LabelField("Crunch Compression");
82
+ _crunchCompression = EditorGUILayout.IntField(_crunchCompression);
83
+
84
+ GUILayout.Space(4);
85
+ EditorGUILayout.LabelField("Compression Level");
86
+ _compressionLevel = (TextureImporterCompression)
87
+ EditorGUILayout.EnumPopup(_compressionLevel);
88
+
89
+ GUILayout.Space(12);
90
+ EditorGUILayout.LabelField("Atlas Output Folder");
91
+ EditorGUILayout.LabelField(_outputFolder, EditorStyles.textField);
92
+ if (GUILayout.Button("Select Output Folder"))
93
+ {
94
+ string absPath = EditorUtility.OpenFolderPanel(
95
+ "Select Atlas Output Folder",
96
+ Application.dataPath,
97
+ ""
98
+ );
99
+ if (!string.IsNullOrEmpty(absPath))
100
+ {
101
+ if (absPath.StartsWith(Application.dataPath, StringComparison.Ordinal))
102
+ {
103
+ _outputFolder = "Assets" + absPath.Substring(Application.dataPath.Length);
104
+ }
105
+ else
106
+ {
107
+ EditorUtility.DisplayDialog(
108
+ "Invalid Folder",
109
+ "Please select a folder inside the project's Assets directory.",
110
+ "OK"
111
+ );
112
+ }
113
+ }
114
+ }
115
+
116
+ GUILayout.Space(12);
117
+ if (GUILayout.Button("Generate Atlases"))
118
+ {
119
+ GenerateAtlases();
120
+ }
121
+ }
122
+
123
+ private void UpdateMatchCounts()
124
+ {
125
+ _totalCount = 0;
126
+ _matchCount = 0;
127
+ Regex regex;
128
+ try
129
+ {
130
+ regex = new Regex(_nameRegex);
131
+ }
132
+ catch (ArgumentException ex)
133
+ {
134
+ this.LogError($"Invalid Regex pattern: '{_nameRegex}'. Error: {ex.Message}");
135
+ Repaint();
136
+ return;
137
+ }
138
+
139
+ foreach (Object obj in _sourceFolders)
140
+ {
141
+ if (obj == null)
142
+ {
143
+ continue;
144
+ }
145
+
146
+ string folderPath = AssetDatabase.GetAssetPath(obj);
147
+ if (string.IsNullOrEmpty(folderPath) || !AssetDatabase.IsValidFolder(folderPath))
148
+ {
149
+ this.LogWarn($"Skipping invalid or null source folder entry.");
150
+ continue;
151
+ }
152
+
153
+ string[] guids = AssetDatabase.FindAssets("t:Sprite", new[] { folderPath });
154
+ foreach (string guid in guids)
155
+ {
156
+ string path = AssetDatabase.GUIDToAssetPath(guid);
157
+
158
+ Object[] assets = AssetDatabase.LoadAllAssetsAtPath(path);
159
+ if (assets == null)
160
+ {
161
+ continue;
162
+ }
163
+
164
+ IEnumerable<Sprite> sprites = assets.OfType<Sprite>();
165
+ foreach (Sprite sp in sprites)
166
+ {
167
+ if (sp == null)
168
+ {
169
+ continue;
170
+ }
171
+
172
+ _totalCount++;
173
+ if (regex.IsMatch(sp.name))
174
+ {
175
+ _matchCount++;
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ Repaint();
182
+ }
183
+
184
+ private void GenerateAtlases()
185
+ {
186
+ List<SpriteAtlas> atlases = new();
187
+ int processed = 0;
188
+ try
189
+ {
190
+ EditorUtility.DisplayProgressBar(Name, "Initializing...", 0f);
191
+ if (string.IsNullOrWhiteSpace(_outputFolder))
192
+ {
193
+ this.LogError($"Invalid output folder.");
194
+ EditorUtility.ClearProgressBar();
195
+ return;
196
+ }
197
+
198
+ if (
199
+ _sourceFolders == null
200
+ || _sourceFolders.Length == 0
201
+ || _sourceFolders.All(f => f == null)
202
+ )
203
+ {
204
+ this.LogError($"No valid source folders specified.");
205
+ EditorUtility.ClearProgressBar();
206
+ return;
207
+ }
208
+
209
+ if (!AssetDatabase.IsValidFolder(_outputFolder))
210
+ {
211
+ try
212
+ {
213
+ string parent = Path.GetDirectoryName(_outputFolder);
214
+ string newFolderName = Path.GetFileName(_outputFolder);
215
+ if (string.IsNullOrEmpty(parent) || string.IsNullOrEmpty(newFolderName))
216
+ {
217
+ this.LogError($"Output folder path '{_outputFolder}' is invalid.");
218
+ EditorUtility.ClearProgressBar();
219
+ return;
220
+ }
221
+ AssetDatabase.CreateFolder(parent, newFolderName);
222
+ AssetDatabase.Refresh();
223
+ if (!AssetDatabase.IsValidFolder(_outputFolder))
224
+ {
225
+ this.LogError($"Failed to create output folder: '{_outputFolder}'");
226
+ EditorUtility.ClearProgressBar();
227
+ return;
228
+ }
229
+ }
230
+ catch (Exception ex)
231
+ {
232
+ this.LogError(
233
+ $"Error creating output folder '{_outputFolder}': {ex.Message}"
234
+ );
235
+ EditorUtility.ClearProgressBar();
236
+ return;
237
+ }
238
+ }
239
+
240
+ EditorUtility.DisplayProgressBar(Name, "Deleting old atlases...", 0.1f);
241
+ string[] existing = AssetDatabase
242
+ .FindAssets("t:SpriteAtlas", new[] { _outputFolder })
243
+ .Select(AssetDatabase.GUIDToAssetPath)
244
+ .Where(p => !string.IsNullOrEmpty(p))
245
+ .ToArray();
246
+
247
+ if (existing.Length > 0)
248
+ {
249
+ List<string> failedPaths = new();
250
+ AssetDatabase.DeleteAssets(existing, failedPaths);
251
+ if (failedPaths.Any())
252
+ {
253
+ this.LogWarn(
254
+ $"Failed to delete {failedPaths.Count} atlases:\n{string.Join("\n", failedPaths)}"
255
+ );
256
+ }
257
+ AssetDatabase.Refresh();
258
+ }
259
+
260
+ EditorUtility.DisplayProgressBar(Name, "Scanning sprites...", 0.25f);
261
+ Regex regex;
262
+ try
263
+ {
264
+ regex = new(_nameRegex);
265
+ }
266
+ catch (ArgumentException ex)
267
+ {
268
+ this.LogError(
269
+ $"Invalid Regex pattern for generation: '{_nameRegex}'. Error: {ex.Message}"
270
+ );
271
+ EditorUtility.ClearProgressBar();
272
+ return;
273
+ }
274
+ Dictionary<string, List<Sprite>> groups = new(StringComparer.Ordinal);
275
+
276
+ float sourceFolderProgressIncrement = 0.15f / _sourceFolders.Length;
277
+ float currentProgress = 0.25f;
278
+
279
+ foreach (Object sourceDirectory in _sourceFolders)
280
+ {
281
+ if (sourceDirectory == null)
282
+ {
283
+ continue;
284
+ }
285
+
286
+ string folderPath = AssetDatabase.GetAssetPath(sourceDirectory);
287
+ if (!AssetDatabase.IsValidFolder(folderPath))
288
+ {
289
+ this.LogWarn(
290
+ $"Skipping invalid source folder during generation: '{folderPath}'"
291
+ );
292
+ continue;
293
+ }
294
+
295
+ EditorUtility.DisplayProgressBar(
296
+ Name,
297
+ $"Scanning folder '{folderPath}'...",
298
+ currentProgress
299
+ );
300
+
301
+ foreach (
302
+ string assetGuid in AssetDatabase.FindAssets(
303
+ "t:Sprite",
304
+ new[] { folderPath }
305
+ )
306
+ )
307
+ {
308
+ string assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
309
+ if (string.IsNullOrEmpty(assetPath))
310
+ {
311
+ continue;
312
+ }
313
+
314
+ Object[] allAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
315
+ if (allAssets == null)
316
+ {
317
+ continue;
318
+ }
319
+
320
+ foreach (Sprite sub in allAssets.OfType<Sprite>())
321
+ {
322
+ if (sub == null)
323
+ {
324
+ continue;
325
+ }
326
+
327
+ string assetName = sub.name;
328
+ if (!regex.IsMatch(assetName))
329
+ {
330
+ continue;
331
+ }
332
+
333
+ Match match = Regex.Match(assetName, @"^(.+?)(?:_\d+)?$");
334
+ string key = match.Success ? match.Groups[1].Value : assetName;
335
+ groups.GetOrAdd(key).Add(sub);
336
+ }
337
+ }
338
+ currentProgress += sourceFolderProgressIncrement;
339
+ }
340
+
341
+ const int atlasSize = 8192;
342
+ const long budget = (long)atlasSize * atlasSize;
343
+ int totalChunks = 0;
344
+ Dictionary<string, List<List<Sprite>>> groupChunks = new();
345
+
346
+ EditorUtility.DisplayProgressBar(Name, "Calculating chunks...", 0.4f);
347
+
348
+ foreach (KeyValuePair<string, List<Sprite>> kv in groups)
349
+ {
350
+ List<Sprite> sprites = kv
351
+ .Value.OrderByDescending(s => s.rect.width * s.rect.height)
352
+ .ToList();
353
+ List<List<Sprite>> chunks = new();
354
+ List<Sprite> current = new();
355
+ long currentArea = 0;
356
+ foreach (Sprite sprite in sprites)
357
+ {
358
+ if (sprite == null || sprite.rect.width <= 0 || sprite.rect.height <= 0)
359
+ {
360
+ this.LogWarn(
361
+ $"Skipping invalid sprite '{sprite?.name ?? "null"}' in group '{kv.Key}'."
362
+ );
363
+ continue;
364
+ }
365
+
366
+ long area = (long)(sprite.rect.width * sprite.rect.height);
367
+ if (area > budget)
368
+ {
369
+ this.LogWarn(
370
+ $"Sprite '{sprite.name}' ({sprite.rect.width}x{sprite.rect.height}) is larger than max atlas area budget and will be placed in its own atlas chunk."
371
+ );
372
+ continue;
373
+ }
374
+ if (currentArea + area <= budget && current.Count < 2000)
375
+ {
376
+ current.Add(sprite);
377
+ currentArea += area;
378
+ }
379
+ else
380
+ {
381
+ if (current.Count > 1)
382
+ {
383
+ chunks.Add(current);
384
+ }
385
+ current = new List<Sprite> { sprite };
386
+ currentArea = area;
387
+ }
388
+ }
389
+
390
+ if (current.Count > 1)
391
+ {
392
+ chunks.Add(current);
393
+ }
394
+
395
+ if (chunks.Count > 0)
396
+ {
397
+ groupChunks[kv.Key] = chunks;
398
+ totalChunks += chunks.Count;
399
+ }
400
+ }
401
+
402
+ if (totalChunks == 0)
403
+ {
404
+ this.Log(
405
+ $"No sprites matched the regex '{_nameRegex}' or formed valid chunks."
406
+ );
407
+ EditorUtility.ClearProgressBar();
408
+ return;
409
+ }
410
+
411
+ int chunkIndex = 0;
412
+ float atlasCreationProgressStart = 0.45f;
413
+ float atlasCreationProgressRange = 0.5f;
414
+
415
+ foreach ((string prefix, List<List<Sprite>> chunks) in groupChunks)
416
+ {
417
+ for (int i = 0; i < chunks.Count; i++)
418
+ {
419
+ List<Sprite> chunk = chunks[i];
420
+ if (chunk == null || chunk.Count == 0)
421
+ {
422
+ continue;
423
+ }
424
+
425
+ float progress =
426
+ atlasCreationProgressStart
427
+ + atlasCreationProgressRange * (chunkIndex / (float)totalChunks);
428
+
429
+ string atlasName = chunks.Count > 1 ? $"{prefix}_{i}" : prefix;
430
+ EditorUtility.DisplayProgressBar(
431
+ Name,
432
+ $"Creating atlas '{atlasName}' ({i + 1}/{chunks.Count})... Sprites: {chunk.Count}",
433
+ progress
434
+ );
435
+
436
+ SpriteAtlas atlas = new();
437
+ atlases.Add(atlas);
438
+
439
+ SpriteAtlasPackingSettings packingSettings = atlas.GetPackingSettings();
440
+ packingSettings.enableTightPacking = true;
441
+ packingSettings.padding = 4;
442
+ packingSettings.enableRotation = false;
443
+ atlas.SetPackingSettings(packingSettings);
444
+
445
+ SpriteAtlasTextureSettings textureSettings = atlas.GetTextureSettings();
446
+ textureSettings.generateMipMaps = false;
447
+ textureSettings.filterMode = FilterMode.Bilinear;
448
+ textureSettings.readable = false;
449
+ atlas.SetTextureSettings(textureSettings);
450
+
451
+ TextureImporterPlatformSettings platformSettings =
452
+ atlas.GetPlatformSettings(DefaultPlatformName);
453
+
454
+ if (platformSettings == null)
455
+ {
456
+ platformSettings = new TextureImporterPlatformSettings();
457
+ platformSettings.name = DefaultPlatformName;
458
+ this.LogWarn(
459
+ $"Could not get default platform settings for {atlasName}. Creating new default."
460
+ );
461
+ }
462
+
463
+ platformSettings.overridden = true;
464
+ platformSettings.maxTextureSize = atlasSize;
465
+ platformSettings.textureCompression = _compressionLevel;
466
+ platformSettings.format = TextureImporterFormat.Automatic;
467
+
468
+ if (_crunchCompression is >= 0 and <= 100)
469
+ {
470
+ platformSettings.crunchedCompression = true;
471
+ platformSettings.compressionQuality = _crunchCompression;
472
+ }
473
+ else
474
+ {
475
+ if (100 < _crunchCompression)
476
+ {
477
+ this.LogWarn(
478
+ $"Invalid crunch compression: {_crunchCompression}. Using default (off)."
479
+ );
480
+ }
481
+ platformSettings.crunchedCompression = false;
482
+ platformSettings.compressionQuality = 50;
483
+ }
484
+
485
+ atlas.SetPlatformSettings(platformSettings);
486
+
487
+ Object[] validSprites = chunk
488
+ .Where(s => s != null)
489
+ .Select(sprite => sprite as Object)
490
+ .ToArray();
491
+ if (validSprites.Length == 0)
492
+ {
493
+ this.LogWarn(
494
+ $"Skipping atlas '{atlasName}' as it contained no valid sprites after filtering."
495
+ );
496
+ atlases.Remove(atlas);
497
+ }
498
+ else
499
+ {
500
+ atlas.Add(validSprites);
501
+ atlas.SetIncludeInBuild(true);
502
+ string path = Path.Combine(_outputFolder, atlasName + ".spriteatlas");
503
+ path = AssetDatabase.GenerateUniqueAssetPath(path);
504
+ AssetDatabase.CreateAsset(atlas, path);
505
+ processed++;
506
+ }
507
+ chunkIndex++;
508
+ }
509
+ }
510
+
511
+ if (processed > 0)
512
+ {
513
+ EditorUtility.DisplayProgressBar(Name, "Saving assets...", 0.95f);
514
+ AssetDatabase.SaveAssets();
515
+ AssetDatabase.Refresh();
516
+
517
+ EditorUtility.DisplayProgressBar(Name, "Packing atlases...", 0.97f);
518
+ SpriteAtlasUtility.PackAllAtlases(
519
+ EditorUserBuildSettings.activeBuildTarget,
520
+ false
521
+ );
522
+
523
+ bool anyChanged = false;
524
+ EditorUtility.DisplayProgressBar(Name, "Optimizing atlas sizes...", 0.98f);
525
+ foreach (
526
+ SpriteAtlas atlas in atlases
527
+ .Select(AssetDatabase.GetAssetPath)
528
+ .Where(p => !string.IsNullOrEmpty(p))
529
+ .Select(AssetDatabase.LoadAssetAtPath<SpriteAtlas>)
530
+ .Where(a => a != null)
531
+ )
532
+ {
533
+ Texture2D preview = atlas.GetPreviewTexture();
534
+ if (preview == null)
535
+ {
536
+ continue;
537
+ }
538
+
539
+ TextureImporterPlatformSettings platformSettings =
540
+ atlas.GetPlatformSettings(DefaultPlatformName);
541
+ if (platformSettings is not { overridden: true })
542
+ {
543
+ continue;
544
+ }
545
+
546
+ int actualWidth = preview.width;
547
+ int actualHeight = preview.height;
548
+ int newMaxSize = Mathf.Max(
549
+ Mathf.NextPowerOfTwo(actualWidth),
550
+ Mathf.NextPowerOfTwo(actualHeight)
551
+ );
552
+ newMaxSize = Mathf.Clamp(newMaxSize, 32, atlasSize);
553
+
554
+ if (newMaxSize < platformSettings.maxTextureSize)
555
+ {
556
+ this.Log(
557
+ $"Optimizing atlas '{atlas.name}' max size from {platformSettings.maxTextureSize} to {newMaxSize}"
558
+ );
559
+ platformSettings.maxTextureSize = newMaxSize;
560
+ atlas.SetPlatformSettings(platformSettings);
561
+ EditorUtility.SetDirty(atlas);
562
+ anyChanged = true;
563
+ }
564
+ }
565
+
566
+ if (anyChanged)
567
+ {
568
+ EditorUtility.DisplayProgressBar(Name, "Saving optimizations...", 0.99f);
569
+ AssetDatabase.SaveAssets();
570
+ AssetDatabase.Refresh();
571
+ }
572
+ }
573
+ }
574
+ catch (Exception e)
575
+ {
576
+ this.LogError($"An unexpected error occurred during atlas generation.", e);
577
+ }
578
+ finally
579
+ {
580
+ EditorUtility.ClearProgressBar();
581
+ }
582
+
583
+ if (processed > 0)
584
+ {
585
+ this.Log(
586
+ $"[SpriteAtlasGenerator] Successfully created or updated {processed} atlases in '{_outputFolder}'."
587
+ );
588
+ }
589
+ else
590
+ {
591
+ this.Log(
592
+ $"[SpriteAtlasGenerator] No atlases were generated. Check source folders and regex pattern."
593
+ );
594
+ }
595
+ }
596
+ }
597
+ #endif
598
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: b0dd60e3c1f24e7eb4afc773157cb996
3
+ timeCreated: 1746461513