com.wallstop-studios.unity-helpers 2.0.0-rc73.5 → 2.0.0-rc73.6
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.
- package/Editor/AnimationCreator.cs +14 -20
- package/Editor/AnimationEventEditor.cs +11 -23
- package/Editor/FitTextureSizeWindow.cs +1 -1
- package/Editor/PrefabChecker.cs +12 -18
- package/Editor/SpriteAtlasGenerator.cs +47 -66
- package/Editor/SpriteCropper.cs +49 -5
- package/Editor/SpriteSettingsApplier.cs +5 -3
- package/Editor/Utils/GUIIndentScope.cs +20 -0
- package/Editor/Utils/GUIIndentScope.cs.meta +3 -0
- package/package.json +2 -1
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
using System.IO;
|
|
8
8
|
using System.Linq;
|
|
9
9
|
using System.Text.RegularExpressions;
|
|
10
|
+
using Core.Helper;
|
|
10
11
|
using UnityEditor;
|
|
11
12
|
using UnityEngine;
|
|
13
|
+
using Utils;
|
|
12
14
|
using Object = UnityEngine.Object;
|
|
13
15
|
|
|
14
16
|
[Serializable]
|
|
@@ -25,6 +27,8 @@
|
|
|
25
27
|
|
|
26
28
|
public sealed class AnimationCreatorWindow : EditorWindow
|
|
27
29
|
{
|
|
30
|
+
private static readonly char[] WhiteSpaceSplitters = { ' ', '\t', '\n', '\r' };
|
|
31
|
+
|
|
28
32
|
private SerializedObject _serializedObject;
|
|
29
33
|
private SerializedProperty _animationDataProp;
|
|
30
34
|
private SerializedProperty _animationSourcesProp;
|
|
@@ -87,7 +91,7 @@
|
|
|
87
91
|
else if (
|
|
88
92
|
_animationSourcesProp.arraySize == 0
|
|
89
93
|
|| _animationSourcesProp.FindPropertyRelative("Array.size").intValue == 0
|
|
90
|
-
|| animationSources.TrueForAll(
|
|
94
|
+
|| animationSources.TrueForAll(Objects.Null)
|
|
91
95
|
)
|
|
92
96
|
{
|
|
93
97
|
EditorGUILayout.HelpBox(
|
|
@@ -205,10 +209,7 @@
|
|
|
205
209
|
int listSize = _animationDataProp.arraySize;
|
|
206
210
|
string[] searchTerms = string.IsNullOrWhiteSpace(_searchString)
|
|
207
211
|
? Array.Empty<string>()
|
|
208
|
-
: _searchString.Split(
|
|
209
|
-
new[] { ' ', '\t', '\n', '\r' },
|
|
210
|
-
StringSplitOptions.RemoveEmptyEntries
|
|
211
|
-
);
|
|
212
|
+
: _searchString.Split(WhiteSpaceSplitters, StringSplitOptions.RemoveEmptyEntries);
|
|
212
213
|
|
|
213
214
|
List<int> matchingIndices = new();
|
|
214
215
|
for (int i = 0; i < listSize; ++i)
|
|
@@ -245,7 +246,7 @@
|
|
|
245
246
|
|
|
246
247
|
if (_animationDataIsExpanded)
|
|
247
248
|
{
|
|
248
|
-
|
|
249
|
+
using GUIIndentScope indent = new();
|
|
249
250
|
if (matchCount > 0)
|
|
250
251
|
{
|
|
251
252
|
foreach (int index in matchingIndices)
|
|
@@ -273,8 +274,6 @@
|
|
|
273
274
|
MessageType.Info
|
|
274
275
|
);
|
|
275
276
|
}
|
|
276
|
-
|
|
277
|
-
EditorGUI.indentLevel--;
|
|
278
277
|
}
|
|
279
278
|
}
|
|
280
279
|
|
|
@@ -439,14 +438,14 @@
|
|
|
439
438
|
}
|
|
440
439
|
|
|
441
440
|
EditorGUILayout.Space();
|
|
442
|
-
using (new EditorGUI.DisabledScope(animationData
|
|
441
|
+
using (new EditorGUI.DisabledScope(animationData is not { Count: > 0 }))
|
|
443
442
|
{
|
|
444
443
|
if (GUILayout.Button("Create Animations"))
|
|
445
444
|
{
|
|
446
445
|
CreateAnimations();
|
|
447
446
|
}
|
|
448
447
|
}
|
|
449
|
-
if (animationData
|
|
448
|
+
if (animationData is not { Count: > 0 })
|
|
450
449
|
{
|
|
451
450
|
EditorGUILayout.HelpBox(
|
|
452
451
|
"Add Animation Data entries before creating.",
|
|
@@ -457,7 +456,7 @@
|
|
|
457
456
|
|
|
458
457
|
private void CreateAnimations()
|
|
459
458
|
{
|
|
460
|
-
if (animationData is not { Count:
|
|
459
|
+
if (animationData is not { Count: > 0 })
|
|
461
460
|
{
|
|
462
461
|
this.LogError($"No animation data to create.");
|
|
463
462
|
return;
|
|
@@ -532,7 +531,7 @@
|
|
|
532
531
|
}
|
|
533
532
|
|
|
534
533
|
List<Sprite> frames = data.frames;
|
|
535
|
-
if (frames is not { Count:
|
|
534
|
+
if (frames is not { Count: > 0 })
|
|
536
535
|
{
|
|
537
536
|
this.LogWarn(
|
|
538
537
|
$"Ignoring animation '{animationName}' because it has no frames."
|
|
@@ -659,7 +658,7 @@
|
|
|
659
658
|
_matchedSpriteCount = 0;
|
|
660
659
|
_unmatchedSpriteCount = 0;
|
|
661
660
|
|
|
662
|
-
if (animationSources is not { Count:
|
|
661
|
+
if (animationSources is not { Count: > 0 } || _compiledRegex == null)
|
|
663
662
|
{
|
|
664
663
|
if (_compiledRegex == null && !string.IsNullOrWhiteSpace(spriteNameRegex))
|
|
665
664
|
{
|
|
@@ -667,7 +666,7 @@
|
|
|
667
666
|
$"Cannot find sprites, regex pattern '{spriteNameRegex}' is invalid."
|
|
668
667
|
);
|
|
669
668
|
}
|
|
670
|
-
else if (animationSources is not { Count:
|
|
669
|
+
else if (animationSources is not { Count: > 0 })
|
|
671
670
|
{
|
|
672
671
|
this.LogWarn($"Cannot find sprites, no animation sources specified.");
|
|
673
672
|
}
|
|
@@ -675,13 +674,8 @@
|
|
|
675
674
|
}
|
|
676
675
|
|
|
677
676
|
List<string> searchPaths = new();
|
|
678
|
-
foreach (Object source in animationSources)
|
|
677
|
+
foreach (Object source in animationSources.Where(Objects.NotNull))
|
|
679
678
|
{
|
|
680
|
-
if (source == null)
|
|
681
|
-
{
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
679
|
string path = AssetDatabase.GetAssetPath(source);
|
|
686
680
|
if (!string.IsNullOrWhiteSpace(path) && AssetDatabase.IsValidFolder(path))
|
|
687
681
|
{
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
using UnityEngine;
|
|
10
10
|
using Core.Attributes;
|
|
11
11
|
using Core.Helper;
|
|
12
|
+
using Utils;
|
|
12
13
|
using WallstopStudios.UnityHelpers.Utils;
|
|
13
14
|
|
|
14
15
|
// https://gist.githubusercontent.com/yujen/5e1cd78e2a341260b38029de08a449da/raw/ac60c1002e0e14375de5b2b0a167af00df3f74b4/SeniaAnimationEventEditor.cs
|
|
@@ -172,16 +173,15 @@
|
|
|
172
173
|
|
|
173
174
|
DrawSpritePreview(item);
|
|
174
175
|
|
|
175
|
-
|
|
176
|
-
RenderAnimationEventItem(item, frame, frameRate);
|
|
177
|
-
|
|
178
|
-
if (i != stateCopy.Count - 1)
|
|
176
|
+
using (new GUIIndentScope())
|
|
179
177
|
{
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
RenderAnimationEventItem(item, frame, frameRate);
|
|
179
|
+
if (i != stateCopy.Count - 1)
|
|
180
|
+
{
|
|
181
|
+
DrawGuiLine(height: 3, color: new Color(0f, 1f, 0.3f, 1f));
|
|
182
|
+
EditorGUILayout.Space();
|
|
183
|
+
}
|
|
182
184
|
}
|
|
183
|
-
|
|
184
|
-
EditorGUI.indentLevel--;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
EditorGUILayout.EndScrollView();
|
|
@@ -320,8 +320,7 @@
|
|
|
320
320
|
private void RenderAnimationEventItem(AnimationEventItem item, int frame, float frameRate)
|
|
321
321
|
{
|
|
322
322
|
int index = _state.IndexOf(item);
|
|
323
|
-
|
|
324
|
-
try
|
|
323
|
+
using (new GUIHorizontalScope())
|
|
325
324
|
{
|
|
326
325
|
if (
|
|
327
326
|
1 <= index
|
|
@@ -369,10 +368,6 @@
|
|
|
369
368
|
return;
|
|
370
369
|
}
|
|
371
370
|
}
|
|
372
|
-
finally
|
|
373
|
-
{
|
|
374
|
-
EditorGUILayout.EndHorizontal();
|
|
375
|
-
}
|
|
376
371
|
|
|
377
372
|
IReadOnlyDictionary<Type, IReadOnlyList<MethodInfo>> lookup = FilterLookup(item);
|
|
378
373
|
|
|
@@ -541,7 +536,7 @@
|
|
|
541
536
|
ParameterInfo[] arrayParameterInfo = item.selectedMethod.GetParameters();
|
|
542
537
|
if (arrayParameterInfo.Length == 1)
|
|
543
538
|
{
|
|
544
|
-
|
|
539
|
+
using GUIIndentScope indent = new();
|
|
545
540
|
|
|
546
541
|
Type paramType = arrayParameterInfo[0].ParameterType;
|
|
547
542
|
if (paramType == typeof(int))
|
|
@@ -602,8 +597,6 @@
|
|
|
602
597
|
true
|
|
603
598
|
);
|
|
604
599
|
}
|
|
605
|
-
|
|
606
|
-
EditorGUI.indentLevel--;
|
|
607
600
|
}
|
|
608
601
|
}
|
|
609
602
|
|
|
@@ -686,8 +679,7 @@
|
|
|
686
679
|
}
|
|
687
680
|
else if (!item.isTextureReadable && !string.IsNullOrEmpty(spriteName))
|
|
688
681
|
{
|
|
689
|
-
|
|
690
|
-
try
|
|
682
|
+
using (new GUIHorizontalScope())
|
|
691
683
|
{
|
|
692
684
|
GUILayout.Label($"Sprite '{spriteName}' required \"Read/Write\" enabled");
|
|
693
685
|
if (item.sprite != null && GUILayout.Button("Fix"))
|
|
@@ -711,10 +703,6 @@
|
|
|
711
703
|
EditorUtility.SetDirty(item.sprite);
|
|
712
704
|
}
|
|
713
705
|
}
|
|
714
|
-
finally
|
|
715
|
-
{
|
|
716
|
-
EditorGUILayout.EndHorizontal();
|
|
717
|
-
}
|
|
718
706
|
}
|
|
719
707
|
else if (item.isInvalidTextureRect && !string.IsNullOrEmpty(spriteName))
|
|
720
708
|
{
|
package/Editor/PrefabChecker.cs
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
using Core.Attributes;
|
|
13
13
|
using Core.Extension;
|
|
14
14
|
using Core.Helper;
|
|
15
|
+
using Utils;
|
|
15
16
|
using WallstopStudios.UnityHelpers.Utils;
|
|
16
17
|
using Object = UnityEngine.Object;
|
|
17
18
|
|
|
@@ -148,23 +149,16 @@
|
|
|
148
149
|
GUI.enabled = wasEnabled && _checkNullObjectReferences;
|
|
149
150
|
try
|
|
150
151
|
{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
true
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
finally
|
|
165
|
-
{
|
|
166
|
-
EditorGUI.indentLevel--;
|
|
167
|
-
}
|
|
152
|
+
using GUIIndentScope indent = new();
|
|
153
|
+
DrawAndAlign(
|
|
154
|
+
new GUIContent(
|
|
155
|
+
"Only if [ValidateAssignment]",
|
|
156
|
+
"Only report null object references if the field has the [ValidateAssignment] attribute."
|
|
157
|
+
),
|
|
158
|
+
() => _onlyCheckNullObjectsWithAttribute,
|
|
159
|
+
v => _onlyCheckNullObjectsWithAttribute = v,
|
|
160
|
+
true
|
|
161
|
+
);
|
|
168
162
|
}
|
|
169
163
|
finally
|
|
170
164
|
{
|
|
@@ -374,7 +368,7 @@
|
|
|
374
368
|
|
|
375
369
|
private void RunChecks()
|
|
376
370
|
{
|
|
377
|
-
if (_assetPaths
|
|
371
|
+
if (_assetPaths is not { Count: > 0 })
|
|
378
372
|
{
|
|
379
373
|
this.LogError($"No asset paths specified. Add folders containing prefabs.");
|
|
380
374
|
return;
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
{
|
|
46
46
|
foreach (Sprite sprite in Sprites)
|
|
47
47
|
{
|
|
48
|
-
if (sprite
|
|
48
|
+
if (sprite is { rect: { width: > 0, height: > 0 } })
|
|
49
49
|
{
|
|
50
50
|
TotalArea += (long)(sprite.rect.width * sprite.rect.height);
|
|
51
51
|
}
|
|
@@ -249,7 +249,7 @@
|
|
|
249
249
|
Application.dataPath,
|
|
250
250
|
""
|
|
251
251
|
);
|
|
252
|
-
if (!string.
|
|
252
|
+
if (!string.IsNullOrWhiteSpace(absPath))
|
|
253
253
|
{
|
|
254
254
|
if (absPath.StartsWith(Application.dataPath, StringComparison.Ordinal))
|
|
255
255
|
{
|
|
@@ -295,16 +295,12 @@
|
|
|
295
295
|
try
|
|
296
296
|
{
|
|
297
297
|
float total = _sourceFolders.Length;
|
|
298
|
-
foreach (Object obj in _sourceFolders)
|
|
298
|
+
foreach (Object obj in _sourceFolders.Where(Objects.NotNull))
|
|
299
299
|
{
|
|
300
|
-
if (obj == null)
|
|
301
|
-
{
|
|
302
|
-
continue;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
300
|
string folderPath = AssetDatabase.GetAssetPath(obj);
|
|
306
301
|
if (
|
|
307
|
-
string.
|
|
302
|
+
string.IsNullOrWhiteSpace(folderPath)
|
|
303
|
+
|| !AssetDatabase.IsValidFolder(folderPath)
|
|
308
304
|
)
|
|
309
305
|
{
|
|
310
306
|
this.LogWarn($"Skipping invalid or null source folder entry.");
|
|
@@ -312,7 +308,7 @@
|
|
|
312
308
|
}
|
|
313
309
|
|
|
314
310
|
string[] guids = AssetDatabase.FindAssets("t:Sprite", new[] { folderPath });
|
|
315
|
-
for (int i = 0; i < guids.Length; i
|
|
311
|
+
for (int i = 0; i < guids.Length; ++i)
|
|
316
312
|
{
|
|
317
313
|
EditorUtility.DisplayProgressBar(
|
|
318
314
|
Name,
|
|
@@ -329,14 +325,8 @@
|
|
|
329
325
|
continue;
|
|
330
326
|
}
|
|
331
327
|
|
|
332
|
-
|
|
333
|
-
foreach (Sprite sp in sprites)
|
|
328
|
+
foreach (Sprite sp in assets.OfType<Sprite>().Where(Objects.NotNull))
|
|
334
329
|
{
|
|
335
|
-
if (sp == null)
|
|
336
|
-
{
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
330
|
_totalCount++;
|
|
341
331
|
if (regex.IsMatch(sp.name))
|
|
342
332
|
{
|
|
@@ -369,11 +359,7 @@
|
|
|
369
359
|
return;
|
|
370
360
|
}
|
|
371
361
|
|
|
372
|
-
if (
|
|
373
|
-
_sourceFolders == null
|
|
374
|
-
|| _sourceFolders.Length == 0
|
|
375
|
-
|| _sourceFolders.All(f => f == null)
|
|
376
|
-
)
|
|
362
|
+
if (_sourceFolders == null || Array.TrueForAll(_sourceFolders, Objects.Null))
|
|
377
363
|
{
|
|
378
364
|
this.LogError($"No valid source folders specified.");
|
|
379
365
|
EditorUtility.ClearProgressBar();
|
|
@@ -386,7 +372,10 @@
|
|
|
386
372
|
{
|
|
387
373
|
string parent = Path.GetDirectoryName(_outputFolder);
|
|
388
374
|
string newFolderName = Path.GetFileName(_outputFolder);
|
|
389
|
-
if (
|
|
375
|
+
if (
|
|
376
|
+
string.IsNullOrWhiteSpace(parent)
|
|
377
|
+
|| string.IsNullOrWhiteSpace(newFolderName)
|
|
378
|
+
)
|
|
390
379
|
{
|
|
391
380
|
this.LogError($"Output folder path '{_outputFolder}' is invalid.");
|
|
392
381
|
EditorUtility.ClearProgressBar();
|
|
@@ -415,7 +404,7 @@
|
|
|
415
404
|
string[] existing = AssetDatabase
|
|
416
405
|
.FindAssets("t:SpriteAtlas", new[] { _outputFolder })
|
|
417
406
|
.Select(AssetDatabase.GUIDToAssetPath)
|
|
418
|
-
.Where(
|
|
407
|
+
.Where(path => !string.IsNullOrWhiteSpace(path))
|
|
419
408
|
.ToArray();
|
|
420
409
|
|
|
421
410
|
if (existing.Length > 0)
|
|
@@ -439,7 +428,7 @@
|
|
|
439
428
|
Regex regex;
|
|
440
429
|
try
|
|
441
430
|
{
|
|
442
|
-
regex = new(_nameRegex);
|
|
431
|
+
regex = new Regex(_nameRegex);
|
|
443
432
|
}
|
|
444
433
|
catch (ArgumentException ex)
|
|
445
434
|
{
|
|
@@ -455,13 +444,8 @@
|
|
|
455
444
|
_sourceFolders.Length > 0 ? 0.2f / _sourceFolders.Length : 0f;
|
|
456
445
|
float sourceFolderProgress = 0.1f;
|
|
457
446
|
|
|
458
|
-
foreach (Object sourceDirectory in _sourceFolders)
|
|
447
|
+
foreach (Object sourceDirectory in _sourceFolders.Where(Objects.NotNull))
|
|
459
448
|
{
|
|
460
|
-
if (sourceDirectory == null)
|
|
461
|
-
{
|
|
462
|
-
continue;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
449
|
string folderPath = AssetDatabase.GetAssetPath(sourceDirectory);
|
|
466
450
|
if (!AssetDatabase.IsValidFolder(folderPath))
|
|
467
451
|
{
|
|
@@ -485,7 +469,7 @@
|
|
|
485
469
|
)
|
|
486
470
|
{
|
|
487
471
|
string assetPath = AssetDatabase.GUIDToAssetPath(assetGuid);
|
|
488
|
-
if (string.
|
|
472
|
+
if (string.IsNullOrWhiteSpace(assetPath))
|
|
489
473
|
{
|
|
490
474
|
continue;
|
|
491
475
|
}
|
|
@@ -496,13 +480,8 @@
|
|
|
496
480
|
continue;
|
|
497
481
|
}
|
|
498
482
|
|
|
499
|
-
foreach (Sprite sub in allAssets.OfType<Sprite>())
|
|
483
|
+
foreach (Sprite sub in allAssets.OfType<Sprite>().Where(Objects.NotNull))
|
|
500
484
|
{
|
|
501
|
-
if (sub == null)
|
|
502
|
-
{
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
485
|
string assetName = sub.name;
|
|
507
486
|
if (!regex.IsMatch(assetName))
|
|
508
487
|
{
|
|
@@ -518,14 +497,13 @@
|
|
|
518
497
|
|
|
519
498
|
int totalChunks = 0;
|
|
520
499
|
Dictionary<string, List<List<Sprite>>> groupChunks = new();
|
|
521
|
-
|
|
522
500
|
EditorUtility.DisplayProgressBar(Name, "Calculating chunks...", 0.3f);
|
|
523
|
-
|
|
524
501
|
foreach (KeyValuePair<string, List<Sprite>> kv in groups)
|
|
525
502
|
{
|
|
526
503
|
List<Sprite> spritesInGroup = kv
|
|
527
|
-
.Value.Where(
|
|
528
|
-
.
|
|
504
|
+
.Value.Where(Objects.NotNull)
|
|
505
|
+
.Where(sprite => sprite.rect is { width: > 0, height: > 0 })
|
|
506
|
+
.OrderByDescending(sprite => sprite.rect.width * sprite.rect.height)
|
|
529
507
|
.ToList();
|
|
530
508
|
if (!spritesInGroup.Any())
|
|
531
509
|
{
|
|
@@ -599,7 +577,7 @@
|
|
|
599
577
|
) in groupChunks
|
|
600
578
|
)
|
|
601
579
|
{
|
|
602
|
-
for (int i = 0; i < chunksForThisGroup.Count; i
|
|
580
|
+
for (int i = 0; i < chunksForThisGroup.Count; ++i)
|
|
603
581
|
{
|
|
604
582
|
if (!chunksForThisGroup[i].Any())
|
|
605
583
|
{
|
|
@@ -621,21 +599,21 @@
|
|
|
621
599
|
}
|
|
622
600
|
|
|
623
601
|
allInitialCandidates = allInitialCandidates
|
|
624
|
-
.OrderByDescending(
|
|
625
|
-
.ThenBy(
|
|
602
|
+
.OrderByDescending(candidate => candidate.TotalArea)
|
|
603
|
+
.ThenBy(candidate => candidate.CandidateName, StringComparer.Ordinal)
|
|
626
604
|
.ToList();
|
|
627
605
|
|
|
628
606
|
List<MergeableAtlas> workingAtlases = allInitialCandidates
|
|
629
|
-
.Select(
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
607
|
+
.Select(candidate => new MergeableAtlas(
|
|
608
|
+
candidate.OriginalGroupKey,
|
|
609
|
+
candidate.Sprites,
|
|
610
|
+
candidate.CandidateName,
|
|
611
|
+
candidate.TotalArea
|
|
634
612
|
))
|
|
635
613
|
.ToList();
|
|
636
614
|
int passNumber = 0;
|
|
637
|
-
float mergeOptimizationProgressStart = 0.
|
|
638
|
-
float mergeOptimizationProgressRange = 0.
|
|
615
|
+
const float mergeOptimizationProgressStart = 0.3f;
|
|
616
|
+
const float mergeOptimizationProgressRange = 0.5f;
|
|
639
617
|
|
|
640
618
|
while (true)
|
|
641
619
|
{
|
|
@@ -643,7 +621,7 @@
|
|
|
643
621
|
bool mergedInThisPass = false;
|
|
644
622
|
float currentPassProgress =
|
|
645
623
|
mergeOptimizationProgressStart
|
|
646
|
-
+ passNumber * (mergeOptimizationProgressRange /
|
|
624
|
+
+ passNumber * (mergeOptimizationProgressRange / 15f);
|
|
647
625
|
EditorUtility.DisplayProgressBar(
|
|
648
626
|
Name,
|
|
649
627
|
$"Optimizing atlas count (Pass {passNumber}, {workingAtlases.Count} atlases)...",
|
|
@@ -654,13 +632,16 @@
|
|
|
654
632
|
);
|
|
655
633
|
|
|
656
634
|
workingAtlases = workingAtlases
|
|
657
|
-
.OrderByDescending(
|
|
658
|
-
.ThenBy(
|
|
635
|
+
.OrderByDescending(atlas => atlas.TotalArea)
|
|
636
|
+
.ThenBy(
|
|
637
|
+
atlas => atlas.RepresentativeInitialName,
|
|
638
|
+
StringComparer.Ordinal
|
|
639
|
+
)
|
|
659
640
|
.ToList();
|
|
660
641
|
|
|
661
642
|
bool[] isSubsumed = new bool[workingAtlases.Count];
|
|
662
643
|
|
|
663
|
-
for (int i = 0; i < workingAtlases.Count; i
|
|
644
|
+
for (int i = 0; i < workingAtlases.Count; ++i)
|
|
664
645
|
{
|
|
665
646
|
if (isSubsumed[i])
|
|
666
647
|
{
|
|
@@ -669,14 +650,14 @@
|
|
|
669
650
|
|
|
670
651
|
MergeableAtlas baseAtlas = workingAtlases[i];
|
|
671
652
|
string baseRepresentativeKey = baseAtlas
|
|
672
|
-
.OriginalGroupKeys.OrderBy(
|
|
653
|
+
.OriginalGroupKeys.OrderBy(key => key, StringComparer.Ordinal)
|
|
673
654
|
.First();
|
|
674
655
|
|
|
675
656
|
int bestPartnerIndex = -1;
|
|
676
657
|
MergeableAtlas bestPartnerObject = null;
|
|
677
658
|
int currentMinLevenshtein = int.MaxValue;
|
|
678
659
|
|
|
679
|
-
for (int j = i + 1; j < workingAtlases.Count; j
|
|
660
|
+
for (int j = i + 1; j < workingAtlases.Count; ++j)
|
|
680
661
|
{
|
|
681
662
|
if (isSubsumed[j])
|
|
682
663
|
{
|
|
@@ -693,7 +674,7 @@
|
|
|
693
674
|
}
|
|
694
675
|
|
|
695
676
|
string partnerRepresentativeKey = potentialPartner
|
|
696
|
-
.OriginalGroupKeys.OrderBy(
|
|
677
|
+
.OriginalGroupKeys.OrderBy(key => key, StringComparer.Ordinal)
|
|
697
678
|
.First();
|
|
698
679
|
int distance = baseRepresentativeKey.LevenshteinDistance(
|
|
699
680
|
partnerRepresentativeKey
|
|
@@ -754,8 +735,8 @@
|
|
|
754
735
|
}
|
|
755
736
|
|
|
756
737
|
finalAtlasesData = workingAtlases
|
|
757
|
-
.Select(
|
|
758
|
-
.OrderBy(
|
|
738
|
+
.Select(atlas => (Name: atlas.GenerateFinalName(), atlas.Sprites))
|
|
739
|
+
.OrderBy(atlas => atlas.Name, StringComparer.Ordinal)
|
|
759
740
|
.ToList();
|
|
760
741
|
}
|
|
761
742
|
else
|
|
@@ -766,7 +747,7 @@
|
|
|
766
747
|
string prefix = chunk.Key;
|
|
767
748
|
List<List<Sprite>> chunks = chunk.Value;
|
|
768
749
|
List<(string, List<Sprite>)> finalChunks = new();
|
|
769
|
-
for (int i = 0; i < chunks.Count; i
|
|
750
|
+
for (int i = 0; i < chunks.Count; ++i)
|
|
770
751
|
{
|
|
771
752
|
string atlasName = chunks.Count > 1 ? $"{prefix}_{i}" : prefix;
|
|
772
753
|
finalChunks.Add((atlasName, chunks[i]));
|
|
@@ -775,15 +756,15 @@
|
|
|
775
756
|
})
|
|
776
757
|
.ToList();
|
|
777
758
|
int chunkIndex = 0;
|
|
778
|
-
float atlasCreationProgressStart = 0.45f;
|
|
779
|
-
float atlasCreationProgressRange = 0.5f;
|
|
759
|
+
const float atlasCreationProgressStart = 0.45f;
|
|
760
|
+
const float atlasCreationProgressRange = 0.5f;
|
|
780
761
|
|
|
781
762
|
foreach ((string prefix, List<List<Sprite>> chunks) in groupChunks)
|
|
782
763
|
{
|
|
783
|
-
for (int i = 0; i < chunks.Count; i
|
|
764
|
+
for (int i = 0; i < chunks.Count; ++i)
|
|
784
765
|
{
|
|
785
766
|
List<Sprite> chunk = chunks[i];
|
|
786
|
-
if (chunk
|
|
767
|
+
if (chunk is not { Count: > 0 })
|
|
787
768
|
{
|
|
788
769
|
continue;
|
|
789
770
|
}
|
package/Editor/SpriteCropper.cs
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
private const float AlphaThreshold = 0.01f;
|
|
31
31
|
|
|
32
32
|
[SerializeField]
|
|
33
|
-
private Object
|
|
33
|
+
private List<Object> _inputDirectories = new();
|
|
34
34
|
|
|
35
35
|
[SerializeField]
|
|
36
36
|
private bool _onlyNecessary;
|
|
@@ -44,12 +44,56 @@
|
|
|
44
44
|
{
|
|
45
45
|
GUILayout.Label("Drag folders below", EditorStyles.boldLabel);
|
|
46
46
|
SerializedObject so = new(this);
|
|
47
|
+
so.Update();
|
|
47
48
|
SerializedProperty dirs = so.FindProperty(nameof(_inputDirectories));
|
|
48
49
|
EditorGUILayout.PropertyField(dirs, true);
|
|
49
50
|
SerializedProperty onlyNecessary = so.FindProperty(nameof(_onlyNecessary));
|
|
50
51
|
EditorGUILayout.PropertyField(onlyNecessary, true);
|
|
51
52
|
so.ApplyModifiedProperties();
|
|
52
53
|
|
|
54
|
+
if (GUILayout.Button("Select Input Folder"))
|
|
55
|
+
{
|
|
56
|
+
string path = EditorUtility.OpenFolderPanel(
|
|
57
|
+
"Select Sprite Input Folder",
|
|
58
|
+
Application.dataPath,
|
|
59
|
+
""
|
|
60
|
+
);
|
|
61
|
+
if (!string.IsNullOrWhiteSpace(path))
|
|
62
|
+
{
|
|
63
|
+
if (path.StartsWith(Application.dataPath, StringComparison.Ordinal))
|
|
64
|
+
{
|
|
65
|
+
path = "Assets" + path.Substring(Application.dataPath.Length);
|
|
66
|
+
if (
|
|
67
|
+
!_inputDirectories
|
|
68
|
+
.Select(AssetDatabase.GetAssetPath)
|
|
69
|
+
.Any(directory =>
|
|
70
|
+
string.Equals(
|
|
71
|
+
directory,
|
|
72
|
+
path,
|
|
73
|
+
StringComparison.OrdinalIgnoreCase
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
{
|
|
78
|
+
Object folder = AssetDatabase.LoadAssetAtPath<Object>(path);
|
|
79
|
+
if (folder == null)
|
|
80
|
+
{
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
_inputDirectories.Add(folder);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else
|
|
87
|
+
{
|
|
88
|
+
EditorUtility.DisplayDialog(
|
|
89
|
+
"Invalid Folder",
|
|
90
|
+
"Please select a folder inside the project's Assets directory.",
|
|
91
|
+
"OK"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
53
97
|
if (GUILayout.Button("Find Sprites To Process"))
|
|
54
98
|
{
|
|
55
99
|
FindFilesToProcess();
|
|
@@ -79,7 +123,7 @@
|
|
|
79
123
|
private void FindFilesToProcess()
|
|
80
124
|
{
|
|
81
125
|
_filesToProcess = new List<string>();
|
|
82
|
-
if (_inputDirectories
|
|
126
|
+
if (_inputDirectories is not { Count: > 0 })
|
|
83
127
|
{
|
|
84
128
|
this.LogWarn($"No input directories selected.");
|
|
85
129
|
return;
|
|
@@ -118,7 +162,7 @@
|
|
|
118
162
|
|
|
119
163
|
private void ProcessFoundSprites()
|
|
120
164
|
{
|
|
121
|
-
if (_filesToProcess
|
|
165
|
+
if (_filesToProcess is not { Count: > 0 })
|
|
122
166
|
{
|
|
123
167
|
this.LogWarn($"No files found or selected for processing.");
|
|
124
168
|
return;
|
|
@@ -209,7 +253,7 @@
|
|
|
209
253
|
}
|
|
210
254
|
|
|
211
255
|
TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
|
|
212
|
-
if (importer
|
|
256
|
+
if (importer is not { textureType: TextureImporterType.Sprite })
|
|
213
257
|
{
|
|
214
258
|
return;
|
|
215
259
|
}
|
|
@@ -233,7 +277,7 @@
|
|
|
233
277
|
}
|
|
234
278
|
|
|
235
279
|
TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
|
|
236
|
-
if (importer
|
|
280
|
+
if (importer is not { textureType: TextureImporterType.Sprite })
|
|
237
281
|
{
|
|
238
282
|
return null;
|
|
239
283
|
}
|
|
@@ -10,6 +10,7 @@ namespace WallstopStudios.UnityHelpers.Editor
|
|
|
10
10
|
using System.Linq;
|
|
11
11
|
using UnityEditor;
|
|
12
12
|
using UnityEngine;
|
|
13
|
+
using Utils;
|
|
13
14
|
using Object = UnityEngine.Object;
|
|
14
15
|
|
|
15
16
|
[Serializable]
|
|
@@ -203,9 +204,10 @@ namespace WallstopStudios.UnityHelpers.Editor
|
|
|
203
204
|
valuePropHeight
|
|
204
205
|
);
|
|
205
206
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
using (new GUIIndentScope())
|
|
208
|
+
{
|
|
209
|
+
EditorGUI.PropertyField(valueRect, valueProp, GUIContent.none, true);
|
|
210
|
+
}
|
|
209
211
|
|
|
210
212
|
currentRect.y += valueRect.height + EditorGUIUtility.standardVerticalSpacing;
|
|
211
213
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Editor.Utils
|
|
2
|
+
{
|
|
3
|
+
#if UNITY_EDITOR
|
|
4
|
+
using System;
|
|
5
|
+
using UnityEditor;
|
|
6
|
+
|
|
7
|
+
public sealed class GUIIndentScope : IDisposable
|
|
8
|
+
{
|
|
9
|
+
public GUIIndentScope()
|
|
10
|
+
{
|
|
11
|
+
EditorGUI.indentLevel++;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public void Dispose()
|
|
15
|
+
{
|
|
16
|
+
EditorGUI.indentLevel--;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
#endif
|
|
20
|
+
}
|
package/package.json
CHANGED