Files
dungeons/Assets/UltimateXR/Runtime/Scripts/Extensions/Unity/ComponentExt.cs
2024-08-06 21:58:35 +02:00

316 lines
13 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ComponentExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Core;
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UltimateXR.Extensions.Unity
{
/// <summary>
/// <see cref="Component" /> extensions.
/// </summary>
public static class ComponentExt
{
#region Public Methods
/// <summary>
/// Controls whether to show a given component in the inspector.
/// </summary>
/// <param name="self">The component to show</param>
/// <param name="show">Whether to show the component or now</param>
public static void ShowInInspector(this Component self, bool show = true)
{
#if UNITY_EDITOR
if (Application.isEditor)
{
SerializedObject so = new SerializedObject(self);
SerializedProperty prop = so.FindProperty(UxrConstants.Editor.PropertyObjectHideFlags);
if (show)
{
prop.intValue &= (int)~HideFlags.HideInInspector;
}
else
{
prop.intValue |= (int)HideFlags.HideInInspector;
}
so.ApplyModifiedProperties();
return;
}
#endif
if (show)
{
self.hideFlags &= ~HideFlags.HideInInspector;
}
else
{
self.hideFlags |= HideFlags.HideInInspector;
}
}
/// <summary>
/// Controls whether to show a given component in the inspector and whether it is editable.
/// </summary>
/// <param name="self">The object to set</param>
/// <param name="show">Whether to show it in the inspector</param>
/// <param name="editable">Whether it is editable</param>
public static void ShowInInspector(this Component self, bool show, bool editable)
{
#if UNITY_EDITOR
SerializedObject so = new SerializedObject(self);
SerializedProperty prop = so.FindProperty(UxrConstants.Editor.PropertyObjectHideFlags);
if (show)
{
prop.intValue &= (int)~HideFlags.HideInInspector;
}
else
{
prop.intValue |= (int)HideFlags.HideInInspector;
}
if (editable)
{
prop.intValue &= (int)~HideFlags.NotEditable;
}
else
{
prop.intValue |= (int)HideFlags.NotEditable;
}
so.ApplyModifiedProperties();
#else
if (show)
{
self.hideFlags &= ~HideFlags.HideInInspector;
}
else
{
self.hideFlags |= HideFlags.HideInInspector;
}
if (editable)
{
self.hideFlags &= ~HideFlags.NotEditable;
}
else
{
self.hideFlags |= HideFlags.NotEditable;
}
#endif
}
/// <summary>
/// Checks whether the component is in a prefab.
/// </summary>
/// <param name="self">Component to check</param>
/// <returns>Whether the component is in a prefab</returns>
public static bool IsInPrefab(this Component self)
{
return self.gameObject.IsInPrefab();
}
#if UNITY_EDITOR
/// <summary>
/// Gets the GUID of the prefab the component is in, if it is in a prefab, or the GUID of the prefab the component was
/// instantiated from, if it was instantiated from a prefab.
/// If the component is not in a prefab and doesn't have a source prefab either, it will return string.Empty.
/// </summary>
/// <param name="prefabGuid">If the call was successful, returns the GUID or string.Empty</param>
/// <returns>Whether the prefab GUID could be retrieved</returns>
/// <remarks>
/// The reason the call can be unsuccessful is because Unity for some reason will report
/// a null/empty asset path even though PrefabUtility.IsPartOfPrefabAsset() returns true.
/// This behaviour happens when in prefab isolation/context mode in the editor
/// </remarks>
public static bool GetPrefabGuid(this Component self, out string prefabGuid)
{
return self.gameObject.GetPrefabGuid(out prefabGuid, out string _);
}
/// <summary>
/// Same as <see cref="GetPrefabGuid(UnityEngine.Component)" /> but it also returns the asset path if it exists.
/// </summary>
/// <param name="prefabGuid">If the call was successful, returns the GUID or string.Empty</param>
/// <param name="assetPath">If the call was successful, returns the asset path or string.Empty</param>
/// <returns>Whether the prefab GUID could be retrieved</returns>
/// <remarks>
/// The reason the call can be unsuccessful is because Unity for some reason will report
/// a null/empty asset path even though PrefabUtility.IsPartOfPrefabAsset() returns true.
/// This behaviour happens when in prefab isolation/context mode in the editor
/// </remarks>
public static bool GetPrefabGuid(this Component self, out string prefabGuid, out string assetPath)
{
return self.gameObject.GetPrefabGuid(out prefabGuid, out assetPath);
}
#endif
/// <summary>
/// Gets the Component of a given type. If it doesn't exist, it is added to the GameObject.
/// </summary>
/// <param name="self">Component whose GameObject will be used to retrieve or add the given component type to</param>
/// <typeparam name="T">Component type to get or add</typeparam>
/// <returns>Existing component or newly added if it didn't exist before</returns>
public static T GetOrAddComponent<T>(this Component self) where T : Component
{
T component = self.GetComponent<T>();
if (component == null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
{
component = self.gameObject.AddComponent<T>();
}
else
{
component = Undo.AddComponent<T>(self.gameObject);
}
#else
component = self.gameObject.AddComponent<T>();
#endif
}
return component;
}
/// <summary>
/// Gets the Component of a given type in the GameObject or any of its parents. It also works on prefabs, where regular
/// <see cref="Component.GetComponentInParent" /> will not work:
/// https://issuetracker.unity3d.com/issues/getcomponentinparent-is-returning-null-when-the-gameobject-is-a-prefab
/// </summary>
/// <typeparam name="T"><see cref="Component" /> type to get</typeparam>
/// <returns>Component in same GameObject or any of its parents. Null if it wasn't found</returns>
public static T SafeGetComponentInParent<T>(this Component self)
{
return self.GetComponentInParent<T>() ?? self.GetComponentsInParent<T>(true).FirstOrDefault();
}
/// <summary>
/// Gets the full path under current scene, including all parents, but scene name, for the given component.
/// </summary>
/// <remarks>
/// The path generated might not be unique. If that is the purpose, use <see cref="GetUniqueScenePath" /> instead.
/// </remarks>
/// <param name="self"><see cref="Component" /> to get the path for</param>
/// <param name="relativeTo">
/// Optional Transform to get the path relative to. If it's not the same Transform or a Transform up in the hierarchy
/// it will return the full path
/// </param>
/// <returns>Component path string</returns>
public static string GetPathUnderScene(this Component self, Transform relativeTo = null)
{
string path = self.transform.GetPathUnderScene(relativeTo);
return self is Transform ? path : $"{path}/{self.GetType().Name}";
}
/// <summary>
/// Gets an unique path in the scene for the given component. It will include scene name, sibling and component indices
/// to make it unique.
/// </summary>
/// <param name="self"><see cref="Component" /> to get the unique path for</param>
/// <param name="relativeTo">
/// Optional Transform to get the path relative to. If it's not the same Transform or a Transform up in the hierarchy
/// it will return the full path
/// </param>
/// <returns>Unique component path string</returns>
public static string GetUniqueScenePath(this Component self, Transform relativeTo = null)
{
string path = self.transform.GetUniqueScenePath(relativeTo);
return self is Transform ? path : $"{path}/{Array.IndexOf(self.GetComponents<Component>(), self):00}{self.GetType().Name}";
}
/// <summary>
/// Gets an unique identifier string for the given component.
/// </summary>
/// <remarks>Generates an 8 characters hexadecimal hash code of <see cref="GetUniqueScenePath" />.</remarks>
/// <param name="self"><see cref="Component" /> to get UID for.</param>
/// <returns>8 characters hexadecimal unique identifier <see cref="string" /></returns>
public static string GetSceneUid(this Component self)
{
return self.GetUniqueScenePath().GetHashCode().ToString("x8");
}
/// <summary>
/// Gets a list of all components of the given type in the open scenes
/// </summary>
/// <typeparam name="T">Type of component to look for</typeparam>
/// <param name="includeInactive">Whether to include inactive components or not</param>
/// <returns>List of components</returns>
public static List<T> GetAllComponentsInOpenScenes<T>(bool includeInactive) where T : Component
{
List<T> listResult = new List<T>();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene s = SceneManager.GetSceneAt(i);
if (s.isLoaded)
{
GameObject[] rootGameObjects = s.GetRootGameObjects();
foreach (GameObject go in rootGameObjects)
{
listResult.AddRange(go.GetComponentsInChildren<T>(includeInactive));
}
}
}
return listResult;
}
/// <summary>
/// From a set of components, returns which one of them has a transform that is a common root of all.
/// The transform must be the transform of a component in the list.
/// </summary>
/// <param name="components">Components whose transforms to check</param>
/// <returns>
/// Returns which transform from all the components passed as parameters is a common root of all. If no component has a
/// transform that is a common root it returns null.
/// </returns>
public static T GetCommonRootComponentFromSet<T>(params T[] components) where T : Component
{
T commonRoot = null;
for (int i = 0; i < components.Length; i++)
{
if (i == 0)
{
commonRoot = components[i];
}
else
{
if (commonRoot == null || (components[i] != commonRoot && components[i].transform.HasParent(commonRoot.transform) == false))
{
bool found = true;
for (int j = 0; j < i - 1; j++)
{
if (components[i] != components[j] && components[j].transform.HasParent(components[i].transform) == false)
{
found = false;
}
}
commonRoot = found ? components[i] : null;
}
}
}
return commonRoot;
}
#endregion
}
}