ISAP/Library/PackageCache/com.unity.2d.animation@9.0.2/Editor/UpgradeTools/SpriteLibUpgrader.cs

530 lines
22 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor.SceneManagement;
using UnityEditor.U2D.Sprites;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.U2D.Animation;
using Object = UnityEngine.Object;
namespace UnityEditor.U2D.Animation.Upgrading
{
internal class SpriteLibUpgrader : BaseUpgrader
{
static class Contents
{
public static readonly string ProgressBarTitle = L10n.Tr("Upgrading Sprite Library Assets");
public static readonly string VerifyingSelection = L10n.Tr("Verifying the selection");
public static readonly string CreatingNewLibraries = L10n.Tr("Creating new Sprite Library Assets");
public static readonly string ReassignAssetsInComponents = L10n.Tr("Re-assigning assets in components");
public static readonly string RemoveOldSpriteLibraries = L10n.Tr("Removing old Sprite Library Assets");
}
const string k_SpriteLibTypeId = "t:SpriteLibraryAsset";
const string k_PsbImporterCategoriesId = "m_SpriteCategoryList.m_Categories";
static readonly Dictionary<Type, List<FieldInfo>> k_SpriteLibraryReferenceLookup;
bool m_OnlyFindOldAssets;
bool m_OnlySearchInAssets;
HashSet<int> m_IndicesWithAssetBundleConnection = new HashSet<int>();
HashSet<string> m_AssetBundlesNeedingUpgrade = new HashSet<string>();
static SpriteLibUpgrader()
{
k_SpriteLibraryReferenceLookup = GetSpriteLibReferenceLookup();
}
/// <param name="onlyFindOldAssets">Set this to true if you only want to find .asset SpriteLibraries</param>
/// <param name="onlySearchInAssets">Set this to true if you only want to find SpriteLibraries in the Assets/ folder or its children</param>
public SpriteLibUpgrader(bool onlyFindOldAssets = true, bool onlySearchInAssets = true)
{
m_OnlyFindOldAssets = onlyFindOldAssets;
m_OnlySearchInAssets = onlySearchInAssets;
}
static Dictionary<Type, List<FieldInfo>> GetSpriteLibReferenceLookup()
{
var result = new Dictionary<Type, List<FieldInfo>>();
var allObjectsWithSpriteLibProperties = TypeCache
.GetTypesDerivedFrom<Component>()
.Where(type => type
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Any(HasSpriteLibField));
foreach (var property in allObjectsWithSpriteLibProperties)
{
if (!result.ContainsKey(property))
result.Add(property, new List<FieldInfo>());
var libraryFields = property
.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(HasSpriteLibField)
.ToList();
foreach (var field in libraryFields)
{
result[property].Add(field);
}
}
return result;
}
static bool HasSpriteLibField(FieldInfo field)
{
return (field.FieldType == typeof(SpriteLibraryAsset) ||
field.FieldType == typeof(SpriteLibraryAsset[]) ||
field.FieldType == typeof(List<SpriteLibraryAsset>)) &&
(field.IsPublic || field.IsDefined(typeof(SerializeField)));
}
internal override List<Object> GetUpgradableAssets()
{
var rootFolder = m_OnlySearchInAssets ? new[] { "Assets" } : null;
var assetPaths = AssetDatabase.FindAssets(k_SpriteLibTypeId, rootFolder)
.Select(AssetDatabase.GUIDToAssetPath).ToArray();
if (m_OnlyFindOldAssets)
{
assetPaths = assetPaths
.Where(x => x.EndsWith(".asset") || UpgradeUtilities.IsPsbImportedFile(x))
.ToArray();
}
var assets = assetPaths
.Select(AssetDatabase.LoadAssetAtPath<SpriteLibraryAsset>)
.Cast<Object>()
.ToList();
return assets;
}
internal override UpgradeReport UpgradeSelection(List<ObjectIndexPair> objects)
{
EditorUtility.DisplayProgressBar(Contents.ProgressBarTitle, Contents.VerifyingSelection, 0f);
var entries = new List<UpgradeEntry>();
var libraryIndexPairs = new Dictionary<int, SpriteLibraryAsset>();
string msg;
foreach (var obj in objects)
{
if (obj.Target == null)
{
msg = "The upgrade failed. Invalid selection.";
m_Logger.Add(msg);
m_Logger.AddLineBreak();
entries.Add(new UpgradeEntry()
{
Result = UpgradeResult.Error,
Target = obj.Target,
Index = obj.Index,
Message = msg
});
}
else if (obj.Target is SpriteLibraryAsset lib)
{
libraryIndexPairs.Add(obj.Index, lib);
}
else
{
msg = $"The upgrade failed. {obj.Target.name} is not a SpriteLibraryAsset.";
m_Logger.Add(msg);
m_Logger.AddLineBreak();
entries.Add(new UpgradeEntry()
{
Result = UpgradeResult.Error,
Target = obj.Target,
Index = obj.Index,
Message = msg
});
}
}
var oldLibraries = libraryIndexPairs.Values.ToList();
EditorUtility.DisplayProgressBar(Contents.ProgressBarTitle, Contents.CreatingNewLibraries, 0.2f);
m_Logger.AddLineBreak();
m_Logger.Add(Contents.CreatingNewLibraries);
var newSourceAssetPaths = CreateNewAssetLibraries(libraryIndexPairs);
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
var newLibraries = LoadNewLibraries(newSourceAssetPaths);
EditorUtility.DisplayProgressBar(Contents.ProgressBarTitle, Contents.ReassignAssetsInComponents, 0.4f);
m_Logger.AddLineBreak();
m_Logger.Add(Contents.ReassignAssetsInComponents);
var assetPaths = GetAllAssetPaths(libraryIndexPairs);
if (assetPaths.Length > 0)
ReassignAssets(assetPaths, oldLibraries, newLibraries);
EditorUtility.DisplayProgressBar(Contents.ProgressBarTitle, Contents.RemoveOldSpriteLibraries, 0.8f);
m_Logger.AddLineBreak();
m_Logger.Add(Contents.RemoveOldSpriteLibraries);
RemoveOldLibraries(oldLibraries);
AssetDatabase.Refresh();
for (var i = 0; i < newLibraries.Count; ++i)
{
UpgradeResult result;
if (m_IndicesWithAssetBundleConnection.Contains(newSourceAssetPaths[i].Item1))
{
msg = $"Successfully replaced {newLibraries[i].name}. Note that the asset is connected to an AssetBundle. Make sure to rebuild this AssetBundle to complete the upgrade. See more info in the upgrade log.";
result = UpgradeResult.Warning;
}
else
{
msg = $"Successfully replaced {newLibraries[i].name}";
result = UpgradeResult.Successful;
}
m_Logger.Add(msg);
entries.Add(new UpgradeEntry()
{
Result = result,
Target = newLibraries[i],
Index = newSourceAssetPaths[i].Item1,
Message = msg
});
}
EditorUtility.ClearProgressBar();
if (m_AssetBundlesNeedingUpgrade.Count > 0)
AddAssetBundlesToLog();
var report = new UpgradeReport()
{
UpgradeEntries = entries,
Log = m_Logger.GetLog()
};
m_Logger.Clear();
m_AssetBundlesNeedingUpgrade.Clear();
return report;
}
static string[] GetAllAssetPaths(Dictionary<int, SpriteLibraryAsset> spriteLibraries)
{
var ids = new List<string>();
foreach(var lib in spriteLibraries.Values)
ids.Add(GetObjectIDString(lib));
var spriteLibIds = ids.ToArray();
var assetPaths = new List<string>();
var allAssetPaths = AssetDatabase.GetAllAssetPaths();
foreach (var path in allAssetPaths)
{
if (!IsPrefabOrScenePath(path, spriteLibIds))
continue;
assetPaths.Add(path);
}
return assetPaths.ToArray();
}
static string GetObjectIDString(Object obj)
{
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj.GetInstanceID(), out string guid, out long localId))
return "fileID: " + localId + ", guid: " + guid;
return null;
}
static bool IsPrefabOrScenePath(string path, string[] ids)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException(nameof(path));
if (path.StartsWith("Packages"))
return false;
if (path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase))
return DoesFileContainString(path, ids);
return false;
}
static bool DoesFileContainString(string path, string[] strings)
{
if (strings != null && strings.Length > 0)
{
using (var file = File.OpenText(path))
{
string line;
while ((line = file.ReadLine()) != null)
{
for (var i = 0; i < strings.Length; i++)
{
if (line.Contains(strings[i]))
return true;
}
}
}
}
return false;
}
List<(int, string)> CreateNewAssetLibraries(Dictionary<int, SpriteLibraryAsset> spriteLibraries)
{
var newLibraryPaths = new List<(int, string)>();
foreach (var pair in spriteLibraries)
{
var sourceAsset = pair.Value;
var path = AssetDatabase.GetAssetPath(sourceAsset);
var currentAssetPath = Path.GetDirectoryName(path);
var fileName = Path.GetFileNameWithoutExtension(path);
var convertFileName = fileName + SpriteLibrarySourceAsset.extension;
convertFileName = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(currentAssetPath, convertFileName));
var destAsset = ScriptableObject.CreateInstance<SpriteLibrarySourceAsset>();
destAsset.SetLibrary(new List<SpriteLibCategoryOverride>(sourceAsset.categories.Count));
foreach (var sourceCat in sourceAsset.categories)
{
var destCat = new SpriteLibCategoryOverride()
{
overrideEntries = new List<SpriteCategoryEntryOverride>(sourceCat.categoryList.Count),
name = sourceCat.name,
entryOverrideCount = sourceCat.categoryList.Count,
fromMain = false
};
destAsset.AddCategory(destCat);
foreach (var entry in sourceCat.categoryList)
{
destCat.overrideEntries.Add(new SpriteCategoryEntryOverride()
{
name = entry.name,
sprite = entry.sprite,
fromMain = false,
spriteOverride = entry.sprite
});
}
}
var assetBundle = AssetDatabase.GetImplicitAssetBundleName(path);
if (!string.IsNullOrEmpty(assetBundle))
{
m_AssetBundlesNeedingUpgrade.Add(assetBundle);
m_IndicesWithAssetBundleConnection.Add(pair.Key);
m_Logger.Add($"{sourceAsset.name} is connected with the following AssetBundle: {assetBundle}.");
}
SpriteLibrarySourceAssetImporter.SaveSpriteLibrarySourceAsset(destAsset, convertFileName);
newLibraryPaths.Add(new ValueTuple<int, string>(pair.Key, convertFileName));
m_Logger.Add($"Created a new SpriteLibrary with the data of {sourceAsset.name}. The new SpriteLibrary is located at: {convertFileName}");
}
return newLibraryPaths;
}
static List<SpriteLibraryAsset> LoadNewLibraries(List<(int, string)> sourceAssetPaths)
{
var newLibraries = new List<SpriteLibraryAsset>();
foreach (var path in sourceAssetPaths)
{
var newLibraryAsset = AssetDatabase.LoadAssetAtPath<SpriteLibraryAsset>(path.Item2);
newLibraries.Add(newLibraryAsset);
}
return newLibraries;
}
void ReassignAssets(string[] assetPaths, List<SpriteLibraryAsset> oldLibraries, List<SpriteLibraryAsset> newLibraries)
{
var index = 0;
foreach (var assetPath in assetPaths)
{
m_Logger.Add($"Scanning {assetPath} for components with SpriteLibraryAsset references in need of reassignment.");
var ext = Path.GetExtension(assetPath);
if (ext == ".prefab")
UpgradePrefab(assetPath, oldLibraries, newLibraries, UpgradeGameObject);
else if (ext == ".unity")
UpgradeScene(assetPath, oldLibraries, newLibraries, UpgradeGameObject);
var assetBundle = AssetDatabase.GetImplicitAssetBundleName(assetPath);
if (!string.IsNullOrEmpty(assetBundle))
{
m_AssetBundlesNeedingUpgrade.Add(assetBundle);
m_IndicesWithAssetBundleConnection.Add(index);
}
index++;
}
}
static void UpgradePrefab(string path, List<SpriteLibraryAsset> oldLibraries, List<SpriteLibraryAsset> newLibraries,
Action<GameObject, List<SpriteLibraryAsset>, List<SpriteLibraryAsset>> objectUpgrader)
{
var objects = AssetDatabase.LoadAllAssetsAtPath(path);
var firstIndex = 0;
for (var i = 0; i < objects.Length; i++)
{
if (objects[i] as GameObject)
{
firstIndex = i;
break;
}
}
if (!PrefabUtility.IsPartOfImmutablePrefab(objects[firstIndex]))
{
foreach (var obj in objects)
{
var go = obj as GameObject;
if (go != null)
{
objectUpgrader(go, oldLibraries, newLibraries);
}
}
var asset = objects[firstIndex] as GameObject;
PrefabUtility.SavePrefabAsset(asset.transform.root.gameObject);
}
}
static void UpgradeScene(string path, List<SpriteLibraryAsset> oldLibraries, List<SpriteLibraryAsset> newLibraries,
Action<GameObject, List<SpriteLibraryAsset>, List<SpriteLibraryAsset>> objectUpgrader)
{
var scene = default(Scene);
var openedByUser = false;
for (var i = 0; i < SceneManager.sceneCount && !openedByUser; i++)
{
scene = SceneManager.GetSceneAt(i);
if (path == scene.path)
openedByUser = true;
}
if (!openedByUser)
scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive);
var gameObjects = scene.GetRootGameObjects();
foreach (var go in gameObjects)
objectUpgrader(go, oldLibraries, newLibraries);
EditorSceneManager.SaveScene(scene);
if (!openedByUser)
EditorSceneManager.CloseScene(scene, true);
}
void UpgradeGameObject(GameObject go, List<SpriteLibraryAsset> oldLibraries, List<SpriteLibraryAsset> newLibraries)
{
var types = k_SpriteLibraryReferenceLookup.Keys;
foreach (var referenceType in types)
{
var components = go.GetComponentsInChildren(referenceType);
foreach (var component in components)
{
if (PrefabUtility.IsPartOfPrefabInstance(component))
continue;
var fieldInfos = k_SpriteLibraryReferenceLookup[referenceType];
foreach (var field in fieldInfos)
{
var asset = field.GetValue(component);
if (asset is SpriteLibraryAsset spriteLibAsset)
{
var index = oldLibraries.FindIndex(x => x.GetHashCode() == spriteLibAsset.GetHashCode());
if (index == -1)
continue;
field.SetValue(component, newLibraries[index]);
m_Logger.Add($"Updated the SpriteLibraryAsset reference in {component.GetType()} on the GameObject {component.name}");
}
else if (asset is SpriteLibraryAsset[] spriteLibArray)
{
for (var i = 0; i < spriteLibArray.Length; ++i)
{
var index = oldLibraries.FindIndex(x => x.GetHashCode() == spriteLibArray[i].GetHashCode());
if (index == -1)
continue;
spriteLibArray[i] = newLibraries[index];
}
field.SetValue(component, spriteLibArray);
m_Logger.Add($"Updated the SpriteLibraryAsset[] reference in {component.GetType()} on the GameObject {component.name}");
}
else if (asset is List<SpriteLibraryAsset> spriteLibList)
{
for (var i = 0; i < spriteLibList.Count; ++i)
{
var index = oldLibraries.FindIndex(x => x.GetHashCode() == spriteLibList[i].GetHashCode());
if (index == -1)
continue;
spriteLibList[i] = newLibraries[index];
}
field.SetValue(component, spriteLibList);
m_Logger.Add($"Updated the List<SpriteLibraryAsset> reference in {component.GetType()} on the GameObject {component.name}");
}
}
}
}
}
void RemoveOldLibraries(List<SpriteLibraryAsset> oldLibraries)
{
foreach (var library in oldLibraries)
{
var path = AssetDatabase.GetAssetPath(library);
var isPsbFile = UpgradeUtilities.IsPsbImportedFile(path);
if (!string.IsNullOrEmpty(path) && !isPsbFile)
{
m_Logger.Add($"Deleting {path} from project");
AssetDatabase.DeleteAsset(path);
}
}
}
// Leaving this in if we want to cleanup .psbs in the future
void RemoveSpriteLibFromPsb(string path)
{
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
var factory = new SpriteDataProviderFactories();
factory.Init();
var dataProvider = factory.GetSpriteEditorDataProviderFromObject(texture);
dataProvider.InitSpriteEditorDataProvider();
if (dataProvider.targetObject == null)
{
m_Logger.Add($"Could not load the PSDImporter from the path: {path}. Aborting the Sprite Library cleanup inside the .psb.");
return;
}
var so = new SerializedObject(dataProvider.targetObject);
var property = so.FindProperty(k_PsbImporterCategoriesId);
if (property != null && property.isArray)
{
property.arraySize = 0;
so.ApplyModifiedPropertiesWithoutUndo();
dataProvider.Apply();
m_Logger.Add($"Removed the Sprite Library asset inside {path}");
var assetImporter = dataProvider.targetObject as AssetImporter;
assetImporter.SaveAndReimport();
m_Logger.Add($"Saved and re-imported the file.");
}
else
{
m_Logger.Add($"Could not find any Sprite Library asset inside the .psb to cleanup.");
}
}
void AddAssetBundlesToLog()
{
m_Logger.AddLineBreak();
m_Logger.Add("[NOTE] The following AssetBundles need to be rebuilt:");
foreach(var assetBundle in m_AssetBundlesNeedingUpgrade)
m_Logger.Add(assetBundle);
}
}
}