// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- 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 { /// /// extensions. /// public static class GameObjectExt { #region Public Methods /// /// Gets the Component of a given type in the GameObject or any of its parents. It also works on prefabs, where regular /// 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. /// /// Component type to get /// Component in same GameObject or any of its parents. Null if it wasn't found public static T SafeGetComponentInParent(this GameObject self) { T parent = self.GetComponentInParent(); return parent ?? self.GetComponentsInParent(true).FirstOrDefault(); } /// /// Activates/deactivates the object if it isn't active already. /// /// GameObject to activate /// Whether to activate or deactivate the object public static void CheckSetActive(this GameObject self, bool activate) { if (self.activeSelf != activate) { self.SetActive(activate); } } /// /// Gets a unique path in the scene for the given GameObject. It will include sibling indices to make it unique. /// /// GameObject to get the unique path for /// Unique GameObject path string /// public static string GetUniqueScenePath(this GameObject self) { self.ThrowIfNull(nameof(self)); return self.transform.GetUniqueScenePath(); } /// /// Gets the path in the scene of the given GameObject. /// /// GameObject to get the scene path for /// Path of the GameObject in the scene /// public static string GetPathUnderScene(this GameObject self) { self.ThrowIfNull(nameof(self)); return self.transform.GetPathUnderScene(); } /// /// Checks if the given GameObject is the root GameObject inside a prefab. /// /// GameObject to check /// Whether the GameObject is the root GameObject inside a prefab 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; } /// /// Checks if the given GameObject is located inside a prefab. /// /// GameObject to check /// Whether the GameObject is located inside a prefab 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 /// /// 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 /// /// The GameObject to retrieve the information of /// If the call was successful, returns the GUID or string.Empty /// If the call was successful, returns the asset path or string.Empty /// Whether the call was successful /// 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 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 /// /// Checks whether the given GameObject is dynamic. Since 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. /// /// GameObject to check /// Whether the object appears to be dynamic public static bool IsDynamic(this GameObject self) { return self.GetComponentInParent() != null || self.GetComponentInParent() != null || self.GetComponentInParent() != null || self.GetComponentInParent() != null; } /// /// Gets the Component of a given type. If it doesn't exist, it is added to the GameObject. /// /// Target GameObject where the component will be looked for and added to if it doesn't exist /// Component type to get or add /// Existing component or newly added if it didn't exist before public static T GetOrAddComponent(this GameObject self) where T : Component { T component = self.GetComponent(); if (component == null) { #if UNITY_EDITOR if (Application.isPlaying) { component = self.AddComponent(); } else { component = Undo.AddComponent(self); } #else component = self.AddComponent(); #endif } return component; } /// /// Creates a new GameObject in the exact same position as the given one and parents it. /// /// /// GameObject to parent the new object to and also place it at the same position /// and with the same orientation /// /// Name for the new GameObject /// New created GameObject 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; } /// /// Computes the geometric center of the given GameObject based on all the MeshRenderers in the hierarchy. /// /// GameObject to compute the geometric center of /// Geometric center public static Vector3 GetGeometricCenter(this GameObject self) { IEnumerable meshRenderers = self.GetComponentsInChildren().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; } /// /// Calculates the . The bounds are the 's bounds /// if there is one in the GameObject. Otherwise it will encapsulate all renderers found in the children. /// If is true, it will also encapsulate all renderers found in /// the children no matter if the GameObject has a Renderer component or not. /// /// The GameObject whose to get /// /// Whether to also encapsulate all renderers found in the children no matter if the /// GameObject has a Renderer component or not /// /// /// in world-space. /// public static Bounds GetBounds(this GameObject self, bool forceRecurseIntoChildren) { Renderer renderer = self.GetComponent(); if (renderer != null && !forceRecurseIntoChildren) { if (renderer.enabled) { return renderer.bounds; } return new Bounds(self.transform.position, Vector3.zero); } IEnumerable renderers = self.GetComponentsInChildren().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); } /// /// Calculates the in local space. The bounds are the /// 's bounds if there is one in the GameObject. Otherwise it will encapsulate all renderers /// found in the children. /// If is true, it will also encapsulate all renderers found in /// the children no matter if the GameObject has a Renderer component or not. /// /// The GameObject whose local to get /// /// Whether to also encapsulate all renderers found in the children no matter if the /// GameObject has a Renderer component or not /// /// /// Local . /// public static Bounds GetLocalBounds(this GameObject self, bool forceRecurseIntoChildren) { Renderer renderer = self.GetComponent(); if (renderer != null && !forceRecurseIntoChildren) { if (renderer.enabled) { return renderer.localBounds; } return new Bounds(); } IEnumerable renderers = self.GetComponentsInChildren().Where(r => !r.hideFlags.HasFlag(HideFlags.HideInHierarchy) && r.enabled); if (!renderers.Any()) { return new Bounds(); } IEnumerable 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); } /// /// Sets the layer of a GameObject and all its children. /// /// The root GameObject from where to start /// The layer value to assign 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); } } } /// /// Checks whether the given GameObject's layer is present in a layer mask. /// /// The GameObject whose layer to check /// The layer mask to check against /// Whether the GameObject's layer is present in the layer mask public static bool IsInLayerMask(this GameObject self, LayerMask layerMask) { return (1 << self.layer & layerMask.value) != 0; } /// /// Gets the topmost upwards in the hierarchy if it exists. /// /// The GameObject whose parents to look for /// The topmost component upwards in the hierarchy or null if it doesn't exists public static UxrCanvas GetTopmostCanvas(this GameObject self) { UxrCanvas[] canvases = self.GetComponentsInParent(); return ComponentExt.GetCommonRootComponentFromSet(canvases); } #endregion } }