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,407 @@
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.Threading.Tasks;
9
+ using Core.Extension;
10
+ using UnityEditor;
11
+ using UnityEngine;
12
+ using Object = UnityEngine.Object;
13
+
14
+ public sealed class SpriteCropper : EditorWindow
15
+ {
16
+ private const string Name = "Sprite Cropper";
17
+
18
+ private const string CroppedPrefix = "Cropped_";
19
+ private static readonly string[] ImageFileExtensions =
20
+ {
21
+ ".png",
22
+ ".jpg",
23
+ ".jpeg",
24
+ ".bmp",
25
+ ".tga",
26
+ ".psd",
27
+ ".gif",
28
+ };
29
+
30
+ private const float AlphaThreshold = 0.01f;
31
+
32
+ [SerializeField]
33
+ private Object[] _inputDirectories;
34
+
35
+ [SerializeField]
36
+ private bool _onlyNecessary;
37
+
38
+ private List<string> _filesToProcess;
39
+
40
+ [MenuItem("Tools/Wallstop Studios/Unity Helpers/" + Name)]
41
+ private static void ShowWindow() => GetWindow<SpriteCropper>(Name);
42
+
43
+ private void OnGUI()
44
+ {
45
+ GUILayout.Label("Drag folders below", EditorStyles.boldLabel);
46
+ SerializedObject so = new(this);
47
+ SerializedProperty dirs = so.FindProperty(nameof(_inputDirectories));
48
+ EditorGUILayout.PropertyField(dirs, true);
49
+ SerializedProperty onlyNecessary = so.FindProperty(nameof(_onlyNecessary));
50
+ EditorGUILayout.PropertyField(onlyNecessary, true);
51
+ so.ApplyModifiedProperties();
52
+
53
+ if (GUILayout.Button("Find Sprites To Process"))
54
+ {
55
+ FindFilesToProcess();
56
+ }
57
+
58
+ if (_filesToProcess is { Count: > 0 })
59
+ {
60
+ GUILayout.Label(
61
+ $"Found {_filesToProcess.Count} sprites to process.",
62
+ EditorStyles.boldLabel
63
+ );
64
+ if (GUILayout.Button($"Process {_filesToProcess.Count} Sprites"))
65
+ {
66
+ ProcessFoundSprites();
67
+ _filesToProcess = null;
68
+ }
69
+ }
70
+ else if (_filesToProcess != null)
71
+ {
72
+ GUILayout.Label(
73
+ "No sprites found to process in the selected directories.",
74
+ EditorStyles.label
75
+ );
76
+ }
77
+ }
78
+
79
+ private void FindFilesToProcess()
80
+ {
81
+ _filesToProcess = new List<string>();
82
+ if (_inputDirectories == null || _inputDirectories.Length == 0)
83
+ {
84
+ this.LogWarn($"No input directories selected.");
85
+ return;
86
+ }
87
+
88
+ foreach (Object maybeDirectory in _inputDirectories.Where(d => d != null))
89
+ {
90
+ string assetPath = AssetDatabase.GetAssetPath(maybeDirectory);
91
+ if (!AssetDatabase.IsValidFolder(assetPath))
92
+ {
93
+ this.LogWarn($"Skipping invalid path: {assetPath}");
94
+ continue;
95
+ }
96
+
97
+ IEnumerable<string> files = Directory
98
+ .GetFiles(assetPath, "*.*", SearchOption.AllDirectories)
99
+ .Where(file =>
100
+ Array.Exists(
101
+ ImageFileExtensions,
102
+ extension =>
103
+ file.EndsWith(extension, StringComparison.OrdinalIgnoreCase)
104
+ )
105
+ );
106
+
107
+ foreach (string file in files)
108
+ {
109
+ if (file.Contains(CroppedPrefix, StringComparison.OrdinalIgnoreCase))
110
+ {
111
+ continue;
112
+ }
113
+ _filesToProcess.Add(file);
114
+ }
115
+ }
116
+ Repaint();
117
+ }
118
+
119
+ private void ProcessFoundSprites()
120
+ {
121
+ if (_filesToProcess == null || _filesToProcess.Count == 0)
122
+ {
123
+ this.LogWarn($"No files found or selected for processing.");
124
+ return;
125
+ }
126
+
127
+ string lastProcessed = null;
128
+ try
129
+ {
130
+ int total = _filesToProcess.Count;
131
+ List<TextureImporter> newImporters = new();
132
+ AssetDatabase.StartAssetEditing();
133
+ try
134
+ {
135
+ for (int i = 0; i < _filesToProcess.Count; ++i)
136
+ {
137
+ string file = _filesToProcess[i];
138
+ lastProcessed = file;
139
+ EditorUtility.DisplayProgressBar(
140
+ Name,
141
+ $"Pre-processing {i + 1}/{total}: {Path.GetFileName(file)}",
142
+ i / (float)total
143
+ );
144
+ CheckPreProcessNeeded(file);
145
+ }
146
+ }
147
+ finally
148
+ {
149
+ AssetDatabase.StopAssetEditing();
150
+ AssetDatabase.SaveAssets();
151
+ AssetDatabase.Refresh();
152
+ }
153
+
154
+ AssetDatabase.StartAssetEditing();
155
+ try
156
+ {
157
+ for (int i = 0; i < _filesToProcess.Count; ++i)
158
+ {
159
+ string file = _filesToProcess[i];
160
+ lastProcessed = file;
161
+ EditorUtility.DisplayProgressBar(
162
+ Name,
163
+ $"Processing {i + 1}/{total}: {Path.GetFileName(file)}",
164
+ i / (float)total
165
+ );
166
+ TextureImporter newImporter = ProcessSprite(file);
167
+ if (newImporter != null)
168
+ {
169
+ newImporters.Add(newImporter);
170
+ }
171
+ }
172
+
173
+ foreach (TextureImporter newImporter in newImporters)
174
+ {
175
+ newImporter.SaveAndReimport();
176
+ }
177
+ }
178
+ finally
179
+ {
180
+ AssetDatabase.StopAssetEditing();
181
+ AssetDatabase.SaveAssets();
182
+ AssetDatabase.Refresh();
183
+ }
184
+
185
+ this.Log($"{newImporters.Count} sprites processed successfully.");
186
+ }
187
+ catch (Exception e)
188
+ {
189
+ this.LogError(
190
+ $"An error occurred during processing. Last processed: {lastProcessed}.",
191
+ e
192
+ );
193
+ AssetDatabase.StopAssetEditing();
194
+ AssetDatabase.SaveAssets();
195
+ AssetDatabase.Refresh();
196
+ }
197
+ finally
198
+ {
199
+ EditorUtility.ClearProgressBar();
200
+ }
201
+ }
202
+
203
+ private static void CheckPreProcessNeeded(string assetPath)
204
+ {
205
+ string assetDirectory = Path.GetDirectoryName(assetPath);
206
+ if (string.IsNullOrWhiteSpace(assetDirectory))
207
+ {
208
+ return;
209
+ }
210
+
211
+ TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
212
+ if (importer == null || !importer.textureType.Equals(TextureImporterType.Sprite))
213
+ {
214
+ return;
215
+ }
216
+
217
+ TextureImporterSettings originalSettings = new();
218
+ importer.ReadTextureSettings(originalSettings);
219
+
220
+ if (!importer.isReadable)
221
+ {
222
+ importer.isReadable = true;
223
+ importer.SaveAndReimport();
224
+ }
225
+ }
226
+
227
+ private TextureImporter ProcessSprite(string assetPath)
228
+ {
229
+ string assetDirectory = Path.GetDirectoryName(assetPath);
230
+ if (string.IsNullOrWhiteSpace(assetDirectory))
231
+ {
232
+ return null;
233
+ }
234
+
235
+ TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
236
+ if (importer == null || !importer.textureType.Equals(TextureImporterType.Sprite))
237
+ {
238
+ return null;
239
+ }
240
+
241
+ TextureImporterSettings originalSettings = new();
242
+ importer.ReadTextureSettings(originalSettings);
243
+
244
+ bool originalReadableState = importer.isReadable;
245
+ if (!importer.isReadable)
246
+ {
247
+ importer.isReadable = true;
248
+ importer.SaveAndReimport();
249
+ }
250
+
251
+ Texture2D tex = AssetDatabase.LoadAssetAtPath<Texture2D>(assetPath);
252
+ if (tex == null)
253
+ {
254
+ return null;
255
+ }
256
+
257
+ TextureImporter resultImporter;
258
+ try
259
+ {
260
+ Color32[] pixels = tex.GetPixels32();
261
+
262
+ int width = tex.width;
263
+ int height = tex.height;
264
+ int minX = width;
265
+ int minY = height;
266
+ int maxX = 0;
267
+ int maxY = 0;
268
+ bool hasVisible = false;
269
+
270
+ object lockObject = new();
271
+
272
+ Parallel.For(
273
+ 0,
274
+ width * height,
275
+ () => (minX: width, minY: height, maxX: 0, maxY: 0, hasVisible: false),
276
+ (index, _, localState) =>
277
+ {
278
+ int x = index % width;
279
+ int y = index / width;
280
+
281
+ float a = pixels[index].a / 255f;
282
+ if (a > AlphaThreshold)
283
+ {
284
+ localState.hasVisible = true;
285
+ localState.minX = Mathf.Min(localState.minX, x);
286
+ localState.minY = Mathf.Min(localState.minY, y);
287
+ localState.maxX = Mathf.Max(localState.maxX, x);
288
+ localState.maxY = Mathf.Max(localState.maxY, y);
289
+ }
290
+ return localState;
291
+ },
292
+ finalLocalState =>
293
+ {
294
+ if (finalLocalState.hasVisible)
295
+ {
296
+ lock (lockObject)
297
+ {
298
+ hasVisible = true;
299
+ minX = Mathf.Min(minX, finalLocalState.minX);
300
+ minY = Mathf.Min(minY, finalLocalState.minY);
301
+ maxX = Mathf.Max(maxX, finalLocalState.maxX);
302
+ maxY = Mathf.Max(maxY, finalLocalState.maxY);
303
+ }
304
+ }
305
+ }
306
+ );
307
+
308
+ if (!hasVisible)
309
+ {
310
+ return null;
311
+ }
312
+
313
+ int cropWidth = maxX - minX + 1;
314
+ int cropHeight = maxY - minY + 1;
315
+
316
+ if (_onlyNecessary && cropWidth == width && cropHeight == height)
317
+ {
318
+ return null;
319
+ }
320
+
321
+ Texture2D cropped = new(cropWidth, cropHeight, TextureFormat.RGBA32, false);
322
+ Color32[] croppedPixels = new Color32[cropWidth * cropHeight];
323
+
324
+ Parallel.For(
325
+ 0,
326
+ cropHeight,
327
+ y =>
328
+ {
329
+ int sourceYOffset = (y + minY) * width;
330
+ int destYOffset = y * cropWidth;
331
+ for (int x = 0; x < cropWidth; ++x)
332
+ {
333
+ croppedPixels[destYOffset + x] = pixels[sourceYOffset + x + minX];
334
+ }
335
+ }
336
+ );
337
+
338
+ cropped.SetPixels32(croppedPixels);
339
+ cropped.Apply();
340
+
341
+ string newPath = Path.Combine(
342
+ assetDirectory,
343
+ CroppedPrefix + Path.GetFileName(assetPath)
344
+ );
345
+ File.WriteAllBytes(newPath, cropped.EncodeToPNG());
346
+ DestroyImmediate(cropped);
347
+ AssetDatabase.ImportAsset(newPath);
348
+ TextureImporter newImporter = AssetImporter.GetAtPath(newPath) as TextureImporter;
349
+ if (newImporter == null)
350
+ {
351
+ return null;
352
+ }
353
+
354
+ newImporter.textureType = importer.textureType;
355
+ newImporter.spriteImportMode = importer.spriteImportMode;
356
+ newImporter.filterMode = importer.filterMode;
357
+ newImporter.textureCompression = importer.textureCompression;
358
+ newImporter.wrapMode = importer.wrapMode;
359
+ newImporter.mipmapEnabled = importer.mipmapEnabled;
360
+ newImporter.spritePixelsPerUnit = importer.spritePixelsPerUnit;
361
+
362
+ TextureImporterSettings newSettings = new();
363
+ newImporter.ReadTextureSettings(newSettings);
364
+ newSettings.spriteExtrude = originalSettings.spriteExtrude;
365
+
366
+ Vector2 origPivot = GetSpritePivot(importer);
367
+ Vector2 origCenter = new(width * origPivot.x, height * origPivot.y);
368
+ Vector2 newPivotPixels = origCenter - new Vector2(minX, minY);
369
+ Vector2 newPivotNorm = new(
370
+ cropWidth > 0 ? newPivotPixels.x / cropWidth : 0.5f,
371
+ cropHeight > 0 ? newPivotPixels.y / cropHeight : 0.5f
372
+ );
373
+
374
+ newImporter.spriteImportMode = SpriteImportMode.Single;
375
+ newImporter.spritePivot = newPivotNorm;
376
+ newSettings.spritePivot = newPivotNorm;
377
+ newSettings.spriteAlignment = (int)SpriteAlignment.Custom;
378
+
379
+ newImporter.SetTextureSettings(newSettings);
380
+ newImporter.isReadable = false;
381
+
382
+ resultImporter = newImporter;
383
+ }
384
+ finally
385
+ {
386
+ if (importer != null && importer.isReadable != originalReadableState)
387
+ {
388
+ importer.isReadable = originalReadableState;
389
+ importer.SaveAndReimport();
390
+ }
391
+ }
392
+
393
+ return resultImporter;
394
+ }
395
+
396
+ private static Vector2 GetSpritePivot(TextureImporter importer)
397
+ {
398
+ if (importer.spriteImportMode == SpriteImportMode.Single)
399
+ {
400
+ return importer.spritePivot;
401
+ }
402
+
403
+ return new Vector2(0.5f, 0.5f);
404
+ }
405
+ }
406
+ #endif
407
+ }
@@ -0,0 +1,3 @@
1
+ fileFormatVersion: 2
2
+ guid: cefeb89d99014cba9821f76665919906
3
+ timeCreated: 1746459181