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.
- package/Editor/AnimationCopier.cs +875 -93
- package/Editor/AnimationCreator.cs +840 -137
- package/Editor/AnimationEventEditor.cs +4 -4
- package/Editor/AnimatorControllerCopier.cs +3 -3
- package/Editor/Extensions/UnityExtensions.cs +26 -0
- package/Editor/Extensions/UnityExtensions.cs.meta +3 -0
- package/Editor/Extensions.meta +3 -0
- package/Editor/FitTextureSizeWindow.cs +371 -0
- package/Editor/PrefabChecker.cs +716 -0
- package/Editor/SpriteAtlasGenerator.cs +598 -0
- package/Editor/SpriteAtlasGenerator.cs.meta +3 -0
- package/Editor/SpriteCropper.cs +407 -0
- package/Editor/SpriteCropper.cs.meta +3 -0
- package/Editor/SpriteSettingsApplier.cs +756 -92
- package/Editor/TextureResizerWizard.cs +3 -3
- package/Editor/TextureSettingsApplier.cs +9 -9
- package/Editor/WShowIfPropertyDrawer.cs +2 -2
- package/Runtime/Core/Attributes/EnumDisplayNameAttribute.cs +15 -0
- package/Runtime/Core/Attributes/EnumDisplayNameAttribute.cs.meta +3 -0
- package/Runtime/Core/Attributes/ValidateAssignmentAttribute.cs +1 -1
- package/Runtime/Core/Extension/EnumExtensions.cs +176 -1
- package/Runtime/Core/Extension/UnityExtensions.cs +1 -1
- package/Runtime/Core/Helper/Partials/LogHelpers.cs +1 -1
- package/Runtime/Core/Helper/Partials/MathHelpers.cs +1 -1
- package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +3 -4
- package/Runtime/Tags/Attribute.cs +205 -0
- package/Runtime/Tags/Attribute.cs.meta +3 -0
- package/Runtime/Tags/AttributeEffect.cs +276 -0
- package/Runtime/Tags/AttributeEffect.cs.meta +3 -0
- package/Runtime/Tags/AttributeModification.cs +51 -0
- package/Runtime/Tags/AttributeModification.cs.meta +3 -0
- package/Runtime/Tags/AttributeUtilities.cs +209 -0
- package/Runtime/Tags/AttributeUtilities.cs.meta +3 -0
- package/Runtime/Tags/AttributesComponent.cs +163 -0
- package/Runtime/Tags/AttributesComponent.cs.meta +3 -0
- package/Runtime/Tags/CosmeticEffectComponent.cs +50 -0
- package/Runtime/Tags/CosmeticEffectComponent.cs.meta +3 -0
- package/Runtime/Tags/CosmeticEffectData.cs +63 -0
- package/Runtime/Tags/CosmeticEffectData.cs.meta +3 -0
- package/Runtime/Tags/EffectHandle.cs +63 -0
- package/Runtime/Tags/EffectHandle.cs.meta +3 -0
- package/Runtime/Tags/EffectHandler.cs +380 -0
- package/Runtime/Tags/EffectHandler.cs.meta +3 -0
- package/Runtime/Tags/ModificationAction.cs +9 -0
- package/Runtime/Tags/ModificationAction.cs.meta +3 -0
- package/Runtime/Tags/ModifierDurationType.cs +13 -0
- package/Runtime/Tags/ModifierDurationType.cs.meta +3 -0
- package/Runtime/{Utils → Tags}/TagHandler.cs +42 -5
- package/Runtime/Tags.meta +3 -0
- package/Tests/Runtime/DataStructures/BalancedKDTreeTests.cs +1 -1
- package/Tests/Runtime/DataStructures/CyclicBufferTests.cs +1 -1
- package/Tests/Runtime/DataStructures/QuadTreeTests.cs +1 -1
- package/Tests/Runtime/DataStructures/SpatialTreeTests.cs +1 -1
- package/Tests/Runtime/DataStructures/UnbalancedKDTreeTests.cs +1 -1
- package/Tests/Runtime/Extensions/DictionaryExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/EnumExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/IListExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/LoggingExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/RandomExtensionTests.cs +1 -1
- package/Tests/Runtime/Extensions/StringExtensionTests.cs +1 -1
- package/Tests/Runtime/Helper/ObjectHelperTests.cs +1 -0
- package/Tests/Runtime/Helper/WallMathTests.cs +1 -1
- package/Tests/Runtime/Performance/KDTreePerformanceTests.cs +1 -1
- package/Tests/Runtime/Performance/QuadTreePerformanceTests.cs +1 -1
- package/Tests/Runtime/Performance/SpatialTreePerformanceTest.cs +1 -1
- package/Tests/Runtime/Performance/UnbalancedKDTreeTests.cs +1 -1
- package/Tests/Runtime/Random/RandomTestBase.cs +2 -2
- package/Tests/Runtime/Serialization/JsonSerializationTest.cs +1 -1
- package/package.json +1 -1
- package/Editor/FitTextureSizeWizard.cs +0 -147
- package/Editor/PrefabCheckWizard.cs +0 -167
- /package/Editor/{FitTextureSizeWizard.cs.meta → FitTextureSizeWindow.cs.meta} +0 -0
- /package/Editor/{PrefabCheckWizard.cs.meta → PrefabChecker.cs.meta} +0 -0
- /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
|
+
}
|