// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using UltimateXR.Avatar; using UltimateXR.Core; using UltimateXR.Extensions.Unity; using UnityEditor; using UnityEngine; namespace UltimateXR.Editor { public static partial class UxrEditorUtils { #region Public Methods /// /// Checks if the given GameObject can be destroyed. GameObjects inside prefabs that are inherited from a parent prefab /// cannot be destroyed. /// /// GameObject to check /// Whether the GameObject can be destroyed public static bool CanBeDestroyed(this GameObject gameObject) { if (!gameObject.IsInPrefab()) { return true; } if (gameObject.IsPrefabRoot()) { return false; } GameObject source = PrefabUtility.GetCorrespondingObjectFromSource(gameObject); GameObject parentSource = PrefabUtility.GetCorrespondingObjectFromSource(gameObject.transform.parent.gameObject); if (source == null || parentSource == null) { return true; } return source.transform.parent != parentSource.transform; } /// /// Gets the gameObject in the parent prefab if it exists. /// /// GameObject to process /// /// The GameObject in the prefab it comes from. 3D models (.fbx files) are not considered prefabs by this method. /// /// True if a prefab was found, false if not public static bool GetInParentPrefab(GameObject gameObject, out GameObject prefab) { prefab = null; if (gameObject == null) { return false; } prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject); PrefabAssetType prefabAssetType = prefab != null ? PrefabUtility.GetPrefabAssetType(prefab) : PrefabAssetType.NotAPrefab; return prefab && (prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant); } /// /// Checks if the given GameObject is a prefab instance in a scene. 3D models do not count as prefabs. /// /// GameObject to check /// Whether the GameObject is a prefab instance in a scene public static bool IsPrefabInstance(this GameObject gameObject) { if (gameObject.IsInPrefab()) { return false; } GameObject inParentPrefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject); PrefabAssetType prefabAssetType = inParentPrefab != null ? PrefabUtility.GetPrefabAssetType(inParentPrefab) : PrefabAssetType.NotAPrefab; return prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant; } /// /// Gets the innermost prefab in a hierarchy. /// /// Component, from a prefab or instance in a scene /// /// Returns the root GameObject in the prefab the component comes from. If it's a nested prefab, it will return the /// innermost one. If it's not a prefab, it will return null. 3D models (.fbx files) are not considered prefabs by this /// method. /// /// /// Returns the root GameObject of the instantiated , in the scene or in a prefab. /// /// /// Returns the component in the prefab or null if it's not part of a prefab. /// /// True if a prefab was found, false if not public static bool GetInnermostNon3DModelPrefabRoot(T component, out GameObject prefab, out GameObject prefabInstance, out T componentInPrefab) where T : Component { // Find component but in prefab hierarchy. CorrespondingObjectFromOriginalSource() gets the component in the root prefab hierarchy. prefab = null; prefabInstance = null; componentInPrefab = PrefabUtility.GetCorrespondingObjectFromOriginalSource(component); if (componentInPrefab != null) { Transform current = componentInPrefab.transform; prefabInstance = component.gameObject; // Navigate to parent while (current.parent != null) { current = current.parent; prefabInstance = prefabInstance.transform.parent.gameObject; } prefab = current.gameObject; } PrefabAssetType prefabAssetType = componentInPrefab != null ? PrefabUtility.GetPrefabAssetType(componentInPrefab) : PrefabAssetType.NotAPrefab; if (prefabAssetType == PrefabAssetType.Model) { // Solve case where prefab is model. We want the immediately superior parent prefab in the hierarchy T parentPrefabComponent = PrefabUtility.GetCorrespondingObjectFromSource(component); T prefabComponentCandidate = null; if (parentPrefabComponent != null) { // Navigate to the correct component in the last prefab parent before it's a model while (PrefabUtility.GetPrefabAssetType(parentPrefabComponent) == PrefabAssetType.Regular || PrefabUtility.GetPrefabAssetType(parentPrefabComponent) == PrefabAssetType.Variant) { prefabComponentCandidate = parentPrefabComponent; parentPrefabComponent = PrefabUtility.GetCorrespondingObjectFromSource(parentPrefabComponent); } // Go up in the transform hierarchy to find prefab root prefabInstance = component.gameObject; if (prefabComponentCandidate != null) { componentInPrefab = prefabComponentCandidate; prefab = prefabComponentCandidate.transform.gameObject; while (prefab.transform.parent != null) { prefab = prefab.transform.parent.gameObject; if (prefabInstance != null && prefabInstance.transform.parent != null) { prefabInstance = prefabInstance.transform.parent.gameObject; } } prefabAssetType = PrefabUtility.GetPrefabAssetType(prefab); } } } return componentInPrefab && (prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant); } /// /// Gets the innermost prefab in a hierarchy that meets the requirements. /// /// Component, from a prefab or instance in a scene /// /// Allows to specify a project base path that the prefab needs to be in. /// Even if there are inner prefabs, it will look for the innermost that still is within the base path. /// Use null or empty to specify the whole project. /// /// /// Returns the root GameObject in the prefab the component comes from. If it's a nested prefab, it will return the /// innermost one. If it's not a prefab, it will return null. 3D models (.fbx files) are not considered prefabs by this /// method. /// /// /// Returns the root GameObject of the instantiated , in the scene or in a prefab. /// /// /// Returns the component in the prefab or null if it's not part of a prefab. /// /// True if a prefab was found, false if not public static bool GetInnermostNon3DModelPrefabRoot(T component, string requiredBasePath, bool ignoreUltimateXRAssets, out GameObject prefab, out GameObject prefabInstance, out T componentInPrefab) where T : Component { if (string.IsNullOrEmpty(requiredBasePath)) { return GetInnermostNon3DModelPrefabRoot(component, out prefab, out prefabInstance, out componentInPrefab); } bool IsValidComponent(T c) { return c != null && (PrefabUtility.GetPrefabAssetType(c) == PrefabAssetType.Regular || PrefabUtility.GetPrefabAssetType(c) == PrefabAssetType.Variant) && PathRequiresProcessing(requiredBasePath, AssetDatabase.GetAssetPath(c), ignoreUltimateXRAssets); } // Traverse prefab chain looking for parent prefab that is still in valid base path: componentInPrefab = PrefabUtility.GetCorrespondingObjectFromSource(component); prefab = null; prefabInstance = component.gameObject; T lastValidComponentInPrefab = null; while (IsValidComponent(componentInPrefab)) { lastValidComponentInPrefab = componentInPrefab; componentInPrefab = PrefabUtility.GetCorrespondingObjectFromSource(componentInPrefab); prefab = componentInPrefab.gameObject; while (prefab.transform.parent != null) { prefab = prefab.transform.parent.gameObject; prefabInstance = prefabInstance.transform.parent.gameObject; } } if (lastValidComponentInPrefab == null) { return false; } return true; } /// /// Prompt the user to save a prefab (or prefab variant) of the given avatar. /// /// Avatar to create prefab of /// Text shown on the dialog title /// Default prefab file name /// Returns the prefab GameObject if successful or null if canceled/error /// /// If is a prefab instance in a scene, it will return a new instance /// that substitutes the old one. The old instance will be deleted. If is a prefab it will /// return null. /// /// Whether the prefab was created successfully public static bool CreateAvatarPrefab(UxrAvatar avatar, string title, string defaultName, out GameObject prefab, out GameObject newInstance) { prefab = null; newInstance = null; string path = EditorUtility.SaveFilePanelInProject(title, defaultName, "prefab", "Please select the file in your project to save the prefab to"); if (!string.IsNullOrEmpty(path)) { if (PathIsInUltimateXR(path)) { EditorUtility.DisplayDialog(UxrConstants.Editor.Error, "The file location can't be inside the UltimateXR framework to prevent it from being deleted", UxrConstants.Editor.Ok); } else if (!PathIsInCurrentProject(path)) { EditorUtility.DisplayDialog(UxrConstants.Editor.Error, "The file location can't be outside the project's Assets folder", UxrConstants.Editor.Ok); } else { prefab = PrefabUtility.SaveAsPrefabAsset(avatar.gameObject, path, out bool success); if (!success || prefab == null) { EditorUtility.DisplayDialog(UxrConstants.Editor.Error, "There was an error generating the variant", UxrConstants.Editor.Ok); } else { prefab.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); prefab.transform.localScale = Vector3.one; if (avatar.gameObject.CanBeDestroyed()) { newInstance = PrefabUtility.InstantiatePrefab(prefab, avatar.transform.parent) as GameObject; if (newInstance != null) { newInstance.name = avatar.name; newInstance.transform.SetPositionAndRotation(avatar.transform); newInstance.transform.localScale = avatar.transform.localScale; newInstance.transform.SetSiblingIndex(avatar.transform.GetSiblingIndex()); Object.DestroyImmediate(avatar.gameObject); } } return true; } } } return false; } #endregion } }