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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
namespace WallstopStudios.UnityHelpers.Editor
|
|
2
2
|
{
|
|
3
3
|
#if UNITY_EDITOR
|
|
4
|
+
using Core.Extension;
|
|
4
5
|
using System;
|
|
5
6
|
using System.Collections.Generic;
|
|
7
|
+
using System.IO;
|
|
6
8
|
using System.Linq;
|
|
9
|
+
using System.Text.RegularExpressions;
|
|
7
10
|
using UnityEditor;
|
|
8
11
|
using UnityEngine;
|
|
9
|
-
using WallstopStudios.UnityHelpers.Core.Extension;
|
|
10
12
|
using Object = UnityEngine.Object;
|
|
11
13
|
|
|
12
14
|
[Serializable]
|
|
@@ -14,236 +16,937 @@
|
|
|
14
16
|
{
|
|
15
17
|
public const int DefaultFramesPerSecond = 12;
|
|
16
18
|
|
|
17
|
-
public List<
|
|
19
|
+
public List<Sprite> frames = new();
|
|
18
20
|
public int framesPerSecond = DefaultFramesPerSecond;
|
|
19
21
|
public string animationName = string.Empty;
|
|
22
|
+
public bool isCreatedFromAutoParse;
|
|
23
|
+
public bool loop;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
public sealed class
|
|
26
|
+
public sealed class AnimationCreatorWindow : EditorWindow
|
|
23
27
|
{
|
|
24
|
-
|
|
28
|
+
private SerializedObject _serializedObject;
|
|
29
|
+
private SerializedProperty _animationDataProp;
|
|
30
|
+
private SerializedProperty _animationSourcesProp;
|
|
31
|
+
private SerializedProperty _spriteNameRegexProp;
|
|
32
|
+
private SerializedProperty _textProp;
|
|
33
|
+
|
|
34
|
+
public List<AnimationData> animationData = new();
|
|
25
35
|
public List<Object> animationSources = new();
|
|
36
|
+
public string spriteNameRegex = ".*";
|
|
26
37
|
public string text;
|
|
27
38
|
|
|
28
|
-
[
|
|
29
|
-
|
|
39
|
+
[HideInInspector]
|
|
40
|
+
[SerializeField]
|
|
41
|
+
private List<Sprite> _filteredSprites = new();
|
|
42
|
+
private int _matchedSpriteCount;
|
|
43
|
+
private int _unmatchedSpriteCount;
|
|
44
|
+
private Regex _compiledRegex;
|
|
45
|
+
private string _lastUsedRegex;
|
|
46
|
+
private string _searchString = string.Empty;
|
|
47
|
+
private Vector2 _scrollPosition;
|
|
48
|
+
private string _errorMessage = string.Empty;
|
|
49
|
+
private bool _animationDataIsExpanded = true;
|
|
50
|
+
|
|
51
|
+
[MenuItem("Tools/Wallstop Studios/Unity Helpers/Animation Creator", priority = -3)]
|
|
52
|
+
public static void ShowWindow()
|
|
53
|
+
{
|
|
54
|
+
GetWindow<AnimationCreatorWindow>("Animation Creator");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private void OnEnable()
|
|
30
58
|
{
|
|
31
|
-
|
|
59
|
+
_serializedObject = new SerializedObject(this);
|
|
60
|
+
_animationDataProp = _serializedObject.FindProperty(nameof(animationData));
|
|
61
|
+
_animationSourcesProp = _serializedObject.FindProperty(nameof(animationSources));
|
|
62
|
+
_spriteNameRegexProp = _serializedObject.FindProperty(nameof(spriteNameRegex));
|
|
63
|
+
_textProp = _serializedObject.FindProperty(nameof(text));
|
|
64
|
+
|
|
65
|
+
UpdateRegex();
|
|
66
|
+
FindAndFilterSprites();
|
|
67
|
+
Repaint();
|
|
32
68
|
}
|
|
33
69
|
|
|
34
|
-
|
|
70
|
+
private void OnGUI()
|
|
35
71
|
{
|
|
36
|
-
|
|
72
|
+
_serializedObject.Update();
|
|
37
73
|
|
|
38
|
-
|
|
74
|
+
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
|
|
75
|
+
|
|
76
|
+
EditorGUILayout.LabelField("Configuration", EditorStyles.boldLabel);
|
|
77
|
+
EditorGUILayout.PropertyField(_animationSourcesProp, true);
|
|
78
|
+
EditorGUILayout.PropertyField(_spriteNameRegexProp);
|
|
79
|
+
EditorGUILayout.PropertyField(_textProp);
|
|
80
|
+
|
|
81
|
+
DrawAddSourceFolderButton();
|
|
82
|
+
|
|
83
|
+
if (!string.IsNullOrWhiteSpace(_errorMessage))
|
|
84
|
+
{
|
|
85
|
+
EditorGUILayout.HelpBox(_errorMessage, MessageType.Error);
|
|
86
|
+
}
|
|
87
|
+
else if (
|
|
88
|
+
_animationSourcesProp.arraySize == 0
|
|
89
|
+
|| _animationSourcesProp.FindPropertyRelative("Array.size").intValue == 0
|
|
90
|
+
|| animationSources.TrueForAll(s => s == null)
|
|
91
|
+
)
|
|
39
92
|
{
|
|
40
|
-
|
|
93
|
+
EditorGUILayout.HelpBox(
|
|
94
|
+
"Please specify at least one Animation Source (folder).",
|
|
95
|
+
MessageType.Error
|
|
96
|
+
);
|
|
97
|
+
}
|
|
41
98
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
99
|
+
EditorGUILayout.Space();
|
|
100
|
+
EditorGUILayout.LabelField("Animation Data", EditorStyles.boldLabel);
|
|
101
|
+
_searchString = EditorGUILayout.TextField("Search Animation Name", _searchString);
|
|
102
|
+
|
|
103
|
+
DrawFilteredAnimationData();
|
|
104
|
+
|
|
105
|
+
EditorGUILayout.Space();
|
|
106
|
+
EditorGUILayout.LabelField("Sprite Filter Status", EditorStyles.boldLabel);
|
|
107
|
+
EditorGUILayout.LabelField("Regex Pattern:", spriteNameRegex);
|
|
108
|
+
EditorGUILayout.LabelField("Matched Sprites:", _matchedSpriteCount.ToString());
|
|
109
|
+
EditorGUILayout.LabelField("Unmatched Sprites:", _unmatchedSpriteCount.ToString());
|
|
110
|
+
|
|
111
|
+
EditorGUILayout.Space();
|
|
112
|
+
EditorGUILayout.LabelField("Actions", EditorStyles.boldLabel);
|
|
113
|
+
|
|
114
|
+
DrawActionButtons();
|
|
115
|
+
|
|
116
|
+
EditorGUILayout.EndScrollView();
|
|
117
|
+
|
|
118
|
+
_ = _serializedObject.ApplyModifiedProperties();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private void DrawAddSourceFolderButton()
|
|
122
|
+
{
|
|
123
|
+
if (!GUILayout.Button("Add Source Folder..."))
|
|
124
|
+
{
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
string absolutePath = EditorUtility.OpenFolderPanel(
|
|
129
|
+
"Select Animation Source Folder",
|
|
130
|
+
"Assets",
|
|
131
|
+
""
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (string.IsNullOrWhiteSpace(absolutePath))
|
|
135
|
+
{
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
absolutePath = absolutePath.Replace("\\", "/");
|
|
140
|
+
if (absolutePath.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase))
|
|
141
|
+
{
|
|
142
|
+
string relativePath =
|
|
143
|
+
"Assets" + absolutePath.Substring(Application.dataPath.Length);
|
|
144
|
+
|
|
145
|
+
DefaultAsset folderAsset = AssetDatabase.LoadAssetAtPath<DefaultAsset>(
|
|
146
|
+
relativePath
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (folderAsset != null && AssetDatabase.IsValidFolder(relativePath))
|
|
47
150
|
{
|
|
48
|
-
|
|
49
|
-
|
|
151
|
+
bool alreadyExists = false;
|
|
152
|
+
for (int i = 0; i < _animationSourcesProp.arraySize; ++i)
|
|
50
153
|
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
foreach (
|
|
56
|
-
string assetGuid in AssetDatabase.FindAssets(
|
|
57
|
-
"t:sprite",
|
|
58
|
-
animationPaths.ToArray()
|
|
154
|
+
if (
|
|
155
|
+
_animationSourcesProp.GetArrayElementAtIndex(i).objectReferenceValue
|
|
156
|
+
== folderAsset
|
|
59
157
|
)
|
|
60
|
-
)
|
|
61
|
-
{
|
|
62
|
-
string path = AssetDatabase.GUIDToAssetPath(assetGuid);
|
|
63
|
-
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(path);
|
|
64
|
-
if (sprite != null && sprite.texture != null)
|
|
65
158
|
{
|
|
66
|
-
|
|
159
|
+
alreadyExists = true;
|
|
160
|
+
this.LogWarn($"Folder '{relativePath}' is already in the list.");
|
|
161
|
+
break;
|
|
67
162
|
}
|
|
68
163
|
}
|
|
69
164
|
|
|
70
|
-
|
|
165
|
+
if (!alreadyExists)
|
|
166
|
+
{
|
|
167
|
+
_animationSourcesProp.arraySize++;
|
|
168
|
+
_animationSourcesProp
|
|
169
|
+
.GetArrayElementAtIndex(_animationSourcesProp.arraySize - 1)
|
|
170
|
+
.objectReferenceValue = folderAsset;
|
|
171
|
+
this.Log($"Added source folder: {relativePath}");
|
|
172
|
+
|
|
173
|
+
_serializedObject.ApplyModifiedProperties();
|
|
174
|
+
FindAndFilterSprites();
|
|
175
|
+
Repaint();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else
|
|
179
|
+
{
|
|
180
|
+
this.LogError(
|
|
181
|
+
$"Could not load folder asset at path: {relativePath}. Is it a valid folder within the project?"
|
|
182
|
+
);
|
|
71
183
|
}
|
|
184
|
+
}
|
|
185
|
+
else
|
|
186
|
+
{
|
|
187
|
+
this.LogError(
|
|
188
|
+
$"Selected folder must be inside the project's Assets folder. Path selected: {absolutePath}"
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
72
192
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
193
|
+
private void DrawCheckSpritesButton()
|
|
194
|
+
{
|
|
195
|
+
if (GUILayout.Button("Check/Refresh Filtered Sprites"))
|
|
196
|
+
{
|
|
197
|
+
UpdateRegex();
|
|
198
|
+
FindAndFilterSprites();
|
|
199
|
+
Repaint();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private void DrawFilteredAnimationData()
|
|
204
|
+
{
|
|
205
|
+
int listSize = _animationDataProp.arraySize;
|
|
206
|
+
string[] searchTerms = string.IsNullOrWhiteSpace(_searchString)
|
|
207
|
+
? Array.Empty<string>()
|
|
208
|
+
: _searchString.Split(
|
|
209
|
+
new[] { ' ', '\t', '\n', '\r' },
|
|
210
|
+
StringSplitOptions.RemoveEmptyEntries
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
List<int> matchingIndices = new();
|
|
214
|
+
for (int i = 0; i < listSize; ++i)
|
|
215
|
+
{
|
|
216
|
+
SerializedProperty elementProp = _animationDataProp.GetArrayElementAtIndex(i);
|
|
217
|
+
SerializedProperty nameProp = elementProp.FindPropertyRelative(
|
|
218
|
+
nameof(AnimationData.animationName)
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
string currentName =
|
|
222
|
+
nameProp != null ? (nameProp.stringValue ?? string.Empty) : string.Empty;
|
|
223
|
+
|
|
224
|
+
bool matchesSearch = true;
|
|
225
|
+
if (searchTerms.Length > 0)
|
|
77
226
|
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
227
|
+
matchesSearch = searchTerms.All(term =>
|
|
228
|
+
currentName.Contains(term, StringComparison.OrdinalIgnoreCase)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (matchesSearch)
|
|
233
|
+
{
|
|
234
|
+
matchingIndices.Add(i);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
int matchCount = matchingIndices.Count;
|
|
238
|
+
string foldoutLabel =
|
|
239
|
+
$"{_animationDataProp.displayName} (Showing {matchCount} / {listSize})";
|
|
240
|
+
_animationDataIsExpanded = EditorGUILayout.Foldout(
|
|
241
|
+
_animationDataIsExpanded,
|
|
242
|
+
foldoutLabel,
|
|
243
|
+
true
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (_animationDataIsExpanded)
|
|
247
|
+
{
|
|
248
|
+
EditorGUI.indentLevel++;
|
|
249
|
+
if (matchCount > 0)
|
|
250
|
+
{
|
|
251
|
+
foreach (int index in matchingIndices)
|
|
252
|
+
{
|
|
253
|
+
SerializedProperty elementProp = _animationDataProp.GetArrayElementAtIndex(
|
|
254
|
+
index
|
|
89
255
|
);
|
|
90
|
-
int lastNumericIndex = frameName.Length - 1;
|
|
91
|
-
for (int i = frameName.Length - 1; 0 <= i; --i)
|
|
92
|
-
{
|
|
93
|
-
if (char.IsNumber(frameName[i]))
|
|
94
|
-
{
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
256
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
257
|
+
SerializedProperty nameProp = elementProp.FindPropertyRelative(
|
|
258
|
+
nameof(AnimationData.animationName)
|
|
259
|
+
);
|
|
260
|
+
string currentName =
|
|
261
|
+
nameProp != null ? nameProp.stringValue ?? string.Empty : string.Empty;
|
|
262
|
+
string labelText = string.IsNullOrWhiteSpace(currentName)
|
|
263
|
+
? $"Element {index} (No Name)"
|
|
264
|
+
: currentName;
|
|
101
265
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
{
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
266
|
+
EditorGUILayout.PropertyField(elementProp, new GUIContent(labelText), true);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
else if (listSize > 0)
|
|
270
|
+
{
|
|
271
|
+
EditorGUILayout.HelpBox(
|
|
272
|
+
$"No animation data matched the search term '{_searchString}'.",
|
|
273
|
+
MessageType.Info
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
EditorGUI.indentLevel--;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private void DrawActionButtons()
|
|
282
|
+
{
|
|
283
|
+
DrawCheckSpritesButton();
|
|
284
|
+
EditorGUILayout.Space();
|
|
285
|
+
EditorGUILayout.LabelField("Actions", EditorStyles.boldLabel);
|
|
286
|
+
using (new EditorGUI.DisabledScope(_filteredSprites.Count == 0))
|
|
287
|
+
{
|
|
288
|
+
if (
|
|
289
|
+
GUILayout.Button(
|
|
290
|
+
$"Populate First Slot with {_filteredSprites.Count} Matched Sprites"
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
{
|
|
294
|
+
if (animationData.Count == 0)
|
|
295
|
+
{
|
|
296
|
+
this.LogWarn($"Add at least one Animation Data entry first.");
|
|
297
|
+
}
|
|
298
|
+
else if (animationData[0].frames.Count > 0)
|
|
299
|
+
{
|
|
300
|
+
if (
|
|
301
|
+
!EditorUtility.DisplayDialog(
|
|
302
|
+
"Confirm Overwrite",
|
|
303
|
+
"This will replace the frames currently in the first animation slot. Are you sure?",
|
|
304
|
+
"Replace",
|
|
305
|
+
"Cancel"
|
|
306
|
+
)
|
|
307
|
+
)
|
|
115
308
|
{
|
|
116
|
-
|
|
309
|
+
return;
|
|
117
310
|
}
|
|
118
311
|
}
|
|
312
|
+
if (animationData.Count > 0)
|
|
313
|
+
{
|
|
314
|
+
animationData[0].frames = new List<Sprite>(_filteredSprites);
|
|
315
|
+
animationData[0].animationName = "All_Matched_Sprites";
|
|
316
|
+
animationData[0].isCreatedFromAutoParse = false;
|
|
317
|
+
_serializedObject.Update();
|
|
318
|
+
Repaint();
|
|
319
|
+
this.Log($"Populated first slot with {_filteredSprites.Count} sprites.");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
119
322
|
|
|
120
|
-
|
|
323
|
+
if (GUILayout.Button("Auto-Parse Matched Sprites into Animations"))
|
|
324
|
+
{
|
|
325
|
+
if (
|
|
326
|
+
EditorUtility.DisplayDialog(
|
|
327
|
+
"Confirm Auto-Parse",
|
|
328
|
+
"This will replace the current animation list with animations generated from matched sprites based on their names (e.g., 'Player_Run_0', 'Player_Run_1'). Are you sure?",
|
|
329
|
+
"Parse",
|
|
330
|
+
"Cancel"
|
|
331
|
+
)
|
|
332
|
+
)
|
|
121
333
|
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
assetPathAndTextures.Value.Select(
|
|
126
|
-
textureAndPrefix => new AnimationData
|
|
127
|
-
{
|
|
128
|
-
frames = textureAndPrefix.Value,
|
|
129
|
-
framesPerSecond = data.framesPerSecond,
|
|
130
|
-
animationName = $"Anim_{textureAndPrefix.Key}",
|
|
131
|
-
}
|
|
132
|
-
)
|
|
133
|
-
)
|
|
134
|
-
);
|
|
334
|
+
AutoParseSprites();
|
|
335
|
+
_serializedObject.Update();
|
|
336
|
+
Repaint();
|
|
135
337
|
}
|
|
136
338
|
}
|
|
137
339
|
}
|
|
138
340
|
|
|
139
|
-
if (
|
|
341
|
+
if (_filteredSprites.Count == 0)
|
|
342
|
+
{
|
|
343
|
+
EditorGUILayout.HelpBox(
|
|
344
|
+
"Cannot perform sprite actions: No sprites matched the filter criteria or sources are empty.",
|
|
345
|
+
MessageType.Info
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
bool canBulkName =
|
|
140
350
|
animationData is { Count: > 0 }
|
|
141
|
-
&& animationData.Any(data => data.frames
|
|
142
|
-
&& !string.IsNullOrWhiteSpace(text)
|
|
143
|
-
|
|
351
|
+
&& animationData.Any(data => data.frames?.Count > 0)
|
|
352
|
+
&& !string.IsNullOrWhiteSpace(text);
|
|
353
|
+
|
|
354
|
+
using (new EditorGUI.DisabledScope(!canBulkName))
|
|
144
355
|
{
|
|
145
|
-
|
|
356
|
+
EditorGUILayout.Space();
|
|
357
|
+
EditorGUILayout.LabelField("Bulk Naming Operations", EditorStyles.boldLabel);
|
|
358
|
+
|
|
359
|
+
if (GUILayout.Button($"Append '{text}' To All Animation Names"))
|
|
146
360
|
{
|
|
361
|
+
bool changed = false;
|
|
147
362
|
foreach (AnimationData data in animationData)
|
|
148
363
|
{
|
|
149
|
-
|
|
364
|
+
if (
|
|
365
|
+
!string.IsNullOrWhiteSpace(data.animationName)
|
|
366
|
+
&& !data.animationName.EndsWith($"_{text}")
|
|
367
|
+
)
|
|
368
|
+
{
|
|
369
|
+
data.animationName += $"_{text}";
|
|
370
|
+
changed = true;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (changed)
|
|
374
|
+
{
|
|
375
|
+
this.Log($"Appended '{text}' to animation names.");
|
|
376
|
+
_serializedObject.Update();
|
|
377
|
+
Repaint();
|
|
378
|
+
}
|
|
379
|
+
else
|
|
380
|
+
{
|
|
381
|
+
this.LogWarn(
|
|
382
|
+
$"No animation names modified. Either none exist or they already end with '_{text}'."
|
|
383
|
+
);
|
|
150
384
|
}
|
|
151
385
|
}
|
|
152
386
|
|
|
153
|
-
if (GUILayout.Button("Remove
|
|
387
|
+
if (GUILayout.Button($"Remove '{text}' From End of Names"))
|
|
154
388
|
{
|
|
389
|
+
bool changed = false;
|
|
390
|
+
string suffix = $"_{text}";
|
|
155
391
|
foreach (AnimationData data in animationData)
|
|
156
392
|
{
|
|
157
|
-
if (
|
|
393
|
+
if (
|
|
394
|
+
!string.IsNullOrWhiteSpace(data.animationName)
|
|
395
|
+
&& data.animationName.EndsWith(suffix)
|
|
396
|
+
)
|
|
397
|
+
{
|
|
398
|
+
data.animationName = data.animationName.Remove(
|
|
399
|
+
data.animationName.Length - suffix.Length
|
|
400
|
+
);
|
|
401
|
+
changed = true;
|
|
402
|
+
}
|
|
403
|
+
else if (
|
|
404
|
+
!string.IsNullOrWhiteSpace(data.animationName)
|
|
405
|
+
&& data.animationName.EndsWith(text)
|
|
406
|
+
)
|
|
158
407
|
{
|
|
159
408
|
data.animationName = data.animationName.Remove(
|
|
160
409
|
data.animationName.Length - text.Length
|
|
161
410
|
);
|
|
411
|
+
changed = true;
|
|
162
412
|
}
|
|
163
413
|
}
|
|
414
|
+
if (changed)
|
|
415
|
+
{
|
|
416
|
+
this.Log($"Removed '{text}' suffix from animation names.");
|
|
417
|
+
_serializedObject.Update();
|
|
418
|
+
Repaint();
|
|
419
|
+
}
|
|
420
|
+
else
|
|
421
|
+
{
|
|
422
|
+
this.LogWarn(
|
|
423
|
+
$"No animation names modified. Either none exist or they do not end with '{text}' or '_{text}'."
|
|
424
|
+
);
|
|
425
|
+
}
|
|
164
426
|
}
|
|
165
427
|
}
|
|
166
428
|
|
|
167
|
-
|
|
429
|
+
if (
|
|
430
|
+
!canBulkName
|
|
431
|
+
&& animationData is { Count: > 0 }
|
|
432
|
+
&& animationData.Any(data => data.frames?.Count > 0)
|
|
433
|
+
)
|
|
434
|
+
{
|
|
435
|
+
EditorGUILayout.HelpBox(
|
|
436
|
+
"Enter text in the 'Text' field above to enable bulk naming operations.",
|
|
437
|
+
MessageType.Info
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
EditorGUILayout.Space();
|
|
442
|
+
using (new EditorGUI.DisabledScope(animationData == null || animationData.Count == 0))
|
|
443
|
+
{
|
|
444
|
+
if (GUILayout.Button("Create Animations"))
|
|
445
|
+
{
|
|
446
|
+
CreateAnimations();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (animationData == null || animationData.Count == 0)
|
|
450
|
+
{
|
|
451
|
+
EditorGUILayout.HelpBox(
|
|
452
|
+
"Add Animation Data entries before creating.",
|
|
453
|
+
MessageType.Warning
|
|
454
|
+
);
|
|
455
|
+
}
|
|
168
456
|
}
|
|
169
457
|
|
|
170
|
-
private void
|
|
458
|
+
private void CreateAnimations()
|
|
171
459
|
{
|
|
172
|
-
if (animationData is not { Count:
|
|
460
|
+
if (animationData is not { Count: not 0 })
|
|
173
461
|
{
|
|
462
|
+
this.LogError($"No animation data to create.");
|
|
174
463
|
return;
|
|
175
464
|
}
|
|
176
465
|
|
|
177
|
-
|
|
466
|
+
string[] searchTerms = string.IsNullOrWhiteSpace(_searchString)
|
|
467
|
+
? Array.Empty<string>()
|
|
468
|
+
: _searchString
|
|
469
|
+
.ToLowerInvariant()
|
|
470
|
+
.Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
|
471
|
+
|
|
472
|
+
List<AnimationData> dataToCreate = new();
|
|
473
|
+
if (searchTerms.Length == 0)
|
|
178
474
|
{
|
|
179
|
-
|
|
180
|
-
|
|
475
|
+
dataToCreate.AddRange(animationData);
|
|
476
|
+
}
|
|
477
|
+
else
|
|
478
|
+
{
|
|
479
|
+
foreach (AnimationData data in animationData)
|
|
181
480
|
{
|
|
182
|
-
|
|
183
|
-
|
|
481
|
+
string lowerName = (data.animationName ?? string.Empty).ToLowerInvariant();
|
|
482
|
+
if (searchTerms.All(term => lowerName.Contains(term)))
|
|
483
|
+
{
|
|
484
|
+
dataToCreate.Add(data);
|
|
485
|
+
}
|
|
184
486
|
}
|
|
487
|
+
this.Log(
|
|
488
|
+
$"Creating animations based on current search filter '{_searchString}'. Only {dataToCreate.Count} out of {animationData.Count} items will be processed."
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (dataToCreate.Count == 0)
|
|
493
|
+
{
|
|
494
|
+
this.LogError(
|
|
495
|
+
$"No animation data matches the current search filter '{_searchString}'. Nothing to create."
|
|
496
|
+
);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
185
499
|
|
|
186
|
-
|
|
187
|
-
|
|
500
|
+
int totalAnimations = dataToCreate.Count;
|
|
501
|
+
int currentAnimationIndex = 0;
|
|
502
|
+
bool errorOccurred = false;
|
|
503
|
+
|
|
504
|
+
AssetDatabase.StartAssetEditing();
|
|
505
|
+
try
|
|
506
|
+
{
|
|
507
|
+
foreach (AnimationData data in dataToCreate)
|
|
188
508
|
{
|
|
189
|
-
|
|
190
|
-
|
|
509
|
+
currentAnimationIndex++;
|
|
510
|
+
string animationName = data.animationName;
|
|
511
|
+
if (string.IsNullOrWhiteSpace(animationName))
|
|
512
|
+
{
|
|
513
|
+
this.LogWarn(
|
|
514
|
+
$"Ignoring animation data entry (original index unknown due to filtering) without an animation name."
|
|
515
|
+
);
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
EditorUtility.DisplayProgressBar(
|
|
520
|
+
"Creating Animations",
|
|
521
|
+
$"Processing '{animationName}' ({currentAnimationIndex}/{totalAnimations})",
|
|
522
|
+
(float)currentAnimationIndex / totalAnimations
|
|
191
523
|
);
|
|
192
|
-
|
|
524
|
+
|
|
525
|
+
int framesPerSecond = data.framesPerSecond;
|
|
526
|
+
if (framesPerSecond <= 0)
|
|
527
|
+
{
|
|
528
|
+
this.LogWarn(
|
|
529
|
+
$"Ignoring animation '{animationName}' with invalid FPS ({framesPerSecond})."
|
|
530
|
+
);
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
List<Sprite> frames = data.frames;
|
|
535
|
+
if (frames is not { Count: not 0 })
|
|
536
|
+
{
|
|
537
|
+
this.LogWarn(
|
|
538
|
+
$"Ignoring animation '{animationName}' because it has no frames."
|
|
539
|
+
);
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
List<Sprite> validFrames = frames.Where(f => f != null).ToList();
|
|
544
|
+
if (validFrames.Count == 0)
|
|
545
|
+
{
|
|
546
|
+
this.LogWarn(
|
|
547
|
+
$"Ignoring animation '{animationName}' because it only contains null frames."
|
|
548
|
+
);
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
validFrames.Sort((s1, s2) => EditorUtility.NaturalCompare(s1.name, s2.name));
|
|
553
|
+
|
|
554
|
+
List<ObjectReferenceKeyframe> keyFrames = new(validFrames.Count);
|
|
555
|
+
float timeStep = 1f / framesPerSecond;
|
|
556
|
+
float currentTime = 0f;
|
|
557
|
+
|
|
558
|
+
foreach (
|
|
559
|
+
ObjectReferenceKeyframe keyFrame in validFrames.Select(
|
|
560
|
+
sprite => new ObjectReferenceKeyframe
|
|
561
|
+
{
|
|
562
|
+
time = currentTime,
|
|
563
|
+
value = sprite,
|
|
564
|
+
}
|
|
565
|
+
)
|
|
566
|
+
)
|
|
567
|
+
{
|
|
568
|
+
keyFrames.Add(keyFrame);
|
|
569
|
+
currentTime += timeStep;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (keyFrames.Count <= 0)
|
|
573
|
+
{
|
|
574
|
+
this.LogWarn(
|
|
575
|
+
$"No valid keyframes could be generated for animation '{animationName}'."
|
|
576
|
+
);
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
AnimationClip animationClip = new() { frameRate = framesPerSecond };
|
|
581
|
+
|
|
582
|
+
if (data.loop)
|
|
583
|
+
{
|
|
584
|
+
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(
|
|
585
|
+
animationClip
|
|
586
|
+
);
|
|
587
|
+
settings.loopTime = true;
|
|
588
|
+
AnimationUtility.SetAnimationClipSettings(animationClip, settings);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
AnimationUtility.SetObjectReferenceCurve(
|
|
592
|
+
animationClip,
|
|
593
|
+
EditorCurveBinding.PPtrCurve("", typeof(SpriteRenderer), "m_Sprite"),
|
|
594
|
+
keyFrames.ToArray()
|
|
595
|
+
);
|
|
596
|
+
|
|
597
|
+
string firstFramePath = AssetDatabase.GetAssetPath(validFrames[0]);
|
|
598
|
+
string assetPath =
|
|
599
|
+
Path.GetDirectoryName(firstFramePath)?.Replace("\\", "/") ?? "Assets";
|
|
600
|
+
if (!assetPath.EndsWith("/"))
|
|
601
|
+
{
|
|
602
|
+
assetPath += "/";
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
string finalPath = AssetDatabase.GenerateUniqueAssetPath(
|
|
606
|
+
$"{assetPath}{animationName}.anim"
|
|
607
|
+
);
|
|
608
|
+
AssetDatabase.CreateAsset(animationClip, finalPath);
|
|
609
|
+
this.Log($"Created animation at '{finalPath}'.");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
catch (Exception e)
|
|
613
|
+
{
|
|
614
|
+
errorOccurred = true;
|
|
615
|
+
this.LogError($"An error occurred during animation creation: {e}");
|
|
616
|
+
}
|
|
617
|
+
finally
|
|
618
|
+
{
|
|
619
|
+
EditorUtility.ClearProgressBar();
|
|
620
|
+
if (!errorOccurred)
|
|
621
|
+
{
|
|
622
|
+
this.Log($"Finished creating {totalAnimations} animations.");
|
|
193
623
|
}
|
|
624
|
+
else
|
|
625
|
+
{
|
|
626
|
+
this.LogError($"Animation creation finished with errors. Check console.");
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
AssetDatabase.StopAssetEditing();
|
|
630
|
+
AssetDatabase.SaveAssets();
|
|
631
|
+
AssetDatabase.Refresh();
|
|
632
|
+
}
|
|
633
|
+
}
|
|
194
634
|
|
|
195
|
-
|
|
196
|
-
|
|
635
|
+
private void UpdateRegex()
|
|
636
|
+
{
|
|
637
|
+
if (_compiledRegex == null || _lastUsedRegex != spriteNameRegex)
|
|
638
|
+
{
|
|
639
|
+
try
|
|
640
|
+
{
|
|
641
|
+
_compiledRegex = new Regex(spriteNameRegex, RegexOptions.Compiled);
|
|
642
|
+
_lastUsedRegex = spriteNameRegex;
|
|
643
|
+
_errorMessage = "";
|
|
644
|
+
this.Log($"Regex updated to: {spriteNameRegex}");
|
|
645
|
+
}
|
|
646
|
+
catch (ArgumentException ex)
|
|
647
|
+
{
|
|
648
|
+
_compiledRegex = null;
|
|
649
|
+
_lastUsedRegex = spriteNameRegex;
|
|
650
|
+
_errorMessage = $"Invalid Regex: {ex.Message}";
|
|
651
|
+
this.LogError($"Invalid Regex '{spriteNameRegex}': {ex.Message}");
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
private void FindAndFilterSprites()
|
|
657
|
+
{
|
|
658
|
+
_filteredSprites.Clear();
|
|
659
|
+
_matchedSpriteCount = 0;
|
|
660
|
+
_unmatchedSpriteCount = 0;
|
|
661
|
+
|
|
662
|
+
if (animationSources is not { Count: not 0 } || _compiledRegex == null)
|
|
663
|
+
{
|
|
664
|
+
if (_compiledRegex == null && !string.IsNullOrWhiteSpace(spriteNameRegex))
|
|
197
665
|
{
|
|
198
666
|
this.LogWarn(
|
|
199
|
-
$"
|
|
667
|
+
$"Cannot find sprites, regex pattern '{spriteNameRegex}' is invalid."
|
|
200
668
|
);
|
|
669
|
+
}
|
|
670
|
+
else if (animationSources is not { Count: not 0 })
|
|
671
|
+
{
|
|
672
|
+
this.LogWarn($"Cannot find sprites, no animation sources specified.");
|
|
673
|
+
}
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
List<string> searchPaths = new();
|
|
678
|
+
foreach (Object source in animationSources)
|
|
679
|
+
{
|
|
680
|
+
if (source == null)
|
|
681
|
+
{
|
|
201
682
|
continue;
|
|
202
683
|
}
|
|
203
684
|
|
|
204
|
-
|
|
685
|
+
string path = AssetDatabase.GetAssetPath(source);
|
|
686
|
+
if (!string.IsNullOrWhiteSpace(path) && AssetDatabase.IsValidFolder(path))
|
|
687
|
+
{
|
|
688
|
+
searchPaths.Add(path);
|
|
689
|
+
}
|
|
690
|
+
else if (source != null)
|
|
691
|
+
{
|
|
692
|
+
this.LogWarn($"Source '{source.name}' is not a valid folder. Skipping.");
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (searchPaths.Count == 0)
|
|
697
|
+
{
|
|
698
|
+
this.LogWarn($"No valid folders found in Animation Sources.");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
string[] assetGuids = AssetDatabase.FindAssets("t:sprite", searchPaths.ToArray());
|
|
703
|
+
float totalAssets = assetGuids.Length;
|
|
704
|
+
this.Log($"Found {totalAssets} total sprite assets in specified paths.");
|
|
705
|
+
|
|
706
|
+
try
|
|
707
|
+
{
|
|
708
|
+
EditorUtility.DisplayProgressBar(
|
|
709
|
+
"Finding and Filtering Sprites",
|
|
710
|
+
$"Scanning {assetGuids.Length} assets...",
|
|
711
|
+
0f
|
|
712
|
+
);
|
|
205
713
|
|
|
206
|
-
|
|
207
|
-
float timeStep = 1f / framesPerSecond;
|
|
208
|
-
foreach (Texture2D frame in frames)
|
|
714
|
+
for (int i = 0; i < totalAssets; i++)
|
|
209
715
|
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (
|
|
716
|
+
string guid = assetGuids[i];
|
|
717
|
+
string path = AssetDatabase.GUIDToAssetPath(guid);
|
|
718
|
+
|
|
719
|
+
if (i % 20 == 0 || Mathf.Approximately(i, totalAssets - 1))
|
|
214
720
|
{
|
|
215
|
-
|
|
721
|
+
float progress = (i + 1) / totalAssets;
|
|
722
|
+
EditorUtility.DisplayProgressBar(
|
|
723
|
+
"Finding and Filtering Sprites",
|
|
724
|
+
$"Checking: {Path.GetFileName(path)} ({i + 1}/{assetGuids.Length})",
|
|
725
|
+
progress
|
|
726
|
+
);
|
|
216
727
|
}
|
|
217
728
|
|
|
218
|
-
|
|
219
|
-
keyFrames.Add(keyFrame);
|
|
729
|
+
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(path);
|
|
220
730
|
|
|
221
|
-
|
|
731
|
+
if (sprite != null)
|
|
732
|
+
{
|
|
733
|
+
if (_compiledRegex.IsMatch(sprite.name))
|
|
734
|
+
{
|
|
735
|
+
_filteredSprites.Add(sprite);
|
|
736
|
+
_matchedSpriteCount++;
|
|
737
|
+
}
|
|
738
|
+
else
|
|
739
|
+
{
|
|
740
|
+
_unmatchedSpriteCount++;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
this.Log(
|
|
745
|
+
$"Sprite filtering complete. Matched: {_matchedSpriteCount}, Unmatched: {_unmatchedSpriteCount}."
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
finally
|
|
749
|
+
{
|
|
750
|
+
EditorUtility.ClearProgressBar();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private void AutoParseSprites()
|
|
755
|
+
{
|
|
756
|
+
if (_filteredSprites.Count == 0)
|
|
757
|
+
{
|
|
758
|
+
this.LogWarn($"Cannot Auto-Parse, no matched sprites available.");
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
Dictionary<string, Dictionary<string, List<Sprite>>> spritesByPrefixAndAssetPath = new(
|
|
763
|
+
StringComparer.Ordinal
|
|
764
|
+
);
|
|
765
|
+
int totalSprites = _filteredSprites.Count;
|
|
766
|
+
int processedCount = 0;
|
|
767
|
+
this.Log($"Starting auto-parse for {_filteredSprites.Count} matched sprites.");
|
|
768
|
+
|
|
769
|
+
try
|
|
770
|
+
{
|
|
771
|
+
foreach (Sprite sprite in _filteredSprites)
|
|
772
|
+
{
|
|
773
|
+
processedCount++;
|
|
774
|
+
if (processedCount % 10 == 0 || processedCount == totalSprites)
|
|
775
|
+
{
|
|
776
|
+
EditorUtility.DisplayProgressBar(
|
|
777
|
+
"Auto-Parsing Sprites",
|
|
778
|
+
$"Processing: {sprite.name} ({processedCount}/{totalSprites})",
|
|
779
|
+
(float)processedCount / totalSprites
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
string assetPath = AssetDatabase.GetAssetPath(sprite);
|
|
784
|
+
string directoryPath =
|
|
785
|
+
Path.GetDirectoryName(assetPath)?.Replace("\\", "/") ?? "";
|
|
786
|
+
string frameName = sprite.name;
|
|
787
|
+
|
|
788
|
+
int splitIndex = frameName.LastIndexOf('_');
|
|
789
|
+
string prefix = frameName;
|
|
790
|
+
|
|
791
|
+
if (splitIndex > 0 && splitIndex < frameName.Length - 1)
|
|
792
|
+
{
|
|
793
|
+
bool allDigitsAfter = true;
|
|
794
|
+
for (int j = splitIndex + 1; j < frameName.Length; j++)
|
|
795
|
+
{
|
|
796
|
+
if (!char.IsDigit(frameName[j]))
|
|
797
|
+
{
|
|
798
|
+
allDigitsAfter = false;
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (allDigitsAfter)
|
|
804
|
+
{
|
|
805
|
+
prefix = frameName.Substring(0, splitIndex);
|
|
806
|
+
}
|
|
807
|
+
else
|
|
808
|
+
{
|
|
809
|
+
prefix = frameName;
|
|
810
|
+
this.LogWarn(
|
|
811
|
+
$"Sprite name '{frameName}' has an underscore but not only digits after the last one. Treating as single frame or check naming."
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else if (splitIndex == -1)
|
|
816
|
+
{
|
|
817
|
+
prefix = frameName;
|
|
818
|
+
this.LogWarn(
|
|
819
|
+
$"Sprite name '{frameName}' has no underscore. Treating as single frame or check naming."
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (!string.IsNullOrWhiteSpace(prefix))
|
|
824
|
+
{
|
|
825
|
+
if (
|
|
826
|
+
!spritesByPrefixAndAssetPath.TryGetValue(
|
|
827
|
+
directoryPath,
|
|
828
|
+
out Dictionary<string, List<Sprite>> spritesByPrefix
|
|
829
|
+
)
|
|
830
|
+
)
|
|
831
|
+
{
|
|
832
|
+
spritesByPrefix = new Dictionary<string, List<Sprite>>(
|
|
833
|
+
StringComparer.Ordinal
|
|
834
|
+
);
|
|
835
|
+
spritesByPrefixAndAssetPath.Add(directoryPath, spritesByPrefix);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
spritesByPrefix.GetOrAdd(prefix).Add(sprite);
|
|
839
|
+
}
|
|
840
|
+
else
|
|
841
|
+
{
|
|
842
|
+
this.LogWarn(
|
|
843
|
+
$"Could not extract valid prefix for frame '{frameName}' at path '{assetPath}'. Skipping."
|
|
844
|
+
);
|
|
845
|
+
}
|
|
222
846
|
}
|
|
223
847
|
|
|
224
|
-
if (
|
|
848
|
+
if (spritesByPrefixAndAssetPath.Count > 0)
|
|
849
|
+
{
|
|
850
|
+
int removedCount = animationData.RemoveAll(data => data.isCreatedFromAutoParse);
|
|
851
|
+
this.Log($"Removed {removedCount} previously auto-parsed animation entries.");
|
|
852
|
+
|
|
853
|
+
int addedCount = 0;
|
|
854
|
+
foreach (
|
|
855
|
+
KeyValuePair<
|
|
856
|
+
string,
|
|
857
|
+
Dictionary<string, List<Sprite>>
|
|
858
|
+
> kvpAssetPath in spritesByPrefixAndAssetPath
|
|
859
|
+
)
|
|
860
|
+
{
|
|
861
|
+
string dirName = new DirectoryInfo(kvpAssetPath.Key).Name;
|
|
862
|
+
|
|
863
|
+
foreach ((string key, List<Sprite> spritesInGroup) in kvpAssetPath.Value)
|
|
864
|
+
{
|
|
865
|
+
if (spritesInGroup.Count == 0)
|
|
866
|
+
{
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
spritesInGroup.Sort(
|
|
871
|
+
(s1, s2) => EditorUtility.NaturalCompare(s1.name, s2.name)
|
|
872
|
+
);
|
|
873
|
+
|
|
874
|
+
string finalAnimName;
|
|
875
|
+
|
|
876
|
+
bool keyIsLikelyFullName =
|
|
877
|
+
spritesInGroup.Count > 0 && key == spritesInGroup[0].name;
|
|
878
|
+
|
|
879
|
+
if (keyIsLikelyFullName)
|
|
880
|
+
{
|
|
881
|
+
int lastUnderscore = key.LastIndexOf('_');
|
|
882
|
+
|
|
883
|
+
if (lastUnderscore > 0 && lastUnderscore < key.Length - 1)
|
|
884
|
+
{
|
|
885
|
+
string suffix = key.Substring(lastUnderscore + 1);
|
|
886
|
+
finalAnimName = SanitizeName($"Anim_{suffix}");
|
|
887
|
+
this.Log(
|
|
888
|
+
$"Naming non-standard sprite group '{key}' as '{finalAnimName}' using suffix '{suffix}'."
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
else
|
|
892
|
+
{
|
|
893
|
+
finalAnimName = SanitizeName($"Anim_{key}");
|
|
894
|
+
this.LogWarn(
|
|
895
|
+
$"Naming non-standard sprite group '{key}' as '{finalAnimName}'. Could not extract suffix."
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
else
|
|
900
|
+
{
|
|
901
|
+
finalAnimName = SanitizeName($"Anim_{key}");
|
|
902
|
+
this.Log(
|
|
903
|
+
$"Naming standard sprite group '{key}' as '{finalAnimName}'."
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
animationData.Add(
|
|
908
|
+
new AnimationData
|
|
909
|
+
{
|
|
910
|
+
frames = spritesInGroup,
|
|
911
|
+
framesPerSecond = AnimationData.DefaultFramesPerSecond,
|
|
912
|
+
animationName = finalAnimName,
|
|
913
|
+
isCreatedFromAutoParse = true,
|
|
914
|
+
loop = false,
|
|
915
|
+
}
|
|
916
|
+
);
|
|
917
|
+
addedCount++;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
this.Log($"Auto-parsed into {addedCount} new animation groups.");
|
|
922
|
+
}
|
|
923
|
+
else
|
|
225
924
|
{
|
|
226
925
|
this.LogWarn(
|
|
227
|
-
$"
|
|
926
|
+
$"Auto-parsing did not result in any animation groups. Check sprite naming conventions (e.g., 'Prefix_0', 'Prefix_1')."
|
|
228
927
|
);
|
|
229
|
-
continue;
|
|
230
928
|
}
|
|
929
|
+
}
|
|
930
|
+
finally
|
|
931
|
+
{
|
|
932
|
+
EditorUtility.ClearProgressBar();
|
|
933
|
+
_serializedObject.Update();
|
|
934
|
+
}
|
|
935
|
+
}
|
|
231
936
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
keyFrames.ToArray()
|
|
237
|
-
);
|
|
238
|
-
string assetPathWithFileNameAndExtension = AssetDatabase.GetAssetPath(frames[0]);
|
|
239
|
-
string assetPath = assetPathWithFileNameAndExtension.Substring(
|
|
240
|
-
0,
|
|
241
|
-
assetPathWithFileNameAndExtension.LastIndexOf("/", StringComparison.Ordinal) + 1
|
|
242
|
-
);
|
|
937
|
+
private static string SanitizeName(string inputName)
|
|
938
|
+
{
|
|
939
|
+
inputName = inputName.Replace(" ", "_");
|
|
940
|
+
inputName = Regex.Replace(inputName, @"[^a-zA-Z0-9_]", "");
|
|
243
941
|
|
|
244
|
-
|
|
942
|
+
if (string.IsNullOrWhiteSpace(inputName))
|
|
943
|
+
{
|
|
944
|
+
return "Default_Animation";
|
|
245
945
|
}
|
|
946
|
+
|
|
947
|
+
return inputName.Trim('_');
|
|
246
948
|
}
|
|
247
949
|
}
|
|
950
|
+
|
|
248
951
|
#endif
|
|
249
952
|
}
|