2026-03-24 11:39:01 +08:00

292 lines
12 KiB
C#

#if UNITY_EDITOR
using DTT.Utils.EditorUtilities.Exceptions;
using System;
using System.IO;
using DTT.Utils.Exceptions;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
namespace DTT.Utils.EditorUtilities
{
/// <summary>
/// A static utility class for retrieving data from the Unity Asset Database.
/// </summary>
public class AssetDatabaseUtility
{
/// <summary>
/// Returns an array of assets loaded of <typeparamref name="T"/> from the
/// asset database using given filter argument.
/// </summary>
/// <param name="filter">The filter to use for finding the assets.</param>
/// <param name="searchFolders">The folders to look in when searching.</param>
/// <returns>The array of assets retrieved from the asset database.</returns>
public static T[] LoadAssets<T>(string filter, string[] searchFolders = null) where T : UnityEngine.Object
{
// Guids are found in search folders if there are any given.
string[] guids = searchFolders == null
? AssetDatabase.FindAssets(filter)
: AssetDatabase.FindAssets(filter, searchFolders);
T[] array = new T[guids.Length];
for (int i = 0; i < array.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
array[i] = AssetDatabase.LoadAssetAtPath<T>(path);
}
return array;
}
/// <summary>
/// Returns an array of assets loaded of <typeparamref name="T"/> from the
/// asset database.
/// </summary>
/// <param name="searchFolders">The folders to look in when searching.</param>
/// <returns>The array of assets retrieved from the asset database.</returns>
public static T[] LoadAssets<T>(string[] searchFolders = null) where T : UnityEngine.Object
{
string filter = string.Format("t:{0}", typeof(T).Name);
// Guids are found in search folders if there are any given.
string[] guids = searchFolders != null
? AssetDatabase.FindAssets(filter, searchFolders)
: AssetDatabase.FindAssets(filter);
T[] array = new T[guids.Length];
for (int i = 0; i < array.Length; i++)
{
string path = AssetDatabase.GUIDToAssetPath(guids[i]);
array[i] = AssetDatabase.LoadAssetAtPath<T>(path);
}
return array;
}
/// <summary>
/// Returns an asset of <typeparamref name="T"/> from the
/// asset database.
/// <para>If there are multiple it will return the first one found.</para>
/// </summary>
/// <param name="filter">The filter to use for finding the asset</param>
/// <param name="searchFolders">The folder to look in when searching.</param>
/// <returns>The asset retrieved from the asset database.</returns>
public static T LoadAsset<T>() where T : UnityEngine.Object
{
T[] assets = LoadAssets<T>();
return assets.Length != 0 ? assets[0] : null;
}
/// <summary>
/// Returns a loaded asset of <typeparamref name="T"/> from the
/// asset database using given filter argument.
/// <para>If there are multiple it will return the first one found.</para>
/// </summary>
/// <param name="filter">The filter to use for finding the asset</param>
/// <param name="searchFolders">The folder to look in when searching.</param>
/// <returns>The asset retrieved from the asset database.</returns>
public static T LoadAsset<T>(string filter) where T : UnityEngine.Object
{
if (filter == null)
throw new AssetDatabaseException("Filter is null.");
T[] assets = LoadAssets<T>(filter);
return assets.Length != 0 ? assets[0] : null;
}
/// <summary>
/// Returns a loaded asset of <typeparamref name="T"/> from the
/// asset database using given filter argument.
/// <para>If there are multiple it will return the first one found.</para>
/// </summary>
/// <param name="filter">The filter to use for finding the asset</param>
/// <param name="searchFolders">The folder to look in when searching.</param>
/// <returns>The asset retrieved from the asset database.</returns>
public static T LoadAsset<T>(string filter, string searchFolder) where T : UnityEngine.Object
{
T[] assets = LoadAssets<T>(filter, new string[] { searchFolder });
return assets.Length != 0 ? assets[0] : null;
}
/// <summary>
/// Saves and refreshes assets after the inspectors have been updated.
/// Use a delayed call for saving assets when saving instantly causes a Unity-problem that
/// will occur whenever an inspector previewing an asset is open while starting playmode.
/// </summary>
public static void SaveAndRefreshAssetsDelayed()
{
EditorApplication.delayCall += SaveAndRefresh;
void SaveAndRefresh()
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorApplication.delayCall -= SaveAndRefresh;
}
}
/// <summary>
/// Returns a component attached to a prefab at given path. Will return null
/// if the component was not attached.
/// </summary>
/// <typeparam name="T">The type of component.</typeparam>
/// <param name="prefabPath">The path the prefab can be found.</param>
/// <returns>The component attached to the prefab.</returns>
public static T GetComponentInPrefab<T>(string prefabPath) where T : Component
{
if (string.IsNullOrEmpty(prefabPath))
throw new NullOrEmptyException(nameof(prefabPath));
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
if (prefab == null)
{
if(File.Exists(prefabPath))
throw new AssetDatabaseException($"Failed to load asset even though it exists at path {prefabPath}. " +
"This can happen when changes outside of Unity have been made to your asset and you load it " +
"during project startup with attributes like [DidReloadScripts] and [InitializeOnLoad].");
throw new AssetDatabaseException($"Prefab can't be loaded at path {prefabPath}");
}
return prefab.GetComponent<T>();
}
/// <summary>
/// Tries returning a loaded scriptable object asset at given path but creates it if
/// it didn't exist.
/// <para>This operation will not save the asset if it has been created.</para>
/// </summary>
/// <typeparam name="T">The type of scriptable object asset.</typeparam>
/// <param name="path">The path at which to load or create.</param>
/// <returns>The loaded or created scriptable object asset reference.</returns>
public static T GetOrCreateScriptableObjectAsset<T>(string path) where T : ScriptableObject
{
if (File.Exists(path))
{
T asset = AssetDatabase.LoadAssetAtPath<T>(path);
if (asset == null)
{
throw new AssetDatabaseException($"Failed to load asset even though it exists at path {path}. " +
"This can happen when changes outside of Unity have been made to your asset and you load it " +
"during project startup with attributes like [DidReloadScripts] and [InitializeOnLoad].");
}
return asset;
}
T instance = ScriptableObject.CreateInstance<T>();
AssetDatabase.CreateAsset(instance, path);
return instance;
}
/// <summary>
/// Tries returning a loaded scriptable object asset at given path but creates it if
/// it didn't exist.
/// <para>This operation will not save the asset if it has been created.</para>
/// </summary>
/// <typeparam name="T">The type of scriptable object asset.</typeparam>
/// <param name="path">The path at which to load or create.</param>
/// <param name="wasCreated">Whether the asset could not be found and was created.</param>
/// <returns>The loaded or created scriptable object asset reference.</returns>
public static T GetOrCreateScriptableObjectAsset<T>(string path, out bool wasCreated) where T : ScriptableObject
{
wasCreated = false;
if (File.Exists(path))
{
T asset = AssetDatabase.LoadAssetAtPath<T>(path);
if (asset == null)
{
throw new AssetDatabaseException($"Failed to load asset even though it exists at path {path}. " +
"This can happen when changes outside of Unity have been made to your asset and you load it " +
"during project startup with attributes like [DidReloadScripts] and [InitializeOnLoad].");
}
return asset;
}
wasCreated = true;
T instance = ScriptableObject.CreateInstance<T>();
AssetDatabase.CreateAsset(instance, path);
return instance;
}
/// <summary>
/// Creates a prefab asset at given path using given GameObject instance.
/// Will destroy the instance if the creation was succesful.
/// <para>
/// This function doesn't return a prefab reference since there
/// is no certainty it can be created instantly.
/// </para>
/// </summary>
/// <param name="path">The path at which to create the prefab.</param>
/// <param name="instanceRoot">
/// The GameObject instance to use for creating the prefab.
/// </param>
public static void CreatePrefabAtPath(string path, GameObject instanceRoot)
{
try
{
var prefab = PrefabUtility.SaveAsPrefabAsset(instanceRoot, path, out bool succes);
if (succes)
{
Debug.Log($"Creating prefab at path: {path}");
if (prefab != null)
Debug.Log($"{prefab} has been instantly created.");
else
Debug.Log("The prefab will be created after imports have finished.");
}
else
{
Debug.LogWarning("Failed creating prefab asset because saving failed.");
}
}
catch (Exception e)
{
throw new AssetDatabaseException("Failed creating prefab.", e);
}
finally
{
GameObject.DestroyImmediate(instanceRoot);
}
}
/// <summary>
/// Opens a script asset.
/// </summary>
/// <param name="scriptAsset">The script asset to open.</param>
public static void OpenScript(TextAsset scriptAsset) => OpenScript(AssetDatabase.GetAssetPath(scriptAsset), 0, 0);
/// <summary>
/// Opens a script at given path.
/// </summary>
/// <param name="scriptAssetPath">The script asset path.</param>
public static bool OpenScript(string scriptAssetPath, int line = 0, int column = 0)
{
if (scriptAssetPath == null)
throw new ArgumentNullException(nameof(scriptAssetPath));
return InternalEditorUtility.OpenFileAtLineExternal(scriptAssetPath, line, column);
}
/// <summary>
/// Opens a prefab asset in prefab mode.
/// </summary>
/// <param name="prefabAsset">The prefab to open in prefab mode.</param>
public static bool OpenPrefab(GameObject prefabAsset)
{
if (prefabAsset == null)
throw new ArgumentNullException(nameof(prefabAsset));
return AssetDatabase.OpenAsset(prefabAsset);
}
}
}
#endif