using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityObject = UnityEngine.Object;
namespace Unity.VisualScripting
{
public static class EditorUnityObjectUtility
{
static EditorUnityObjectUtility()
{
try
{
UnityTypeType = Assembly.GetAssembly(typeof(UnityEditor.Editor)).GetType("UnityEditor.UnityType", true);
UnityTypeType_FindTypeByNameCaseInsensitive = UnityTypeType.GetMethod("FindTypeByNameCaseInsensitive", BindingFlags.Static | BindingFlags.Public);
UnityTypeType_persistentTypeID = UnityTypeType.GetProperty("persistentTypeID", BindingFlags.Instance | BindingFlags.Public);
if (UnityTypeType_FindTypeByNameCaseInsensitive == null)
{
throw new MissingMemberException(UnityTypeType.ToString(), "FindTypeByNameCaseInsensitive");
}
if (UnityTypeType_persistentTypeID == null)
{
throw new MissingMemberException(UnityTypeType.ToString(), "persistentTypeID");
}
#if UNITY_2018_3_OR_NEWER
{
// GetMethod doesn't work with generic methods...
var PrefabUtility_GetCorrespondingObjectFromSource_Candidates = typeof(PrefabUtility).GetMember("GetCorrespondingObjectFromSource", BindingFlags.Public | BindingFlags.Static);
if (PrefabUtility_GetCorrespondingObjectFromSource_Candidates.Length > 0)
{
PrefabUtility_GetCorrespondingObjectFromSource = ((MethodInfo)PrefabUtility_GetCorrespondingObjectFromSource_Candidates[0]).MakeGenericMethod(typeof(UnityObject));
}
PrefabUtility_GetPrefabInstanceHandle = typeof(PrefabUtility).GetMethod("GetPrefabInstanceHandle", BindingFlags.Public | BindingFlags.Static);
PrefabUtility_IsPartOfPrefabAsset = typeof(PrefabUtility).GetMethod("IsPartOfPrefabAsset", BindingFlags.Public | BindingFlags.Static);
PrefabUtility_IsPartOfPrefabInstance = typeof(PrefabUtility).GetMethod("IsPartOfPrefabInstance", BindingFlags.Public | BindingFlags.Static);
PrefabUtility_IsDisconnectedFromPrefabAsset = typeof(PrefabUtility).GetMethod("IsDisconnectedFromPrefabAsset", BindingFlags.Public | BindingFlags.Static);
if (PrefabUtility_GetCorrespondingObjectFromSource == null)
{
throw new MissingMemberException(typeof(PrefabUtility).ToString(), "GetCorrespondingObjectFromSource");
}
if (PrefabUtility_GetPrefabInstanceHandle == null)
{
throw new MissingMemberException(typeof(PrefabUtility).ToString(), "GetPrefabInstanceHandle");
}
if (PrefabUtility_IsPartOfPrefabAsset == null)
{
throw new MissingMemberException(typeof(PrefabUtility).ToString(), "IsPartOfPrefabAsset");
}
if (PrefabUtility_IsPartOfPrefabInstance == null)
{
throw new MissingMemberException(typeof(PrefabUtility).ToString(), "IsPartOfPrefabInstance");
}
if (PrefabUtility_IsDisconnectedFromPrefabAsset == null)
{
throw new MissingMemberException(typeof(PrefabUtility).ToString(), "IsDisconnectedFromPrefabAsset");
}
}
#endif
}
catch (Exception ex)
{
throw new UnityEditorInternalException(ex);
}
}
public static IEnumerable<Type> GetUnityTypes(UnityObject target)
{
Ensure.That(nameof(target)).IsNotNull(target);
if (target.IsComponentHolder())
{
yield return typeof(GameObject);
foreach (var componentType in target.GetComponents<Component>().NotNull().Select(c => c.GetType()).Distinct())
{
yield return componentType;
}
}
else
{
yield return target.GetType();
}
}
#region Prefabs
#if UNITY_2018_3_OR_NEWER
// public class UnityEditor.SceneManagement.PrefabStageUtility (or UnityEditor.Experimental.SceneManagement.PrefabStageUtility if < 2021.2)
private static readonly Type PrefabStageUtilityType; // public class UnityEditor.Experimental.SceneManagement.PrefabStageUtility
private static readonly MethodInfo PrefabStageUtility_GetPrefabStage;
private static readonly MethodInfo PrefabUtility_GetCorrespondingObjectFromSource;
private static readonly MethodInfo PrefabUtility_GetPrefabInstanceHandle;
private static readonly MethodInfo PrefabUtility_IsPartOfPrefabAsset;
private static readonly MethodInfo PrefabUtility_IsPartOfPrefabInstance;
private static readonly MethodInfo PrefabUtility_IsDisconnectedFromPrefabAsset;
#endif
public static UnityObject GetPrefabDefinition(this UnityObject uo)
{
Ensure.That(nameof(uo)).IsNotNull(uo);
#if UNITY_2018_3_OR_NEWER
try
{
return (UnityObject)PrefabUtility_GetCorrespondingObjectFromSource.InvokeOptimized(null, uo);
}
catch (Exception ex)
{
throw new UnityEditorInternalException(ex);
}
#else
return PrefabUtility.GetPrefabParent(uo);
#endif
}
private static UnityObject GetPrefabInstance(this UnityObject uo)
{
#if UNITY_2018_3_OR_NEWER
try
{
return (UnityObject)PrefabUtility_GetPrefabInstanceHandle.InvokeOptimized(null, uo);
}
catch (Exception ex)
{
throw new UnityEditorInternalException(ex);
}
#else
return PrefabUtility.GetPrefabObject(uo);
#endif
}
public static bool IsPrefabInstance(this UnityObject uo)
{
Ensure.That(nameof(uo)).IsNotNull(uo);
#if UNITY_2018_3_OR_NEWER
try
{
return (bool)PrefabUtility_IsPartOfPrefabInstance.InvokeOptimized(null, uo);
}
catch (Exception ex)
{
throw new UnityEditorInternalException(ex);
}
#else
return GetPrefabDefinition(uo) != null;
#endif
}
public static bool IsPrefabDefinition(this UnityObject uo)
{
Ensure.That(nameof(uo)).IsNotNull(uo);
#if UNITY_2018_3_OR_NEWER
try
{
// https://forum.unity.com/threads/editorgui-objectfield-allowsceneobjects-in-isolation-mode.610564/
return (bool)PrefabUtility_IsPartOfPrefabAsset.InvokeOptimized(null, uo);
}
catch (Exception ex)
{
throw new UnityEditorInternalException(ex);
}
#else
return GetPrefabDefinition(uo) == null && GetPrefabInstance(uo) != null;
#endif
}
public static bool IsConnectedPrefabInstance(this UnityObject uo)
{
Ensure.That(nameof(uo)).IsNotNull(uo);
#if UNITY_2018_3_OR_NEWER
try
{
return
(bool)PrefabUtility_IsPartOfPrefabInstance.InvokeOptimized(null, uo) &&
!(bool)PrefabUtility_IsDisconnectedFromPrefabAsset.InvokeOptimized(null, uo);
}
catch (Exception ex)
{
throw new UnityEditorInternalException(ex);
}
#else
return IsPrefabInstance(uo) && GetPrefabInstance(uo) != null;
#endif
}
public static bool IsDisconnectedPrefabInstance(this UnityObject uo)
{
Ensure.That(nameof(uo)).IsNotNull(uo);
#if UNITY_2018_3_OR_NEWER
try
{
return
(bool)PrefabUtility_IsPartOfPrefabInstance.InvokeOptimized(null, uo) &&
(bool)PrefabUtility_IsDisconnectedFromPrefabAsset.InvokeOptimized(null, uo);
}
catch (Exception ex)
{
throw new UnityEditorInternalException(ex);
}
#else
return IsPrefabInstance(uo) && GetPrefabInstance(uo) == null;
#endif
}
public static bool IsSceneBound(this UnityObject uo)
{
Ensure.That(nameof(uo)).IsNotNull(uo);
#if UNITY_2018_3_OR_NEWER
return
(uo is GameObject go && !IsPrefabDefinition(go)) ||
(uo is Component component && !IsPrefabDefinition(component.gameObject));
#else
return
(uo is GameObject go && !IsPrefabDefinition(go)) ||
(uo is Component component && !IsPrefabDefinition(component.gameObject));
#endif
}
#endregion
#region Class
public const int MonoBehaviourClassID = 114;
private static readonly Type UnityTypeType; // internal sealed class UnityType
private static readonly PropertyInfo UnityTypeType_persistentTypeID; // public int persistentTypeID { get; private set; }
private static readonly MethodInfo UnityTypeType_FindTypeByNameCaseInsensitive; // public static extern int StringToClassIDCaseInsensitive(string classString);
public static int GetClassID(Type type)
{
if (typeof(MonoBehaviour).IsAssignableFrom(type) || typeof(ScriptableObject).IsAssignableFrom(type))
{
return MonoBehaviourClassID;
}
try
{
var unityType = UnityTypeType_FindTypeByNameCaseInsensitive.Invoke(null, new object[] { type.Name });
if (unityType == null)
{
throw new Exception($"Could not find UnityType for '{type}'.");
}
return (int)UnityTypeType_persistentTypeID.GetValue(unityType, null);
}
catch (Exception ex)
{
throw new UnityEditorInternalException(ex);
}
}
public static string GetScriptClass(Type type)
{
if (!typeof(MonoBehaviour).IsAssignableFrom(type) && !typeof(ScriptableObject).IsAssignableFrom(type))
{
throw new NotSupportedException("Trying to get script class of a non-script type.");
}
return type.Name;
}
#endregion
}
}