// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Extensions.Unity;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace UltimateXR.Editor
{
public static partial class UxrEditorUtils
{
#region Public Methods
///
/// Modifies a component in a prefab or prefab instance, allowing to apply modifications along the prefab variant chain
/// and original prefab if they exist.
/// The method is guaranteed to process prefabs in an order where the source prefab is processed first and then all
/// child prefabs down to the outermost prefab.
///
/// The component type
///
/// Component or GameObject to process. It can be an instance in the scene or a
/// component/GameObject in a prefab
///
/// Component processing options (flags)
/// Processor that modifies the component
///
/// Will receive updates of the process so that the information can be fed to a Unity
/// progress bar
///
/// Returns whether the user canceled the operation using the progress updater
///
/// Whether to only check if components should be processed, without making any changes. This
/// can be used to get how many elements would be changed without modifying any data
///
/// The component type
/// Whether any modifications were made
public static bool ModifyComponent(Object componentOrGameObject,
UxrComponentProcessingOptions options,
UxrComponentProcessor componentProcessor,
UxrProgressUpdater progressUpdater,
out bool canceled,
bool onlyCheck = false) where T : Component
{
canceled = false;
if (componentOrGameObject == null)
{
return false;
}
// Gather component(s) to process
T[] components = null;
bool sourceIsPrefab = false;
if (componentOrGameObject is GameObject sourceGameObject)
{
components = options.HasFlag(UxrComponentProcessingOptions.RecurseIntoChildren) ? sourceGameObject.GetComponentsInChildren(true) : sourceGameObject.GetComponents();
sourceIsPrefab = sourceGameObject.IsInPrefab();
}
else if (componentOrGameObject is T sourceComponent)
{
components = new[] { sourceComponent };
sourceIsPrefab = sourceComponent.IsInPrefab();
}
else
{
Debug.LogError($"{nameof(ModifyComponent)}: Cannot process type {componentOrGameObject.GetType().Name}, expected {nameof(GameObject)} or component of type {typeof(T)}.");
return false;
}
// Gather all prefabs to process, if any, and for each prefab all its components to process
Dictionary> prefabComponents = new Dictionary>();
for (var i = 0; i < components.Length; i++)
{
T c = components[i];
if (c == null)
{
continue;
}
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Preprocessing components", $"Object {c.name}, Component {c.GetType().Name} ", (float)i / components.Length));
if (canceled)
{
return false;
}
}
// For each component, iterate over all components in variant prefab chain starting from the original prefab, so that changes get propagated in the correct order
T componentInChain = c;
if (!sourceIsPrefab)
{
// Component is instance. Get component in prefab to start.
componentInChain = PrefabUtility.GetCorrespondingObjectFromSource(c);
}
while (componentInChain != null)
{
if (!options.HasFlag(UxrComponentProcessingOptions.RecurseIntoPrefabs) && !sourceIsPrefab)
{
break;
}
T componentInParentPrefab = PrefabUtility.GetCorrespondingObjectFromSource(componentInChain);
bool isOriginalPrefab = componentInParentPrefab == null;
string assetPath = AssetDatabase.GetAssetPath(componentInChain.transform.root.gameObject);
bool process = (isOriginalPrefab && options.HasFlag(UxrComponentProcessingOptions.ProcessOriginalPrefabComponents)) ||
(!isOriginalPrefab && options.HasFlag(UxrComponentProcessingOptions.ProcessNonOriginalPrefabComponents));
if (process)
{
if (!ShouldIgnoreUltimateXRPath(assetPath, !options.HasFlag(UxrComponentProcessingOptions.ProcessUltimateXRAssetComponents)))
{
// Initialize
if (!string.IsNullOrEmpty(assetPath))
{
if (!prefabComponents.ContainsKey(assetPath))
{
prefabComponents.Add(assetPath, new List());
}
// Add if it was not added already through another instance chain
if (!prefabComponents[assetPath].Contains(componentInChain))
{
prefabComponents[assetPath].Add(componentInChain);
}
}
}
}
componentInChain = componentInParentPrefab;
if (!options.HasFlag(UxrComponentProcessingOptions.RecurseIntoPrefabs) && sourceIsPrefab)
{
break;
}
}
}
// Get sorted prefab list and process prefabs in correct order
List sortedPrefabPaths = GetSortedPrefabProcessingPaths(prefabComponents.Keys, !options.HasFlag(UxrComponentProcessingOptions.ProcessUltimateXRAssetComponents));
bool processedAny = false;
for (var i = 0; i < sortedPrefabPaths.Count; i++)
{
string prefabPath = sortedPrefabPaths[i];
// Filter out paths that are dependencies but are not included in the list that we built because they are not being processed
if (prefabComponents.ContainsKey(prefabPath))
{
GameObject prefab = AssetDatabase.LoadMainAssetAtPath(prefabPath) as GameObject;
bool processed = false;
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Processing components", $"Prefab {prefab.name}", (float)i / components.Length));
if (canceled)
{
return processedAny;
}
}
foreach (T component in prefabComponents[prefabPath])
{
bool isOriginalSource = PrefabUtility.GetCorrespondingObjectFromSource(component) == null;
if (componentProcessor != null && componentProcessor.Invoke(new UxrComponentInfo(component, prefab, isOriginalSource, isOriginalSource), onlyCheck))
{
processed = true;
processedAny = true;
}
}
if (processed && !onlyCheck)
{
PrefabUtility.SavePrefabAsset(prefab);
}
}
}
// End with instances if there are any
if (!sourceIsPrefab)
{
for (var i = 0; i < components.Length; i++)
{
T c = components[i];
if (c == null)
{
continue;
}
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Processing components", $"Object {c.name}, Component {c.GetType().Name} ", (float)i / components.Length));
if (canceled)
{
return processedAny;
}
}
bool process = (c.gameObject.IsPrefabInstance() && options.HasFlag(UxrComponentProcessingOptions.ProcessPrefabSceneComponents)) ||
(!c.gameObject.IsPrefabInstance() && options.HasFlag(UxrComponentProcessingOptions.ProcessOriginalSceneComponents));
if (c.gameObject.scene.name != null)
{
if (ShouldIgnoreUltimateXRPath(c.gameObject.scene.path, !options.HasFlag(UxrComponentProcessingOptions.ProcessUltimateXRAssetComponents)))
{
process = false;
}
}
bool isOriginalSource = !c.gameObject.IsPrefabInstance();
if (process && componentProcessor != null && componentProcessor.Invoke(new UxrComponentInfo(c, null, isOriginalSource, isOriginalSource), onlyCheck))
{
processedAny = true;
if (!onlyCheck)
{
EditorUtility.SetDirty(c);
}
}
}
}
return processedAny;
}
///
/// Processes all components in a project:
///
/// - Components in the currently open scenes
/// - Components in other scenes in the project
/// - Components in the project's prefabs
///
/// Each component can be guaranteed to be processed only once by checking
/// .
/// It is true for:
///
/// - Components in the scene that are not instantiated from a prefab
/// - Components inside the original prefab
///
/// And false for
///
/// - Components in the scene that are instantiated from a prefab
/// - Components inside prefabs that are inherited from another source prefab
///
/// When processing components inside prefabs, will tell which prefab
/// is being processed.
/// Also, components are processed in an order that, in prefabs, guarantees that parent prefabs are processed first,
/// starting from the source prefab downwards to the outermost prefab.
/// This ensures that changes in a prefab that depend on parent prefabs will always get the updated inherited value in
/// the current prefab.
///
/// The type of the component to process
///
/// Base path of scenes and prefabs to process. Use null or empty to process the whole project. If
/// using a base path, it should start with Assets/
///
///
/// The component processor. It will receive the component to process as argument and it requires to return a boolean
/// telling whether the component was modified or not.
///
///
/// Will receive updates of the process so that the information can be fed to a Unity progress bar
///
/// Returns whether the user canceled the operation using the progress updater
/// Whether to ignore components in assets in UltimateXR folders
///
/// Whether to only check if components should be processed, without making any changes. This
/// can be used to get how many elements would be changed without modifying any data
///
/// The component type
/// Whether any modifications were made
public static bool ProcessAllProjectComponents(string basePath,
UxrComponentProcessor componentProcessor,
UxrProgressUpdater progressUpdater,
out bool canceled,
bool ignoreUltimateXRAssets = false,
bool onlyCheck = false) where T : Component
{
// Process prefabs in project in best order
bool processedAny = ProcessPrefabs(GetSortedPrefabProcessingPaths(basePath, ignoreUltimateXRAssets), basePath, ignoreUltimateXRAssets, true, true, componentProcessor, progressUpdater, out canceled, onlyCheck);
if (canceled)
{
return processedAny;
}
// Open scenes
List openScenes = new List();
for (int i = 0; i < SceneManager.sceneCount; ++i)
{
Scene scene = SceneManager.GetSceneAt(i);
if (PathRequiresProcessing(basePath, scene.path, ignoreUltimateXRAssets))
{
if (ProcessAllNonPrefabInstanceGameObjects(scene, componentProcessor, progressUpdater, out canceled, onlyCheck))
{
processedAny = true;
}
if (canceled)
{
return processedAny;
}
if (ProcessAllInnermostPrefabInstanceGameObjects(scene, basePath, ignoreUltimateXRAssets, componentProcessor, progressUpdater, out canceled, onlyCheck))
{
processedAny = true;
}
if (canceled)
{
return processedAny;
}
}
openScenes.Add(scene);
}
// Process non-open scenes
foreach (string scenePath in GetAllAssetPathsExceptPackages().Where(path => path.EndsWith(".unity")))
{
if (openScenes.All(s => s.path != scenePath) && PathRequiresProcessing(basePath, scenePath, ignoreUltimateXRAssets))
{
Scene scene = default;
try
{
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Opening scene", scenePath, 0.0f));
if (canceled)
{
return processedAny;
}
}
scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
if (ProcessAllNonPrefabInstanceGameObjects(scene, componentProcessor, progressUpdater, out canceled, onlyCheck))
{
processedAny = true;
}
if (canceled)
{
return processedAny;
}
if (ProcessAllInnermostPrefabInstanceGameObjects(scene, basePath, ignoreUltimateXRAssets, componentProcessor, progressUpdater, out canceled, onlyCheck))
{
processedAny = true;
}
if (canceled)
{
return processedAny;
}
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Closing scene", scene.name, 1.0f));
if (canceled)
{
return processedAny;
}
}
if (processedAny && !onlyCheck)
{
EditorSceneManager.SaveScene(scene);
}
EditorSceneManager.CloseScene(scene, true);
}
catch (Exception)
{
Debug.LogWarning($"Error opening scene {scenePath}. Skipping...");
if (scene != default)
{
EditorSceneManager.CloseScene(scene, true);
}
}
}
}
if (!onlyCheck)
{
AssetDatabase.SaveAssets();
Resources.UnloadUnusedAssets();
}
return processedAny;
}
///
/// Same as with some key aspects:
///
/// -
/// Instead of processing a path looking for scenes, the scene paths will be provided as a parameter.
///
/// -
/// From the scenes, components that do not belong to a prefab instance will be processed.
/// Additionally, all prefabs that have instances in the scene will be processed, including all prefabs up in
/// the prefab chain. Only prefabs instantiated in the scene that are in will be
/// processed.
///
/// -
/// If is true, all prefab assets in will be
/// processed too. If any is instantiated in a scene processed from ,
/// it will be processed only once.
///
///
///
/// Scene paths to process
/// Whether to process all prefab assets in
///
/// Base path to process looking for prefabs. In addition, prefabs in any scene from
/// will be processed only if they are located in this base path.
/// If the base path is null or empty, the whole project is considered
///
///
/// The component processor. It will receive the component to process as argument and it requires to return a boolean
/// telling whether the component was modified or not.
///
///
/// Will receive updates of the process so that the information can be fed to a Unity progress bar
///
/// Returns whether the user canceled the operation using the progress updater
/// Whether to ignore components in assets in UltimateXR folders
///
/// Whether to only check if components should be processed, without making any changes. This
/// can be used to get how many elements would be changed without modifying any data
///
/// The component type
/// Whether any modifications were made
public static bool ProcessScenesAndProjectPathPrefabs(IEnumerable scenePaths,
bool processBasePath,
string basePath,
UxrComponentProcessor componentProcessor,
UxrProgressUpdater progressUpdater,
out bool canceled,
bool ignoreUltimateXRAssets = false,
bool onlyCheck = false) where T : Component
{
bool processedAny = false;
canceled = false;
List scenePrefabPaths = new List();
// Process open scenes if they are in the list
if (scenePaths.Any())
{
List openScenes = new List();
for (int i = 0; i < SceneManager.sceneCount; ++i)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scenePaths.Any(s => s == scene.path))
{
if (ProcessAllNonPrefabInstanceGameObjects(scene, componentProcessor, progressUpdater, out canceled, onlyCheck))
{
processedAny = true;
}
if (canceled)
{
return processedAny;
}
if (ProcessAllInnermostPrefabInstanceGameObjects(scene, basePath, ignoreUltimateXRAssets, componentProcessor, progressUpdater, out canceled, onlyCheck))
{
processedAny = true;
}
if (canceled)
{
return processedAny;
}
GetScenePrefabPaths(scene, scenePrefabPaths);
if (canceled)
{
return processedAny;
}
}
openScenes.Add(scene);
}
// Process non-open scenes in list
foreach (string scenePath in scenePaths)
{
bool isSceneOpen = false;
for (int i = 0; i < SceneManager.sceneCount; ++i)
{
if (SceneManager.GetSceneAt(i).path == scenePath)
{
isSceneOpen = true;
break;
}
}
if (!isSceneOpen)
{
try
{
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Opening scene", scenePath, 0.0f));
if (canceled)
{
return processedAny;
}
}
Scene scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
if (ProcessAllNonPrefabInstanceGameObjects(scene, componentProcessor, progressUpdater, out canceled, onlyCheck))
{
processedAny = true;
}
if (canceled)
{
return processedAny;
}
if (ProcessAllInnermostPrefabInstanceGameObjects(scene, basePath, ignoreUltimateXRAssets, componentProcessor, progressUpdater, out canceled, onlyCheck))
{
processedAny = true;
}
if (canceled)
{
return processedAny;
}
GetScenePrefabPaths(scene, scenePrefabPaths);
if (canceled)
{
return processedAny;
}
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Closing scene", scene.name, 1.0f));
if (canceled)
{
return processedAny;
}
}
if (processedAny && !onlyCheck)
{
EditorSceneManager.SaveScene(scene);
}
EditorSceneManager.CloseScene(scene, true);
}
catch (Exception)
{
Debug.LogWarning($"Error opening scene {scenePath}. Skipping...");
}
}
}
}
// Process prefabs in best order
if (processBasePath || scenePrefabPaths.Any())
{
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Processing prefabs", "Sorting prefabs", 0.0f));
if (canceled)
{
return processedAny;
}
}
processedAny = ProcessPrefabs(GetSortedPrefabProcessingPaths(scenePrefabPaths, basePath, processBasePath, ignoreUltimateXRAssets), basePath, ignoreUltimateXRAssets, true, true, componentProcessor, progressUpdater, out canceled, onlyCheck);
if (canceled)
{
return processedAny;
}
}
if (!onlyCheck)
{
AssetDatabase.SaveAssets();
Resources.UnloadUnusedAssets();
}
return processedAny;
}
#endregion
#region Private Methods
///
/// Checks whether a given path should be ignored based on the path and the ignore setting.
///
/// Asset path to check if it should be ignored
/// Whether to ignore components in assets in UltimateXR folders
/// Whether the given path should be ignored
private static bool ShouldIgnoreUltimateXRPath(string assetPath, bool ignoreUltimateXRAssets)
{
return ignoreUltimateXRAssets && PathIsInUltimateXR(assetPath);
}
///
/// Checks whether the given asset path requires processing.
///
/// Base path to process. Null or empty to process all paths
/// Asset path to check if it should be processed
/// Whether to ignore components in assets in UltimateXR folders
/// Whether should be processed
private static bool PathRequiresProcessing(string basePath, string assetPath, bool ignoreUltimateXRAssets)
{
if (ShouldIgnoreUltimateXRPath(assetPath, ignoreUltimateXRAssets))
{
return false;
}
return string.IsNullOrEmpty(basePath) || assetPath.StartsWith(basePath);
}
///
/// Gets a list of prefab paths that should be modified to process all components of a given type. The order ensures
/// that parent prefabs will always come first than the child prefabs. This is useful when processing components in
/// prefabs that depend on the parent prefab value, because each prefab will always have the updated value from the
/// parent when it's its turn to be processed.
///
/// Prefab paths to process
/// Whether to ignore components in assets in UltimateXR folders
/// Component type
/// Sorted prefab path list
private static List GetSortedPrefabProcessingPaths(IEnumerable paths, bool ignoreUltimateXRAssets) where T : Component
{
// Build prefab dependencies
Dictionary> assetDependencies = new Dictionary>();
foreach (string prefabPath in paths)
{
if (!ShouldIgnoreUltimateXRPath(prefabPath, ignoreUltimateXRAssets) && AssetDatabase.GetMainAssetTypeAtPath(prefabPath) == typeof(GameObject) && !assetDependencies.ContainsKey(prefabPath))
{
Dictionary dependencies = new Dictionary(); // (path, count)
GameObject gameObject = AssetDatabase.LoadMainAssetAtPath(prefabPath) as GameObject;
// Get all components of the given type in the prefab
T[] components = gameObject.GetComponentsInChildren(true);
// Build dictionary where we list all asset paths with inner prefabs that this prefab depends on.
foreach (T component in components)
{
if (component != null && GetInnermostNon3DModelPrefabRoot(component, out GameObject prefab, out GameObject prefabInstance, out T componentInPrefab))
{
if (prefab != gameObject)
{
// Not original source, it's coming from an inner prefab. Save dependency.
string assetPath = AssetDatabase.GetAssetPath(prefab);
if (!dependencies.ContainsKey(assetPath))
{
dependencies.Add(assetPath, 1);
}
else
{
dependencies[assetPath]++;
}
}
}
}
assetDependencies.Add(prefabPath, dependencies.Keys.ToList());
}
}
// Recursive method to create the list
void AddDependenciesAndAsset(string assetPath, Dictionary> assetDependencies, ref List sortedAssetList)
{
// Add all asset dependencies first
if (assetDependencies.ContainsKey(assetPath))
{
foreach (string path in assetDependencies[assetPath])
{
if (!sortedAssetList.Contains(path))
{
AddDependenciesAndAsset(path, assetDependencies, ref sortedAssetList);
}
}
}
// Add asset
if (!sortedAssetList.Contains(assetPath))
{
sortedAssetList.Add(assetPath);
}
}
// Create the list using the recursive method
List sortedPathList = new List();
foreach (string prefabPath in assetDependencies.Keys)
{
AddDependenciesAndAsset(prefabPath, assetDependencies, ref sortedPathList);
}
return sortedPathList;
}
///
/// Same as but
/// specifying a list of paths and a base path in the project folder. This method will take care of not processing
/// duplicates if there are prefabs specified in that are also in
/// .
///
/// Paths to process
/// Project base path to process
/// Whether to process all prefabs in
/// Whether to ignore components in assets in UltimateXR folders
/// Component type
/// Sorted prefab path list
private static List GetSortedPrefabProcessingPaths(IEnumerable additionalPaths, string basePath, bool processBasePath, bool ignoreUltimateXRAssets) where T : Component
{
// Filter required paths from all asset paths in project
IEnumerable GetPrefabsToProcess()
{
foreach (string prefabPath in additionalPaths)
{
if (AssetDatabase.GetMainAssetTypeAtPath(prefabPath) == typeof(GameObject) && PathRequiresProcessing(basePath, prefabPath, ignoreUltimateXRAssets))
{
yield return prefabPath;
}
}
if (processBasePath)
{
foreach (string prefabPath in GetAllAssetPathsExceptPackages())
{
if (AssetDatabase.GetMainAssetTypeAtPath(prefabPath) == typeof(GameObject) && PathRequiresProcessing(basePath, prefabPath, ignoreUltimateXRAssets) && !additionalPaths.Contains(prefabPath))
{
yield return prefabPath;
}
}
}
}
return GetSortedPrefabProcessingPaths(GetPrefabsToProcess(), ignoreUltimateXRAssets);
}
///
/// Same as but
/// specifying a base path in the project folder.
///
/// Project base path to process
/// Whether to ignore components in assets in UltimateXR folders
/// Component type
/// Sorted prefab path list
private static List GetSortedPrefabProcessingPaths(string basePath, bool ignoreUltimateXRAssets) where T : Component
{
// Filter required paths from all asset paths in project
IEnumerable GetPrefabsToProcess()
{
foreach (string prefabPath in GetAllAssetPathsExceptPackages())
{
if (AssetDatabase.GetMainAssetTypeAtPath(prefabPath) == typeof(GameObject) && PathRequiresProcessing(basePath, prefabPath, ignoreUltimateXRAssets))
{
yield return prefabPath;
}
}
}
return GetSortedPrefabProcessingPaths(GetPrefabsToProcess(), ignoreUltimateXRAssets);
}
///
/// Processes all prefabs in the given paths.
///
/// List of prefab paths to process
/// The base path that was used to get the prefabs from the list
/// Whether the prefab list was created ignoring UltimateXR assets
///
/// Whether to process components where they are in the original prefab that they were
/// added
///
///
/// Whether to process components where they are not in the original prefab that
/// they were added, but inherited from another prefab instead
///
///
/// The component processor. It will receive the component to process as argument and it requires to return a boolean
/// telling whether the component was modified or not.
/// Here the GameObject that will be given as parameter is the GameObject that has the given component.
///
///
/// Will receive updates of the process so that the information can be fed to a Unity progress bar
///
/// Returns whether the user canceled the operation using the progress updater
///
/// Whether to only check if components should be processed, without making any changes. This
/// can be used to get how many elements would be changed without modifying any data
///
/// The component type
/// True if there was at least one component that was modified
private static bool ProcessPrefabs(List prefabAssetPaths,
string basePath,
bool ignoreUltimateXRAssets,
bool processOriginalValues,
bool processNonOriginalValues,
UxrComponentProcessor componentProcessor,
UxrProgressUpdater progressUpdater,
out bool canceled,
bool onlyCheck = false) where T : Component
{
bool processedAny = false;
canceled = false;
for (int i = 0; i < prefabAssetPaths.Count; ++i)
{
GameObject prefab = AssetDatabase.LoadMainAssetAtPath(prefabAssetPaths[i]) as GameObject;
bool processed = false;
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo("Processing prefabs", $"Prefab {prefab.name}", (float)i / prefabAssetPaths.Count));
if (canceled)
{
return processedAny;
}
}
// Get all components of the given type in the prefab
T[] components = prefab.GetComponentsInChildren(true);
foreach (T component in components)
{
if (component != null && GetInnermostNon3DModelPrefabRoot(component, out GameObject originalPrefab, out GameObject prefabInstance, out T componentInPrefab))
{
// Only process those prefabs that are originally in this prefab and don't come from any prefab above in the hierarchy
bool isOriginalPrefab = originalPrefab == prefab;
bool process = (isOriginalPrefab && processOriginalValues) || (!isOriginalPrefab && processNonOriginalValues);
bool isInnermostPrefabInValidChain = isOriginalPrefab;
T componentInParentPrefab = PrefabUtility.GetCorrespondingObjectFromSource(component);
if (!isInnermostPrefabInValidChain && componentInParentPrefab != null && !PathRequiresProcessing(basePath, AssetDatabase.GetAssetPath(componentInParentPrefab), ignoreUltimateXRAssets))
{
isInnermostPrefabInValidChain = true;
}
if (process && componentProcessor != null && componentProcessor.Invoke(new UxrComponentInfo(component, prefab, isOriginalPrefab, isInnermostPrefabInValidChain), onlyCheck))
{
processedAny = true;
processed = true;
}
}
}
if (processed && !onlyCheck)
{
PrefabUtility.SavePrefabAsset(prefab);
}
}
return processedAny;
}
///
/// Processes all components of a given type in a scene only in gameObjects that are not prefab instances.
///
/// Component type to process
/// Scene to process
///
/// The component processor. It will receive the component to process as argument and it requires to return a boolean
/// telling whether the component was modified or not.
/// Here the GameObject that will be given as parameter is the GameObject that has the given component.
///
///
/// Will receive updates of the process so that the information can be fed to a Unity progress bar
///
/// Returns whether the user canceled the operation using the progress updater
///
/// Whether to only check if components should be processed, without making any changes. This
/// can be used to get how many elements would be changed without modifying any data
///
/// True if there was at least one component that was modified
/// The component type
private static bool ProcessAllNonPrefabInstanceGameObjects(Scene scene,
UxrComponentProcessor componentProcessor,
UxrProgressUpdater progressUpdater,
out bool canceled,
bool onlyCheck = false) where T : Component
{
List components = scene.GetRootGameObjects().SelectMany(go => go.GetComponentsInChildren()).ToList();
bool processedAny = false;
canceled = false;
for (int c = 0; c < components.Count; ++c)
{
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo($"Processing scene {scene.name}", $"Object {components[c].name}", (float)c / components.Count));
if (canceled)
{
return processedAny;
}
}
bool isInstantiatedPrefab = GetInnermostNon3DModelPrefabRoot(components[c], out GameObject prefab, out GameObject prefabInstance, out T componentInPrefab);
// Only process if it's not an instantiated prefab
if (!isInstantiatedPrefab && componentProcessor != null && componentProcessor.Invoke(new UxrComponentInfo(components[c], null, true, true), onlyCheck))
{
processedAny = true;
}
}
return processedAny;
}
///
/// Processes all components of a given type in a scene only in prefab instances whose source prefabs lie outside a
/// specified project base path.
/// In this case, in the
/// will be marked as true.
/// This is useful in component processors that target the innermost prefab only, but when the user specifies a base
/// path that prevents the real innermost prefab from being processed.
///
/// Component type to process
/// Scene to process
///
/// Project base path to process. This will be used to check whether the prefab instances in the
/// scene should be processed. The instances will be processed if their source prefab is not located in the base path
///
///
/// Whether UltimateXR folders are being ignored. In a similar way to
/// , if UltimateXR folders are being ignored, instances will be processed if their source
/// prefab is from UltimateXR directly
///
///
/// The component processor. It will receive the component to process as argument and it requires to return a boolean
/// telling whether the component was modified or not.
/// Here the GameObject that will be given as parameter is the GameObject that has the given component.
///
///
/// Will receive updates of the process so that the information can be fed to a Unity progress bar
///
/// Returns whether the user canceled the operation using the progress updater
///
/// Whether to only check if components should be processed, without making any changes. This
/// can be used to get how many elements would be changed without modifying any data
///
/// True if there was at least one component that was modified
/// The component type
private static bool ProcessAllInnermostPrefabInstanceGameObjects(Scene scene,
string basePath,
bool ignoreUltimateXRAssets,
UxrComponentProcessor componentProcessor,
UxrProgressUpdater progressUpdater,
out bool canceled,
bool onlyCheck = false) where T : Component
{
List components = scene.GetRootGameObjects().SelectMany(go => go.GetComponentsInChildren()).ToList();
bool processedAny = false;
canceled = false;
for (int c = 0; c < components.Count; ++c)
{
if (progressUpdater != null)
{
canceled = progressUpdater.Invoke(new UxrProgressInfo($"Processing scene {scene.name}", $"Object {components[c].name}", (float)c / components.Count));
if (canceled)
{
return processedAny;
}
}
bool isInstantiatedPrefab = GetInnermostNon3DModelPrefabRoot(components[c], out GameObject prefab, out GameObject prefabInstance, out T componentInPrefab);
if (isInstantiatedPrefab)
{
T componentInParentPrefab = PrefabUtility.GetCorrespondingObjectFromSource(components[c]);
// Only process if it's an instantiated prefab whose prefab doesn't meet the path requirements
if (componentInParentPrefab != null && !PathRequiresProcessing(basePath, AssetDatabase.GetAssetPath(componentInParentPrefab), ignoreUltimateXRAssets))
{
if (componentProcessor != null && componentProcessor.Invoke(new UxrComponentInfo(components[c], null, false, true), onlyCheck))
{
processedAny = true;
}
}
}
}
return processedAny;
}
///
/// Gets all the prefab paths from a scene that contain components of the given type.
///
/// Scene to process
/// List where to add the prefab paths found
/// Component type
private static void GetScenePrefabPaths(Scene scene, List paths) where T : Component
{
List components = scene.GetRootGameObjects().SelectMany(go => go.GetComponentsInChildren()).ToList();
foreach (T c in components)
{
T componentInPrefab = PrefabUtility.GetCorrespondingObjectFromSource(c);
while (componentInPrefab != null)
{
string assetPath = AssetDatabase.GetAssetPath(componentInPrefab);
if (!string.IsNullOrEmpty(assetPath) &&
(PrefabUtility.GetPrefabAssetType(componentInPrefab) == PrefabAssetType.Regular || PrefabUtility.GetPrefabAssetType(componentInPrefab) == PrefabAssetType.Variant) &&
!paths.Contains(assetPath))
{
paths.Add(assetPath);
}
componentInPrefab = PrefabUtility.GetCorrespondingObjectFromSource(componentInPrefab);
}
}
}
///
/// Same as AssetDatabase.GetAllAssetPaths() but filtering out assets from packages.
///
/// List of asset paths filtering out assets from packages
private static string[] GetAllAssetPathsExceptPackages()
{
return AssetDatabase.GetAllAssetPaths().Where(p => !p.StartsWith("Packages/")).ToArray();
}
#endregion
}
}