com.wallstop-studios.unity-helpers 2.0.0-rc73.4 → 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.
@@ -437,7 +437,10 @@
437
437
  string sourceRelPath = AssetDatabase.GUIDToAssetPath(guid);
438
438
  if (
439
439
  string.IsNullOrWhiteSpace(sourceRelPath)
440
- || !sourceRelPath.StartsWith(_animationSourcePathRelative)
440
+ || !sourceRelPath.StartsWith(
441
+ _animationSourcePathRelative,
442
+ StringComparison.OrdinalIgnoreCase
443
+ )
441
444
  )
442
445
  {
443
446
  continue;
@@ -506,7 +509,10 @@
506
509
  else
507
510
  {
508
511
  string destHash = CalculateFileHash(destFullPath);
509
- if (string.IsNullOrEmpty(sourceInfo.Hash) || string.IsNullOrEmpty(destHash))
512
+ if (
513
+ string.IsNullOrWhiteSpace(sourceInfo.Hash)
514
+ || string.IsNullOrWhiteSpace(destHash)
515
+ )
510
516
  {
511
517
  this.LogWarn(
512
518
  $"Could not compare '{sourceInfo.FileName}' due to hashing error. Treating as 'Changed'."
@@ -594,8 +600,32 @@
594
600
 
595
601
  int successCount = 0;
596
602
  int errorCount = 0;
597
- AssetDatabase.StartAssetEditing();
603
+ foreach (AnimationFileInfo animInfo in animationsToCopy)
604
+ {
605
+ string destinationAssetPath = animInfo.DestinationRelativePath;
606
+ string destDirectory = Path.GetDirectoryName(destinationAssetPath).SanitizePath();
607
+
608
+ if (
609
+ string.IsNullOrWhiteSpace(destDirectory)
610
+ || AssetDatabase.IsValidFolder(destDirectory)
611
+ )
612
+ {
613
+ continue;
614
+ }
598
615
 
616
+ try
617
+ {
618
+ EnsureDirectoryExists(destDirectory);
619
+ }
620
+ catch (Exception ex)
621
+ {
622
+ this.LogError(
623
+ $"Failed to create destination directory '{destDirectory}' for animation '{animInfo.FileName}'. Error: {ex.Message}. Skipping."
624
+ );
625
+ }
626
+ }
627
+
628
+ AssetDatabase.StartAssetEditing();
599
629
  try
600
630
  {
601
631
  for (int i = 0; i < animationsToCopy.Count; i++)
@@ -616,28 +646,6 @@
616
646
 
617
647
  string sourceAssetPath = animInfo.RelativePath;
618
648
  string destinationAssetPath = animInfo.DestinationRelativePath;
619
- string destDirectory = Path.GetDirectoryName(destinationAssetPath)
620
- .SanitizePath();
621
-
622
- if (
623
- !string.IsNullOrEmpty(destDirectory)
624
- && !AssetDatabase.IsValidFolder(destDirectory)
625
- )
626
- {
627
- try
628
- {
629
- EnsureDirectoryExists(destDirectory);
630
- }
631
- catch (Exception ex)
632
- {
633
- this.LogError(
634
- $"Failed to create destination directory '{destDirectory}' for animation '{animInfo.FileName}'. Error: {ex.Message}. Skipping."
635
- );
636
- errorCount++;
637
- continue;
638
- }
639
- }
640
-
641
649
  bool copySuccessful = AssetDatabase.CopyAsset(
642
650
  sourceAssetPath,
643
651
  destinationAssetPath
@@ -868,28 +876,28 @@
868
876
  return string.Empty;
869
877
  }
870
878
 
871
- private static string CalculateFileHash(string filePath)
879
+ private string CalculateFileHash(string filePath)
872
880
  {
873
881
  try
874
882
  {
875
- using (MD5 md5 = MD5.Create())
876
- using (FileStream stream = File.OpenRead(filePath))
877
- {
878
- byte[] hashBytes = md5.ComputeHash(stream);
879
- return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
880
- }
883
+ using MD5 md5 = MD5.Create();
884
+ using FileStream stream = File.OpenRead(filePath);
885
+ byte[] hashBytes = md5.ComputeHash(stream);
886
+ return BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
881
887
  }
882
888
  catch (IOException ioEx)
883
889
  {
884
- Debug.LogError(
885
- $"[AnimationCopierWindow] IO Error calculating hash for {filePath}: {ioEx.Message}"
890
+ this.LogError(
891
+ $"[AnimationCopierWindow] IO Error calculating hash for {filePath}.",
892
+ ioEx
886
893
  );
887
894
  return string.Empty;
888
895
  }
889
896
  catch (Exception ex)
890
897
  {
891
- Debug.LogError(
892
- $"[AnimationCopierWindow] Error calculating hash for {filePath}: {ex.Message}"
898
+ this.LogError(
899
+ $"[AnimationCopierWindow] Error calculating hash for {filePath}.",
900
+ ex
893
901
  );
894
902
  return string.Empty;
895
903
  }
@@ -901,6 +909,7 @@
901
909
  {
902
910
  return;
903
911
  }
912
+
904
913
  if (!relativeDirectoryPath.StartsWith("Assets/"))
905
914
  {
906
915
  if (relativeDirectoryPath.Equals("Assets", StringComparison.OrdinalIgnoreCase))
@@ -923,15 +932,14 @@
923
932
  }
924
933
 
925
934
  string parentPath = Path.GetDirectoryName(relativeDirectoryPath).SanitizePath();
926
-
927
935
  if (
928
- string.IsNullOrEmpty(parentPath)
936
+ string.IsNullOrWhiteSpace(parentPath)
929
937
  || parentPath.Equals("Assets", StringComparison.OrdinalIgnoreCase)
930
938
  )
931
939
  {
932
940
  string folderNameToCreate = Path.GetFileName(relativeDirectoryPath);
933
941
  if (
934
- !string.IsNullOrEmpty(folderNameToCreate)
942
+ !string.IsNullOrWhiteSpace(folderNameToCreate)
935
943
  && !AssetDatabase.IsValidFolder(relativeDirectoryPath)
936
944
  )
937
945
  {
@@ -941,10 +949,9 @@
941
949
  }
942
950
 
943
951
  EnsureDirectoryExists(parentPath);
944
-
945
952
  string currentFolderName = Path.GetFileName(relativeDirectoryPath);
946
953
  if (
947
- !string.IsNullOrEmpty(currentFolderName)
954
+ !string.IsNullOrWhiteSpace(currentFolderName)
948
955
  && !AssetDatabase.IsValidFolder(relativeDirectoryPath)
949
956
  )
950
957
  {
@@ -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(s => s == null)
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
- EditorGUI.indentLevel++;
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 == null || animationData.Count == 0))
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 == null || animationData.Count == 0)
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: not 0 })
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: not 0 })
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: not 0 } || _compiledRegex == null)
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: not 0 })
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
- EditorGUI.indentLevel++;
176
- RenderAnimationEventItem(item, frame, frameRate);
177
-
178
- if (i != stateCopy.Count - 1)
176
+ using (new GUIIndentScope())
179
177
  {
180
- DrawGuiLine(height: 3, color: new Color(0f, 1f, 0.3f, 1f));
181
- EditorGUILayout.Space();
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
- EditorGUILayout.BeginHorizontal();
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
- EditorGUI.indentLevel++;
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
- EditorGUILayout.BeginHorizontal();
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
  {
@@ -40,7 +40,7 @@
40
40
  nameof(_textureSourcePaths)
41
41
  );
42
42
 
43
- if (_textureSourcePaths != null && _textureSourcePaths.Count != 0)
43
+ if (_textureSourcePaths is { Count: > 0 })
44
44
  {
45
45
  return;
46
46
  }
@@ -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
- EditorGUI.indentLevel++;
152
- try
153
- {
154
- DrawAndAlign(
155
- new GUIContent(
156
- "Only if [ValidateAssignment]",
157
- "Only report null object references if the field has the [ValidateAssignment] attribute."
158
- ),
159
- () => _onlyCheckNullObjectsWithAttribute,
160
- v => _onlyCheckNullObjectsWithAttribute = v,
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 == null || _assetPaths.Count == 0)
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 != null && sprite.rect is { width: > 0, height: > 0 })
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.IsNullOrEmpty(absPath))
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.IsNullOrEmpty(folderPath) || !AssetDatabase.IsValidFolder(folderPath)
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
- IEnumerable<Sprite> sprites = assets.OfType<Sprite>();
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 (string.IsNullOrEmpty(parent) || string.IsNullOrEmpty(newFolderName))
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(p => !string.IsNullOrEmpty(p))
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.IsNullOrEmpty(assetPath))
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(s => s != null && s.rect is { width: > 0, height: > 0 })
528
- .OrderByDescending(s => s.rect.width * s.rect.height)
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(c => c.TotalArea)
625
- .ThenBy(c => c.CandidateName, StringComparer.Ordinal)
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(c => new MergeableAtlas(
630
- c.OriginalGroupKey,
631
- c.Sprites,
632
- c.CandidateName,
633
- c.TotalArea
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.30f;
638
- float mergeOptimizationProgressRange = 0.50f;
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 / 15.0f);
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(a => a.TotalArea)
658
- .ThenBy(a => a.RepresentativeInitialName, StringComparer.Ordinal)
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(k => k, StringComparer.Ordinal)
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(k => k, StringComparer.Ordinal)
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(a => (Name: a.GenerateFinalName(), Sprites: a.Sprites))
758
- .OrderBy(a => a.Name)
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 == null || chunk.Count == 0)
767
+ if (chunk is not { Count: > 0 })
787
768
  {
788
769
  continue;
789
770
  }
@@ -30,7 +30,7 @@
30
30
  private const float AlphaThreshold = 0.01f;
31
31
 
32
32
  [SerializeField]
33
- private Object[] _inputDirectories;
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 == null || _inputDirectories.Length == 0)
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 == null || _filesToProcess.Count == 0)
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 == null || !importer.textureType.Equals(TextureImporterType.Sprite))
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 == null || !importer.textureType.Equals(TextureImporterType.Sprite))
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
- EditorGUI.indentLevel++;
207
- EditorGUI.PropertyField(valueRect, valueProp, GUIContent.none, true);
208
- EditorGUI.indentLevel--;
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
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: ba7be1ee2025494c963d393c9b5e5c2f
3
+ timeCreated: 1746811930
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.unity-helpers",
3
- "version": "2.0.0-rc73.4",
3
+ "version": "2.0.0-rc73.6",
4
4
  "displayName": "Unity Helpers",
5
5
  "description": "Various Unity Helper Library",
6
6
  "dependencies": {},
@@ -40,3 +40,5 @@
40
40
 
41
41
 
42
42
 
43
+
44
+