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

397 lines
17 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="GameObjectExt.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Avatar;
using UltimateXR.Extensions.System;
using UltimateXR.Extensions.Unity.Math;
using UltimateXR.Manipulation;
using UltimateXR.UI.UnityInputModule;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
#endif
namespace UltimateXR.Extensions.Unity
{
/// <summary>
/// <see cref="GameObject" /> extensions.
/// </summary>
public static class GameObjectExt
{
#region Public Methods
/// <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
/// It also works on objects recently instantiated or on disabled objects.
/// </summary>
/// <typeparam name="T">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 GameObject self)
{
T parent = self.GetComponentInParent<T>();
return parent ?? self.GetComponentsInParent<T>(true).FirstOrDefault();
}
/// <summary>
/// Activates/deactivates the object if it isn't active already.
/// </summary>
/// <param name="self">GameObject to activate</param>
/// <param name="activate">Whether to activate or deactivate the object</param>
public static void CheckSetActive(this GameObject self, bool activate)
{
if (self.activeSelf != activate)
{
self.SetActive(activate);
}
}
/// <summary>
/// Gets a unique path in the scene for the given GameObject. It will include sibling indices to make it unique.
/// </summary>
/// <param name="self">GameObject to get the unique path for</param>
/// <returns>Unique GameObject path string</returns>
/// <seealso cref="TransformExt.GetUniqueScenePath" />
public static string GetUniqueScenePath(this GameObject self)
{
self.ThrowIfNull(nameof(self));
return self.transform.GetUniqueScenePath();
}
/// <summary>
/// Gets the path in the scene of the given GameObject.
/// </summary>
/// <param name="self">GameObject to get the scene path for</param>
/// <returns>Path of the GameObject in the scene</returns>
/// <seealso cref="TransformExt.GetPathUnderScene" />
public static string GetPathUnderScene(this GameObject self)
{
self.ThrowIfNull(nameof(self));
return self.transform.GetPathUnderScene();
}
/// <summary>
/// Checks if the given GameObject is the root GameObject inside a prefab.
/// </summary>
/// <param name="self">GameObject to check</param>
/// <returns>Whether the GameObject is the root GameObject inside a prefab</returns>
public static bool IsPrefabRoot(this GameObject self)
{
#if UNITY_EDITOR
if (PrefabStageUtility.GetCurrentPrefabStage() != null && PrefabStageUtility.GetCurrentPrefabStage().prefabContentsRoot == self)
{
return true;
}
#endif
return self.scene.name == null && self.transform.parent == null;
}
/// <summary>
/// Checks if the given GameObject is located inside a prefab.
/// </summary>
/// <param name="self">GameObject to check</param>
/// <returns>Whether the GameObject is located inside a prefab</returns>
public static bool IsInPrefab(this GameObject self)
{
#if UNITY_EDITOR
if (PrefabStageUtility.GetCurrentPrefabStage() != null && PrefabStageUtility.GetCurrentPrefabStage().IsPartOfPrefabContents(self))
{
return true;
}
return PrefabUtility.IsPartOfPrefabAsset(self);
#else
return self.scene.name == null;
#endif
}
#if UNITY_EDITOR
/// <summary>
/// Gets the GUID of the prefab the GameObject is in, if it is in a prefab, or the GUID of the prefab the GameObject
/// was instantiated from, if it was instantiated from a prefab. If it is not
/// </summary>
/// <param name="self">The GameObject to retrieve the information of</param>
/// <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 call was successful</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 GameObject self, out string prefabGuid, out string assetPath)
{
prefabGuid = string.Empty;
assetPath = string.Empty;
if (PrefabStageUtility.GetCurrentPrefabStage() != null && PrefabStageUtility.GetCurrentPrefabStage().IsPartOfPrefabContents(self))
{
// Asset in prefab view
assetPath = PrefabStageUtility.GetCurrentPrefabStage().assetPath;
}
else if (PrefabUtility.IsPartOfPrefabAsset(self))
{
// Prefab asset
assetPath = AssetDatabase.GetAssetPath(self);
if (string.IsNullOrEmpty(assetPath))
{
return false;
}
}
else if (PrefabUtility.IsPartOfNonAssetPrefabInstance(self) && self.scene.name != null)
{
GameObject instanceRoot = PrefabUtility.GetOutermostPrefabInstanceRoot(self);
GameObject rootPrefab = PrefabUtility.GetCorrespondingObjectFromSource(instanceRoot);
assetPath = AssetDatabase.GetAssetPath(rootPrefab);
}
if (!string.IsNullOrEmpty(assetPath))
{
// Try to get GUID from asset path, checking if path is valid. Otherwise return null.
// Check if all zeroes, which means invalid guid.
prefabGuid = AssetDatabase.GUIDFromAssetPath(assetPath).ToString();
prefabGuid = string.IsNullOrEmpty(prefabGuid) || prefabGuid.All(c => c == '0') ? null : prefabGuid;
return true;
}
return false;
}
#endif
/// <summary>
/// Checks whether the given GameObject is dynamic. Since <see cref="GameObject.isStatic" /> doesn't work at runtime
/// due to the static flags being editor-only, a workaround is required to try to find out if an object is dynamic or
/// not.
/// </summary>
/// <param name="self">GameObject to check</param>
/// <returns>Whether the object appears to be dynamic</returns>
public static bool IsDynamic(this GameObject self)
{
return self.GetComponentInParent<Rigidbody>() != null ||
self.GetComponentInParent<SkinnedMeshRenderer>() != null ||
self.GetComponentInParent<UxrAvatar>() != null ||
self.GetComponentInParent<UxrGrabbableObject>() != null;
}
/// <summary>
/// Gets the Component of a given type. If it doesn't exist, it is added to the GameObject.
/// </summary>
/// <param name="self">Target GameObject where the component will be looked for and added to if it doesn't exist</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 GameObject self) where T : Component
{
T component = self.GetComponent<T>();
if (component == null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
{
component = self.AddComponent<T>();
}
else
{
component = Undo.AddComponent<T>(self);
}
#else
component = self.AddComponent<T>();
#endif
}
return component;
}
/// <summary>
/// Creates a new GameObject in the exact same position as the given one and parents it.
/// </summary>
/// <param name="parent">
/// GameObject to parent the new object to and also place it at the same position
/// and with the same orientation
/// </param>
/// <param name="newGameObjectName">Name for the new GameObject</param>
/// <returns>New created GameObject</returns>
public static GameObject CreateGameObjectAndParentSameTransform(GameObject parent, string newGameObjectName)
{
GameObject newGameObject = new GameObject(newGameObjectName);
newGameObject.transform.parent = parent.transform;
newGameObject.transform.localPosition = Vector3.zero;
newGameObject.transform.localRotation = Quaternion.identity;
return newGameObject;
}
/// <summary>
/// Computes the geometric center of the given GameObject based on all the MeshRenderers in the hierarchy.
/// </summary>
/// <param name="self">GameObject to compute the geometric center of</param>
/// <returns>Geometric center</returns>
public static Vector3 GetGeometricCenter(this GameObject self)
{
IEnumerable<MeshRenderer> meshRenderers = self.GetComponentsInChildren<MeshRenderer>().Where(r => !r.hideFlags.HasFlag(HideFlags.HideInHierarchy) && r.enabled);
bool initialized = false;
Vector3 min = Vector3.zero;
Vector3 max = Vector3.zero;
if (!meshRenderers.Any())
{
return self.transform.position;
}
foreach (MeshRenderer renderer in meshRenderers)
{
if (!initialized)
{
initialized = true;
min = renderer.bounds.min;
max = renderer.bounds.max;
}
else
{
min = Vector3.Min(min, renderer.bounds.min);
max = Vector3.Max(max, renderer.bounds.max);
}
}
return (min + max) * 0.5f;
}
/// <summary>
/// Calculates the <see cref="GameObject" /> <see cref="Bounds" />. The bounds are the <see cref="Renderer" />'s bounds
/// if there is one in the GameObject. Otherwise it will encapsulate all renderers found in the children.
/// If <paramref name="forceRecurseIntoChildren" /> is true, it will also encapsulate all renderers found in
/// the children no matter if the GameObject has a Renderer component or not.
/// </summary>
/// <param name="self">The GameObject whose <see cref="Bounds" /> to get</param>
/// <param name="forceRecurseIntoChildren">
/// Whether to also encapsulate all renderers found in the children no matter if the
/// GameObject has a Renderer component or not
/// </param>
/// <returns>
/// <see cref="Bounds" /> in world-space.
/// </returns>
public static Bounds GetBounds(this GameObject self, bool forceRecurseIntoChildren)
{
Renderer renderer = self.GetComponent<Renderer>();
if (renderer != null && !forceRecurseIntoChildren)
{
if (renderer.enabled)
{
return renderer.bounds;
}
return new Bounds(self.transform.position, Vector3.zero);
}
IEnumerable<Renderer> renderers = self.GetComponentsInChildren<Renderer>().Where(r => !r.hideFlags.HasFlag(HideFlags.HideInHierarchy) && r.enabled);
if (!renderers.Any())
{
return new Bounds(self.transform.position, Vector3.zero);
}
Vector3 min = Vector3Ext.Min(renderers.Select(r => r.bounds.min));
Vector3 max = Vector3Ext.Max(renderers.Select(r => r.bounds.max));
return new Bounds((max + min) * 0.5f, max - min);
}
/// <summary>
/// Calculates the <see cref="GameObject" /> <see cref="Bounds" /> in local space. The bounds are the
/// <see cref="Renderer" />'s bounds if there is one in the GameObject. Otherwise it will encapsulate all renderers
/// found in the children.
/// If <paramref name="forceRecurseIntoChildren" /> is true, it will also encapsulate all renderers found in
/// the children no matter if the GameObject has a Renderer component or not.
/// </summary>
/// <param name="self">The GameObject whose local <see cref="Bounds" /> to get</param>
/// <param name="forceRecurseIntoChildren">
/// Whether to also encapsulate all renderers found in the children no matter if the
/// GameObject has a Renderer component or not
/// </param>
/// <returns>
/// Local <see cref="Bounds" />.
/// </returns>
public static Bounds GetLocalBounds(this GameObject self, bool forceRecurseIntoChildren)
{
Renderer renderer = self.GetComponent<Renderer>();
if (renderer != null && !forceRecurseIntoChildren)
{
if (renderer.enabled)
{
return renderer.localBounds;
}
return new Bounds();
}
IEnumerable<Renderer> renderers = self.GetComponentsInChildren<Renderer>().Where(r => !r.hideFlags.HasFlag(HideFlags.HideInHierarchy) && r.enabled);
if (!renderers.Any())
{
return new Bounds();
}
IEnumerable<Vector3> allMinMaxToLocal = renderers.Select(r => self.transform.InverseTransformPoint(r.transform.TransformPoint(r.localBounds.min))).Concat(renderers.Select(r => self.transform.InverseTransformPoint(r.transform.TransformPoint(r.localBounds.max))));
Vector3 min = Vector3Ext.Min(allMinMaxToLocal);
Vector3 max = Vector3Ext.Max(allMinMaxToLocal);
return new Bounds((max + min) * 0.5f, max - min);
}
/// <summary>
/// Sets the layer of a GameObject and all its children.
/// </summary>
/// <param name="self">The root GameObject from where to start</param>
/// <param name="layer">The layer value to assign</param>
public static void SetLayerRecursively(this GameObject self, int layer)
{
if (self != null)
{
Transform selfTransform = self.transform;
self.gameObject.layer = layer;
for (int i = 0; i < selfTransform.childCount; ++i)
{
SetLayerRecursively(selfTransform.GetChild(i).gameObject, layer);
}
}
}
/// <summary>
/// Checks whether the given GameObject's layer is present in a layer mask.
/// </summary>
/// <param name="self">The GameObject whose layer to check</param>
/// <param name="layerMask">The layer mask to check against</param>
/// <returns>Whether the GameObject's layer is present in the layer mask</returns>
public static bool IsInLayerMask(this GameObject self, LayerMask layerMask)
{
return (1 << self.layer & layerMask.value) != 0;
}
/// <summary>
/// Gets the topmost <see cref="UxrCanvas" /> upwards in the hierarchy if it exists.
/// </summary>
/// <param name="self">The GameObject whose parents to look for</param>
/// <returns>The topmost <see cref="UxrCanvas" /> component upwards in the hierarchy or null if it doesn't exists</returns>
public static UxrCanvas GetTopmostCanvas(this GameObject self)
{
UxrCanvas[] canvases = self.GetComponentsInParent<UxrCanvas>();
return ComponentExt.GetCommonRootComponentFromSet(canvases);
}
#endregion
}
}