A game about forced loneliness, made by TACStudios
1using System.Collections.Generic;
2using System.Linq;
3using UnityEngine;
4using UnityEngine.U2D.Animation;
5using UnityEngine.U2D.Common;
6
7namespace UnityEditor.U2D.Animation.Upgrading
8{
9 internal class AnimClipUpgrader : BaseUpgrader
10 {
11 static class Contents
12 {
13 public static readonly string ProgressBarTitle = L10n.Tr("Upgrading Animation Clips");
14 public static readonly string VerifyingSelection = L10n.Tr("Verifying the selection");
15 public static readonly string UpgradingSpriteKeys = L10n.Tr("Upgrading Sprite Keys");
16 public static readonly string UpgradingCategoryLabelHash = L10n.Tr("Upgrading Category and Label hashes");
17 }
18
19 enum HashType
20 {
21 Label,
22 Category,
23 SpriteKey,
24 SpriteHash
25 }
26
27 class BindingData
28 {
29 public string BindingPath;
30 public System.Type BindingType;
31 public List<KeyData> RawKeys;
32 public List<ConvertedKeyData> ConvertedKeys;
33 }
34
35 class KeyData
36 {
37 public HashType HashType;
38 public float Time;
39 public float Value;
40 }
41
42 class ConvertedKeyData
43 {
44 public float Time;
45 public float Value;
46 public string Category;
47 public string Label;
48 }
49
50 const string k_LabelHashId = "m_labelHash";
51 const string k_CategoryHashId = "m_CategoryHash";
52 const string k_SpriteKeyId = "m_SpriteKey";
53 const string k_SpriteHashId = "m_SpriteHash";
54 const string k_AnimClipTypeId = "t:AnimationClip";
55
56 static bool IsSpriteHashBinding(EditorCurveBinding b) =>
57 b.type == typeof(SpriteResolver)
58 && !string.IsNullOrEmpty(b.propertyName)
59 && b.propertyName == k_SpriteHashId;
60
61 static bool IsSpriteKeyBinding(EditorCurveBinding b) =>
62 b.type == typeof(SpriteResolver)
63 && !string.IsNullOrEmpty(b.propertyName)
64 && b.propertyName == k_SpriteKeyId;
65
66 static bool IsSpriteCategoryBinding(EditorCurveBinding b) =>
67 b.type == typeof(SpriteResolver)
68 && !string.IsNullOrEmpty(b.propertyName)
69 && b.propertyName == k_CategoryHashId;
70
71 static bool IsSpriteLabelBinding(EditorCurveBinding b) =>
72 b.type == typeof(SpriteResolver)
73 && !string.IsNullOrEmpty(b.propertyName)
74 && b.propertyName == k_LabelHashId;
75
76 static SpriteLibUpgrader s_SpriteLibUpgrader = new SpriteLibUpgrader(false, false);
77
78 internal override List<Object> GetUpgradableAssets()
79 {
80 var assets = AssetDatabase.FindAssets(k_AnimClipTypeId, new[] { "Assets" })
81 .Select(AssetDatabase.GUIDToAssetPath)
82 .Select(AssetDatabase.LoadAssetAtPath<Object>)
83 .ToArray();
84
85 var clips = assets
86 .Select(x => x as AnimationClip)
87 .Where(clip => clip != null)
88 .Where(clip =>
89 {
90 var bindings = AnimationUtility.GetCurveBindings(clip)
91 .Where(m => IsSpriteKeyBinding(m) || IsSpriteCategoryBinding(m) || IsSpriteLabelBinding(m))
92 .ToArray();
93 return bindings.Length > 0;
94 }).ToArray();
95
96 var assetList = new List<Object>(clips);
97 return assetList;
98 }
99
100 internal override UpgradeReport UpgradeSelection(List<ObjectIndexPair> objects)
101 {
102 var entries = new List<UpgradeEntry>();
103
104 AssetDatabase.StartAssetEditing();
105
106 string msg;
107 foreach (var obj in objects)
108 {
109 m_Logger.Add($"Verifying if the asset {obj.Target} is an AnimationClip.");
110 EditorUtility.DisplayProgressBar(
111 Contents.ProgressBarTitle,
112 Contents.VerifyingSelection,
113 GetUpgradeProgress(entries, objects));
114
115 if (obj.Target == null)
116 {
117 msg = "The upgrade failed. Invalid selection.";
118 m_Logger.Add(msg);
119 m_Logger.AddLineBreak();
120 entries.Add(new UpgradeEntry()
121 {
122 Result = UpgradeResult.Error,
123 Target = obj.Target,
124 Index = obj.Index,
125 Message = msg
126 });
127 continue;
128 }
129
130 var clip = obj.Target as AnimationClip;
131 if (clip == null)
132 {
133 msg = $"The upgrade failed. The asset {obj.Target.name} is not an AnimationClip.";
134 m_Logger.Add(msg);
135 m_Logger.AddLineBreak();
136 entries.Add(new UpgradeEntry()
137 {
138 Result = UpgradeResult.Error,
139 Target = obj.Target,
140 Index = obj.Index,
141 Message = msg
142 });
143 continue;
144 }
145
146 var extractedData = ExtractDataFromClip(clip);
147 ConvertData(ref extractedData);
148
149 var wasCleanupSuccessful = CleanupData(ref extractedData);
150 if (!wasCleanupSuccessful)
151 {
152 msg = $"The upgrade of the clip {obj.Target.name} failed. Some keyframes could not be converted in the animation clip.";
153 m_Logger.Add(msg);
154 m_Logger.AddLineBreak();
155 entries.Add(new UpgradeEntry()
156 {
157 Result = UpgradeResult.Error,
158 Target = obj.Target,
159 Index = obj.Index,
160 Message = msg
161 });
162 continue;
163 }
164
165 var isDataValid = ValidateConvertedData(extractedData, obj, entries);
166 if (!isDataValid)
167 continue;
168
169 UpdateClipWithConvertedData(clip, extractedData);
170 RemoveOldData(clip, extractedData);
171
172 msg = $"Upgrade successful. The clip {obj.Target.name} now uses the latest SpriteResolver data format.";
173 m_Logger.Add(msg);
174 m_Logger.AddLineBreak();
175 entries.Add(new UpgradeEntry()
176 {
177 Result = UpgradeResult.Successful,
178 Target = obj.Target,
179 Index = obj.Index,
180 Message = msg
181 });
182 }
183
184 AssetDatabase.SaveAssets();
185 AssetDatabase.StopAssetEditing();
186
187 EditorUtility.ClearProgressBar();
188
189 var report = new UpgradeReport()
190 {
191 UpgradeEntries = entries,
192 Log = m_Logger.GetLog()
193 };
194
195 m_Logger.Clear();
196 return report;
197 }
198
199 bool ValidateConvertedData(List<BindingData> extractedData, ObjectIndexPair upgradingObject, List<UpgradeEntry> entries)
200 {
201 var isDataValid = extractedData.All(data => data.ConvertedKeys.Count != 0);
202 if (!isDataValid)
203 {
204 var msg = $"The upgrade of the clip {upgradingObject.Target.name} failed. One or more bindings could not convert its keyframes to the latest data format.";
205 m_Logger.Add(msg);
206 m_Logger.AddLineBreak();
207 entries.Add(new UpgradeEntry()
208 {
209 Result = UpgradeResult.Error,
210 Target = upgradingObject.Target,
211 Index = upgradingObject.Index,
212 Message = msg
213 });
214 }
215
216 return isDataValid;
217 }
218
219 List<BindingData> ExtractDataFromClip(AnimationClip clip)
220 {
221 var spriteHashBindings = ExtractBindingsFromClip(clip, HashType.SpriteHash, IsSpriteHashBinding);
222 var spriteKeyBindings = ExtractBindingsFromClip(clip, HashType.SpriteKey, IsSpriteKeyBinding);
223 var categoryBindings = ExtractBindingsFromClip(clip, HashType.Category, IsSpriteCategoryBinding);
224 var labelBindings = ExtractBindingsFromClip(clip, HashType.Label, IsSpriteLabelBinding);
225
226 var bindings = new List<BindingData>();
227 bindings.AddRange(spriteHashBindings);
228 bindings.AddRange(spriteKeyBindings);
229 bindings.AddRange(categoryBindings);
230 bindings.AddRange(labelBindings);
231
232 bindings = MergeBindingData(bindings);
233 for (var i = 0; i < bindings.Count; ++i)
234 SortKeyData(bindings[i]);
235 return bindings;
236 }
237
238 BindingData[] ExtractBindingsFromClip(AnimationClip clip, HashType hashType, System.Func<EditorCurveBinding, bool> isBindingFunc)
239 {
240 var spriteHashBindings = AnimationUtility.GetCurveBindings(clip)
241 .Where(isBindingFunc.Invoke)
242 .ToArray();
243
244 var bindingData = new BindingData[spriteHashBindings.Length];
245 for (var i = 0; i < spriteHashBindings.Length; ++i)
246 bindingData[i] = ExtractKeyframesFromClip(clip, spriteHashBindings[i], hashType);
247
248 m_Logger.Add($"Extracting {hashType} bindings from clip. Found {bindingData.Length} bindings.");
249
250 return bindingData;
251 }
252
253 BindingData ExtractKeyframesFromClip(AnimationClip clip, EditorCurveBinding curveBinding, HashType hashType)
254 {
255 var bindingPath = curveBinding.path;
256 var bindingType = curveBinding.type;
257
258 var curves = AnimationUtility.GetEditorCurve(clip, curveBinding);
259 var keys = curves.keys;
260 var keyData = new List<KeyData>(keys.Length);
261 keyData.AddRange(keys
262 .Select(t => new KeyData() { Time = t.time, Value = t.value, HashType = hashType }));
263
264 var data = new BindingData()
265 {
266 BindingPath = bindingPath,
267 BindingType = bindingType,
268 RawKeys = keyData
269 };
270
271 m_Logger.Add($"Extracting {hashType} keyframes from clip. Found {keyData.Count} keyframes.");
272
273 return data;
274 }
275
276 List<BindingData> MergeBindingData(List<BindingData> bindingData)
277 {
278 var mergedData = new List<BindingData>();
279 for (var i = 0; i < bindingData.Count; i++)
280 {
281 var index = mergedData.FindIndex(x =>
282 x.BindingPath == bindingData[i].BindingPath &&
283 x.BindingType == bindingData[i].BindingType);
284
285 if (index != -1)
286 mergedData[index].RawKeys.AddRange(bindingData[i].RawKeys);
287 else
288 mergedData.Add(bindingData[i]);
289 }
290
291 m_Logger.Add($"Merging different types keyframes from the same bindings, into the same binding list. We now have {mergedData.Count} binding lists.");
292
293 return mergedData;
294 }
295
296 void SortKeyData(BindingData bindingData)
297 {
298 bindingData.RawKeys.Sort((a, b) => a.Time.CompareTo(b.Time));
299 m_Logger.Add($"Order the keyframe data in binding={bindingData.BindingPath} by time.");
300 }
301
302 void ConvertData(ref List<BindingData> bindingData)
303 {
304 for (var i = 0; i < bindingData.Count; ++i)
305 {
306 bindingData[i].ConvertedKeys = ConvertKeyData(bindingData[i]);
307 MergeKeyData(bindingData[i]);
308 RepairMissingKeyData(bindingData[i]);
309 }
310 }
311
312 List<ConvertedKeyData> ConvertKeyData(BindingData bindingData)
313 {
314 var keyData = bindingData.RawKeys;
315 var convertedData = new List<ConvertedKeyData>();
316 for (var i = 0; i < keyData.Count; ++i)
317 {
318 switch (keyData[i].HashType)
319 {
320 case HashType.SpriteHash:
321 convertedData.Add(ConvertSpriteHash(keyData[i]));
322 break;
323 case HashType.SpriteKey:
324 convertedData.Add(ConvertSpriteKey(keyData[i]));
325 break;
326 case HashType.Category:
327 convertedData.Add(ConvertSpriteCategory(keyData[i]));
328 break;
329 case HashType.Label:
330 convertedData.Add(ConvertSpriteLabel(keyData[i]));
331 break;
332 }
333
334 if (convertedData[i].Category == string.Empty && convertedData[i].Label == string.Empty)
335 m_Logger.Add($"Conversion of key={i} of type={keyData[i].HashType} for binding={bindingData.BindingPath} failed to resolve Category and Label values from the Sprite Libraries in the project.");
336 }
337
338 m_Logger.Add($"Converting keyframes into uniformed format for binding={bindingData.BindingPath}");
339 return convertedData;
340 }
341
342 static ConvertedKeyData ConvertSpriteHash(KeyData keyData)
343 {
344 var spriteHash = InternalEngineBridge.ConvertFloatToInt(keyData.Value);
345 SpriteHashToCategoryAndLabelName(spriteHash, out var categoryName, out var labelName);
346 var convertedData = new ConvertedKeyData()
347 {
348 Time = keyData.Time,
349 Value = keyData.Value,
350 Category = categoryName,
351 Label = labelName
352 };
353 return convertedData;
354 }
355
356 static ConvertedKeyData ConvertSpriteKey(KeyData keyData)
357 {
358 var newHash = InternalEngineBridge.ConvertFloatToInt(keyData.Value);
359 var spriteHash = SpriteLibraryUtility.Convert32BitTo30BitHash(newHash);
360
361 SpriteHashToCategoryAndLabelName(spriteHash, out var categoryName, out var labelName);
362 var convertedData = new ConvertedKeyData()
363 {
364 Time = keyData.Time,
365 Value = InternalEngineBridge.ConvertIntToFloat(spriteHash),
366 Category = categoryName,
367 Label = labelName
368 };
369 return convertedData;
370 }
371
372 static ConvertedKeyData ConvertSpriteCategory(KeyData keyData)
373 {
374 var newHash = InternalEngineBridge.ConvertFloatToInt(keyData.Value);
375 var categoryHash = SpriteLibraryUtility.Convert32BitTo30BitHash(newHash);
376 CategoryHashToCategoryName(categoryHash, out var categoryName);
377
378 var convertedData = new ConvertedKeyData()
379 {
380 Time = keyData.Time,
381 Value = 0f,
382 Category = categoryName,
383 Label = string.Empty
384 };
385 return convertedData;
386 }
387
388 static ConvertedKeyData ConvertSpriteLabel(KeyData keyData)
389 {
390 var newHash = InternalEngineBridge.ConvertFloatToInt(keyData.Value);
391 var labelHash = SpriteLibraryUtility.Convert32BitTo30BitHash(newHash);
392 LabelHashToLabelName(labelHash, out var labelName);
393
394 var convertedData = new ConvertedKeyData()
395 {
396 Time = keyData.Time,
397 Value = 0f,
398 Category = string.Empty,
399 Label = labelName
400 };
401 return convertedData;
402 }
403
404 static void SpriteHashToCategoryAndLabelName(int spriteHash, out string categoryName, out string labelName)
405 {
406 var spriteLibraryAssets = s_SpriteLibUpgrader.GetUpgradableAssets()
407 .Cast<SpriteLibraryAsset>().ToArray();
408
409 categoryName = string.Empty;
410 labelName = string.Empty;
411
412 foreach (var spriteLib in spriteLibraryAssets)
413 {
414 foreach (var category in spriteLib.categories)
415 {
416 foreach (var label in category.categoryList)
417 {
418 var combinedHash = SpriteLibrary.GetHashForCategoryAndEntry(category.name, label.name);
419 if (combinedHash == spriteHash)
420 {
421 categoryName = category.name;
422 labelName = label.name;
423 return;
424 }
425 }
426 }
427 }
428 }
429
430 static void CategoryHashToCategoryName(int categoryHash, out string categoryName)
431 {
432 var spriteLibraryAssets = s_SpriteLibUpgrader.GetUpgradableAssets()
433 .Cast<SpriteLibraryAsset>().ToArray();
434
435 categoryName = string.Empty;
436
437 foreach (var spriteLib in spriteLibraryAssets)
438 {
439 foreach (var category in spriteLib.categories)
440 {
441 if (category.hash == categoryHash)
442 {
443 categoryName = category.name;
444 return;
445 }
446 }
447 }
448 }
449
450 static void LabelHashToLabelName(int labelHash, out string labelName)
451 {
452 var spriteLibraryAssets = s_SpriteLibUpgrader.GetUpgradableAssets()
453 .Cast<SpriteLibraryAsset>().ToArray();
454
455 labelName = string.Empty;
456
457 foreach (var spriteLib in spriteLibraryAssets)
458 {
459 foreach (var category in spriteLib.categories)
460 {
461 foreach (var label in category.categoryList)
462 {
463 if (label.hash == labelHash)
464 {
465 labelName = label.name;
466 return;
467 }
468 }
469 }
470 }
471 }
472
473 void MergeKeyData(BindingData bindingData)
474 {
475 var keys = bindingData.ConvertedKeys;
476 for (var i = 0; i < keys.Count; ++i)
477 {
478 var categoryName = keys[i].Category;
479
480 if (categoryName == string.Empty)
481 continue;
482
483 var labelName = keys[i].Label;
484 if (labelName != string.Empty)
485 continue;
486
487 for (var m = 0; m < keys.Count; ++m)
488 {
489 labelName = keys[m].Label;
490 if (labelName == string.Empty)
491 continue;
492 if (Mathf.Abs(keys[i].Time - keys[m].Time) > Mathf.Epsilon)
493 continue;
494
495 keys[m].Category = categoryName;
496 keys[i].Label = labelName;
497
498 m_Logger.Add($"Merged Category={categoryName} and Label={labelName} at time={keys[i].Time} in binding={bindingData.BindingPath}.");
499 }
500 }
501 }
502
503 void RepairMissingKeyData(BindingData bindingData)
504 {
505 var keys = bindingData.ConvertedKeys;
506 var categoryName = string.Empty;
507 var labelName = string.Empty;
508 for (var i = 0; i < keys.Count; ++i)
509 {
510 if (keys[i].Category != string.Empty)
511 categoryName = keys[i].Category;
512 if (keys[i].Label != string.Empty)
513 labelName = keys[i].Label;
514
515 if (keys[i].Value == 0f && categoryName == string.Empty)
516 {
517 m_Logger.Add($"Cannot find a category for keyframe at time={keys[i].Time} in binding={bindingData.BindingPath}.");
518 continue;
519 }
520 if (keys[i].Value == 0f && labelName == string.Empty)
521 {
522 m_Logger.Add($"Cannot find a label for keyframe at time={keys[i].Time} in binding={bindingData.BindingPath}.");
523 continue;
524 }
525
526 if (keys[i].Value == 0f)
527 {
528 var spriteHash = SpriteLibrary.GetHashForCategoryAndEntry(categoryName, labelName);
529 keys[i].Value = InternalEngineBridge.ConvertIntToFloat(spriteHash);
530
531 if (keys[i].Value != 0f)
532 m_Logger.Add($"Combining categoryName={categoryName} labelName={labelName} into spriteHash={spriteHash}.");
533 else
534 m_Logger.Add($"Could not repair keyframe at time={keys[i].Time} for binding={bindingData.BindingPath}. The Sprite Library Asset might be missing.");
535 }
536 }
537 }
538
539 bool CleanupData(ref List<BindingData> bindingData)
540 {
541 foreach (var data in bindingData)
542 {
543 var keys = data.ConvertedKeys;
544
545 var keyTimes = new List<float>();
546 for (var i = 0; i < keys.Count; ++i)
547 {
548 var time = keys[i].Time;
549 if (i == 0 || (time - keyTimes[keyTimes.Count - 1]) > Mathf.Epsilon)
550 keyTimes.Add(time);
551 }
552
553 for (var m = keys.Count - 1; m >= 0; --m)
554 {
555 if (keys[m].Value == 0f)
556 keys.RemoveAt(m);
557 }
558
559 for (var m = keys.Count - 1; m > 0; --m)
560 {
561 if (Mathf.Abs(keys[m].Time - keys[m - 1].Time) < Mathf.Epsilon)
562 keys.RemoveAt(m);
563 }
564
565 if (keyTimes.Count == keys.Count)
566 m_Logger.Add($"Cleaned up keyframes for binding={data.BindingPath}. It now has {keys.Count} keyframes.");
567 else
568 {
569 m_Logger.Add($"Expected {keyTimes.Count} keyframes after cleanup for binding={data.BindingPath}, but ended up with {keys.Count}.");
570 return false;
571 }
572 }
573
574 return true;
575 }
576
577 void UpdateClipWithConvertedData(AnimationClip clip, List<BindingData> convertedBindings)
578 {
579 var destData = new EditorCurveBinding[convertedBindings.Count];
580 for (var i = 0; i < convertedBindings.Count; ++i)
581 destData[i] = EditorCurveBinding.DiscreteCurve(convertedBindings[i].BindingPath, convertedBindings[i].BindingType, k_SpriteHashId);
582
583 var curves = new AnimationCurve[destData.Length];
584 for (var i = 0; i < curves.Length; ++i)
585 {
586 var convertedKeys = convertedBindings[i].ConvertedKeys;
587 var keyFrames = new Keyframe[convertedKeys.Count];
588 for (var m = 0; m < keyFrames.Length; ++m)
589 {
590 keyFrames[m].inTangent = float.PositiveInfinity;
591 keyFrames[m].outTangent = float.PositiveInfinity;
592 keyFrames[m].time = convertedKeys[m].Time;
593 keyFrames[m].value = convertedKeys[m].Value;
594 }
595
596 curves[i] = new AnimationCurve(keyFrames);
597 }
598
599 AnimationUtility.SetEditorCurves(clip, destData, curves);
600 m_Logger.Add($"Injected updated bindings into AnimationClip={clip.name}.");
601 }
602
603 void RemoveOldData(AnimationClip clip, List<BindingData> bindingData)
604 {
605 var spriteKeyCurves = new EditorCurveBinding[bindingData.Count];
606 var spriteCategoryCurves = new EditorCurveBinding[bindingData.Count];
607 var spriteLabelCurves = new EditorCurveBinding[bindingData.Count];
608
609 for (var i = 0; i < bindingData.Count; ++i)
610 {
611 spriteKeyCurves[i] = EditorCurveBinding.DiscreteCurve(bindingData[i].BindingPath, bindingData[i].BindingType, k_SpriteKeyId);
612 spriteCategoryCurves[i] = EditorCurveBinding.DiscreteCurve(bindingData[i].BindingPath, bindingData[i].BindingType, k_CategoryHashId);
613 spriteLabelCurves[i] = EditorCurveBinding.DiscreteCurve(bindingData[i].BindingPath, bindingData[i].BindingType, k_LabelHashId);
614 }
615
616 AnimationUtility.SetEditorCurves(clip, spriteKeyCurves, new AnimationCurve[spriteKeyCurves.Length]);
617 AnimationUtility.SetEditorCurves(clip, spriteCategoryCurves, new AnimationCurve[spriteCategoryCurves.Length]);
618 AnimationUtility.SetEditorCurves(clip, spriteLabelCurves, new AnimationCurve[spriteLabelCurves.Length]);
619
620 m_Logger.Add($"Removed old bindings in AnimationClip={clip.name}.");
621 }
622
623 static float GetUpgradeProgress(List<UpgradeEntry> reports, List<ObjectIndexPair> totalNoOfObjects)
624 {
625 return reports.Count / (float)totalNoOfObjects.Count;
626 }
627 }
628}