// Shared Methods|Utilities|90060 namespace VRTK { using UnityEngine; using UnityEngine.SceneManagement; #if UNITY_EDITOR using UnityEditor; #endif using System; using System.Collections.Generic; using System.Linq; using System.Reflection; #if UNITY_2017_2_OR_NEWER using UnityEngine.XR; #else using XRSettings = UnityEngine.VR.VRSettings; using XRStats = UnityEngine.VR.VRStats; #endif /// /// The Shared Methods script is a collection of reusable static methods that are used across a range of different scripts. /// public static class VRTK_SharedMethods { /// /// The GetBounds methods returns the bounds of the transform including all children in world space. /// /// /// Resets the rotation of the transform temporarily to 0 to eliminate skewed bounds. /// Does not consider the stated object when calculating the bounds. /// The bounds of the transform. public static Bounds GetBounds(Transform transform, Transform excludeRotation = null, Transform excludeTransform = null) { Quaternion oldRotation = Quaternion.identity; if (excludeRotation != null) { oldRotation = excludeRotation.rotation; excludeRotation.rotation = Quaternion.identity; } bool boundsInitialized = false; Bounds bounds = new Bounds(transform.position, Vector3.zero); Renderer[] renderers = transform.GetComponentsInChildren(); for (int i = 0; i < renderers.Length; i++) { Renderer renderer = renderers[i]; if (excludeTransform != null && renderer.transform.IsChildOf(excludeTransform)) { continue; } // do late initialization in case initial transform does not contain any renderers if (!boundsInitialized) { bounds = new Bounds(renderer.transform.position, Vector3.zero); boundsInitialized = true; } bounds.Encapsulate(renderer.bounds); } if (bounds.size.magnitude == 0) { // do second pass as there were no renderers, this time with colliders BoxCollider[] colliders = transform.GetComponentsInChildren(); for (int i = 0; i < colliders.Length; i++) { BoxCollider collider = colliders[i]; if (excludeTransform != null && collider.transform.IsChildOf(excludeTransform)) { continue; } // do late initialization in case initial transform does not contain any colliders if (!boundsInitialized) { bounds = new Bounds(collider.transform.position, Vector3.zero); boundsInitialized = true; } bounds.Encapsulate(collider.bounds); } } if (excludeRotation != null) { excludeRotation.rotation = oldRotation; } return bounds; } /// /// The IsLowest method checks to see if the given value is the lowest number in the given array of values. /// /// The value to check to see if it is lowest. /// The array of values to check against. /// Returns true if the value is lower than all numbers in the given array, returns false if it is not the lowest. public static bool IsLowest(float value, float[] others) { for (int i = 0; i < others.Length; i++) { if (others[i] <= value) { return false; } } return true; } /// /// The AddCameraFade method finds the headset camera and adds a headset fade script to it. /// /// The transform of the headset camera. public static Transform AddCameraFade() { Transform camera = VRTK_DeviceFinder.HeadsetCamera(); VRTK_SDK_Bridge.AddHeadsetFade(camera); return camera; } /// /// The CreateColliders method attempts to add box colliders to all child objects in the given object that have a renderer but no collider. /// /// The game object to attempt to add the colliders to. public static void CreateColliders(GameObject obj) { Renderer[] renderers = obj.GetComponentsInChildren(); for (int i = 0; i < renderers.Length; i++) { Renderer renderer = renderers[i]; if (renderer.gameObject.GetComponent() == null) { renderer.gameObject.AddComponent(); } } } /// /// The ColliderExclude method reduces the colliders in the setA array by those matched in the setB array. /// /// The array that contains all of the relevant colliders. /// The array that contains the colliders to remove from setA. /// A Collider array that is a subset of setA that doesn't contain the colliders from setB. public static Collider[] ColliderExclude(Collider[] setA, Collider[] setB) { return setA.Except(setB).ToArray(); } /// /// The GetCollidersInGameObjects method iterates through a GameObject array and returns all of the unique found colliders for all GameObejcts. /// /// An array of GameObjects to get the colliders for. /// If this is `true` then the given GameObjects will also have their child GameObjects searched for colliders. /// If this is `true` then the inactive GameObjects in the array will also be checked for Colliders. Only relevant if `searchChildren` is `true`. /// An array of Colliders that are found in the given GameObject array. public static Collider[] GetCollidersInGameObjects(GameObject[] gameObjects, bool searchChildren, bool includeInactive) { HashSet foundColliders = new HashSet(); for (int i = 0; i < gameObjects.Length; i++) { Collider[] gameObjectColliders = (searchChildren ? gameObjects[i].GetComponentsInChildren(includeInactive) : gameObjects[i].GetComponents()); for (int j = 0; j < gameObjectColliders.Length; j++) { foundColliders.Add(gameObjectColliders[j]); } } return foundColliders.ToArray(); } /// /// The CloneComponent method takes a source component and copies it to the given destination game object. /// /// The component to copy. /// The game object to copy the component to. /// Determines whether the properties of the component as well as the fields should be copied. /// The component that has been cloned onto the given game object. public static Component CloneComponent(Component source, GameObject destination, bool copyProperties = false) { Component tmpComponent = destination.gameObject.AddComponent(source.GetType()); if (copyProperties) { PropertyInfo[] foundProperties = source.GetType().GetProperties(); for (int i = 0; i < foundProperties.Length; i++) { PropertyInfo foundProperty = foundProperties[i]; if (foundProperty.CanWrite) { foundProperty.SetValue(tmpComponent, foundProperty.GetValue(source, null), null); } } } FieldInfo[] foundFields = source.GetType().GetFields(); for (int i = 0; i < foundFields.Length; i++) { FieldInfo foundField = foundFields[i]; foundField.SetValue(tmpComponent, foundField.GetValue(source)); } return tmpComponent; } /// /// The ColorDarken method takes a given colour and darkens it by the given percentage. /// /// The source colour to apply the darken to. /// The percent to darken the colour by. /// The new colour with the darken applied. public static Color ColorDarken(Color color, float percent) { return new Color(NumberPercent(color.r, percent), NumberPercent(color.g, percent), NumberPercent(color.b, percent), color.a); } /// /// The RoundFloat method is used to round a given float to the given decimal places. /// /// The float to round. /// The number of decimal places to round to. /// If this is true then the decimal places must be given in the decimal multiplier, e.g. 10 for 1dp, 100 for 2dp, etc. /// The rounded float. public static float RoundFloat(float givenFloat, int decimalPlaces, bool rawFidelity = false) { float roundBy = (rawFidelity ? decimalPlaces : Mathf.Pow(10.0f, decimalPlaces)); return Mathf.Round(givenFloat * roundBy) / roundBy; } /// /// The IsEditTime method determines if the state of Unity is in the Unity Editor and the scene is not in play mode. /// /// Returns true if Unity is in the Unity Editor and not in play mode. public static bool IsEditTime() { #if UNITY_EDITOR return !EditorApplication.isPlayingOrWillChangePlaymode; #else return false; #endif } /// /// The Mod method is used to find the remainder of the sum a/b. /// /// The dividend value. /// The divisor value. /// The remainder value. public static float Mod(float a, float b) { return a - b * Mathf.Floor(a / b); } /// /// Finds the first GameObject with a given name and an ancestor that has a specific component. /// /// /// This method returns active as well as inactive GameObjects in all scenes. It doesn't return assets. /// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead. /// /// The component type that needs to be on an ancestor of the wanted GameObject. Must be a subclass of `Component`. /// The name of the wanted GameObject. If it contains a '/' character, this method traverses the hierarchy like a path name, beginning on the game object that has a component of type `T`. /// If this is true, all loaded scenes will be searched. If this is false, only the active scene will be searched. /// The GameObject with name `gameObjectName` and an ancestor that has a `T`. If no such GameObject is found then `null` is returned. public static GameObject FindEvenInactiveGameObject(string gameObjectName = null, bool searchAllScenes = false) where T : Component { if (string.IsNullOrEmpty(gameObjectName)) { T foundComponent = FindEvenInactiveComponentsInValidScenes(searchAllScenes, true).FirstOrDefault(); return foundComponent == null ? null : foundComponent.gameObject; } return FindEvenInactiveComponentsInValidScenes(searchAllScenes) .Select(component => { Transform transform = component.gameObject.transform.Find(gameObjectName); return transform == null ? null : transform.gameObject; }) .FirstOrDefault(gameObject => gameObject != null); } /// /// Finds all components of a given type. /// /// /// This method returns components from active as well as inactive GameObjects in all scenes. It doesn't return assets. /// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead. /// /// The component type to search for. Must be a subclass of `Component`. /// If this is true, all loaded scenes will be searched. If this is false, only the active scene will be searched. /// All the found components. If no component is found an empty array is returned. public static T[] FindEvenInactiveComponents(bool searchAllScenes = false) where T : Component { IEnumerable results = FindEvenInactiveComponentsInValidScenes(searchAllScenes); return results.ToArray(); } /// /// Finds the first component of a given type. /// /// /// This method returns components from active as well as inactive GameObjects in all scenes. It doesn't return assets. /// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead. /// /// The component type to search for. Must be a subclass of `Component`. /// If this is true, all loaded scenes will be searched. If this is false, only the active scene will be searched. /// The found component. If no component is found `null` is returned. public static T FindEvenInactiveComponent(bool searchAllScenes = false) where T : Component { return FindEvenInactiveComponentsInValidScenes(searchAllScenes, true).FirstOrDefault(); } /// /// The GenerateVRTKObjectName method is used to create a standard name string for any VRTK generated object. /// /// An additiona [AUTOGEN] prefix will be added if this is true. /// A collection of parameters to add to the generated name. /// The generated name string. public static string GenerateVRTKObjectName(bool autoGen, params object[] replacements) { string toFormat = "[VRTK]"; if (autoGen) { toFormat += "[AUTOGEN]"; } for (int i = 0; i < replacements.Length; i++) { toFormat += "[{" + i + "}]"; } return string.Format(toFormat, replacements); } /// /// The GetGPUTimeLastFrame retrieves the time spent by the GPU last frame, in seconds, as reported by the VR SDK. /// /// The total GPU time utilized last frame as measured by the VR subsystem. public static float GetGPUTimeLastFrame() { #if UNITY_5_6_OR_NEWER float gpuTimeLastFrame; return (XRStats.TryGetGPUTimeLastFrame(out gpuTimeLastFrame) ? gpuTimeLastFrame : 0f); #else return XRStats.gpuTimeLastFrame; #endif } /// /// The Vector2ShallowCompare method compares two given Vector2 objects based on the given fidelity, which is the equivalent of comparing rounded Vector2 elements to determine if the Vector2 elements are equal. /// /// The Vector2 to compare against. /// The Vector2 to compare with /// The number of decimal places to use when doing the comparison on the float elements within the Vector2. /// Returns `true` if the given Vector2 objects match based on the given fidelity. public static bool Vector2ShallowCompare(Vector2 vectorA, Vector2 vectorB, int compareFidelity) { Vector2 distanceVector = vectorA - vectorB; return (Math.Round(Mathf.Abs(distanceVector.x), compareFidelity, MidpointRounding.AwayFromZero) < float.Epsilon && Math.Round(Mathf.Abs(distanceVector.y), compareFidelity, MidpointRounding.AwayFromZero) < float.Epsilon); } /// /// The Vector3ShallowCompare method compares two given Vector3 objects based on the given threshold, which is the equavelent of checking the distance between two Vector3 objects are above the threshold distance. /// /// The Vector3 to compare against. /// The Vector3 to compare with /// The distance in which the two Vector3 objects can be within to be considered true /// Returns `true` if the given Vector3 objects are within the given threshold distance. public static bool Vector3ShallowCompare(Vector3 vectorA, Vector3 vectorB, float threshold) { return (Vector3.Distance(vectorA, vectorB) < threshold); } /// /// The NumberPercent method is used to determine the percentage of a given value. /// /// The value to determine the percentage from /// The percentage to find within the given value. /// A float containing the percentage value based on the given input. public static float NumberPercent(float value, float percent) { percent = Mathf.Clamp(percent, 0f, 100f); return (percent == 0f ? value : (value - (percent / 100f))); } /// /// The SetGlobalScale method is used to set a transform scale based on a global scale instead of a local scale. /// /// The reference to the transform to scale. /// A Vector3 of a global scale to apply to the given transform. public static void SetGlobalScale(this Transform transform, Vector3 globalScale) { transform.localScale = Vector3.one; transform.localScale = new Vector3(globalScale.x / transform.lossyScale.x, globalScale.y / transform.lossyScale.y, globalScale.z / transform.lossyScale.z); } /// /// The VectorHeading method calculates the current heading of the target position in relation to the origin position. /// /// The point to use as the originating position for the heading calculation. /// The point to use as the target position for the heading calculation. /// A Vector3 containing the heading changes of the target position in relation to the origin position. public static Vector3 VectorHeading(Vector3 originPosition, Vector3 targetPosition) { return targetPosition - originPosition; } /// /// The VectorDirection method calculates the direction the target position is in relation to the origin position. /// /// The point to use as the originating position for the direction calculation. /// The point to use as the target position for the direction calculation. /// A Vector3 containing the direction of the target position in relation to the origin position. public static Vector3 VectorDirection(Vector3 originPosition, Vector3 targetPosition) { Vector3 heading = VectorHeading(originPosition, targetPosition); return heading * DividerToMultiplier(heading.magnitude); } /// /// The DividerToMultiplier method takes a number to be used in a division and converts it to be used for multiplication. (e.g. 2 / 2 becomes 2 * 0.5) /// /// The number to convert into the multplier value. /// The calculated number that can replace the divider number in a multiplication sum. public static float DividerToMultiplier(float value) { return (value != 0f ? 1f / value : 1f); } /// /// The NormalizeValue method takes a given value between a specified range and returns the normalized value between 0f and 1f. /// /// The actual value to normalize. /// The minimum value the actual value can be. /// The maximum value the actual value can be. /// The threshold to force to the minimum or maximum value if the normalized value is within the threhold limits. /// public static float NormalizeValue(float value, float minValue, float maxValue, float threshold = 0f) { float normalizedMax = maxValue - minValue; float normalizedValue = normalizedMax - (maxValue - value); float result = normalizedValue * DividerToMultiplier(normalizedMax); ; result = (result < threshold ? 0f : result); result = (result > 1f - threshold ? 1f : result); return Mathf.Clamp(result, 0f, 1f); } /// /// The AxisDirection method returns the relevant direction Vector3 based on the axis index in relation to x,y,z. /// /// The axis index of the axis. `0 = x` `1 = y` `2 = z` /// An optional Transform to get the Axis Direction for. If this is `null` then the World directions will be used. /// The direction Vector3 based on the given axis index. public static Vector3 AxisDirection(int axisIndex, Transform givenTransform = null) { Vector3[] worldDirections = (givenTransform != null ? new Vector3[] { givenTransform.right, givenTransform.up, givenTransform.forward } : new Vector3[] { Vector3.right, Vector3.up, Vector3.forward }); return worldDirections[(int)Mathf.Clamp(axisIndex, 0f, worldDirections.Length)]; } /// /// The AddListValue method adds the given value to the given list. If `preventDuplicates` is `true` then the given value will only be added if it doesn't already exist in the given list. /// /// The datatype for the list value. /// The list to retrieve the value from. /// The value to attempt to add to the list. /// If this is `false` then the value provided will always be appended to the list. If this is `true` the value provided will only be added to the list if it doesn't already exist. /// Returns `true` if the given value was successfully added to the list. Returns `false` if the given value already existed in the list and `preventDuplicates` is `true`. public static bool AddListValue(List list, TValue value, bool preventDuplicates = false) { if (list != null && (!preventDuplicates || !list.Contains(value))) { list.Add(value); return true; } return false; } /// /// The GetDictionaryValue method attempts to retrieve a value from a given dictionary for the given key. It removes the need for a double dictionary lookup to ensure the key is valid and has the option of also setting the missing key value to ensure the dictionary entry is valid. /// /// The datatype for the dictionary key. /// The datatype for the dictionary value. /// The dictionary to retrieve the value from. /// The key to retrieve the value for. /// The value to utilise when either setting the missing key (if `setMissingKey` is `true`) or the default value to return when no key is found (if `setMissingKey` is `false`). /// If this is `true` and the given key is not present, then the dictionary value for the given key will be set to the `defaultValue` parameter. If this is `false` and the given key is not present then the `defaultValue` parameter will be returned as the value. /// The found value for the given key in the given dictionary, or the default value if no key is found. public static TValue GetDictionaryValue(Dictionary dictionary, TKey key, TValue defaultValue = default(TValue), bool setMissingKey = false) { bool keyExists; return GetDictionaryValue(dictionary, key, out keyExists, defaultValue, setMissingKey); } /// /// The GetDictionaryValue method attempts to retrieve a value from a given dictionary for the given key. It removes the need for a double dictionary lookup to ensure the key is valid and has the option of also setting the missing key value to ensure the dictionary entry is valid. /// /// The datatype for the dictionary key. /// The datatype for the dictionary value. /// The dictionary to retrieve the value from. /// The key to retrieve the value for. /// Sets the given parameter to `true` if the key exists in the given dictionary or sets to `false` if the key didn't existing in the given dictionary. /// The value to utilise when either setting the missing key (if `setMissingKey` is `true`) or the default value to return when no key is found (if `setMissingKey` is `false`). /// If this is `true` and the given key is not present, then the dictionary value for the given key will be set to the `defaultValue` parameter. If this is `false` and the given key is not present then the `defaultValue` parameter will be returned as the value. /// The found value for the given key in the given dictionary, or the default value if no key is found. public static TValue GetDictionaryValue(Dictionary dictionary, TKey key, out bool keyExists, TValue defaultValue = default(TValue), bool setMissingKey = false) { keyExists = false; if (dictionary == null) { return defaultValue; } TValue outputValue; if (dictionary.TryGetValue(key, out outputValue)) { keyExists = true; } else { if (setMissingKey) { dictionary.Add(key, defaultValue); } outputValue = defaultValue; } return outputValue; } /// /// The AddDictionaryValue method attempts to add a value for the given key in the given dictionary if the key does not already exist. If `overwriteExisting` is `true` then it always set the value even if they key exists. /// /// The datatype for the dictionary key. /// The datatype for the dictionary value. /// The dictionary to set the value for. /// The key to set the value for. /// The value to set at the given key in the given dictionary. /// If this is `true` then the value for the given key will always be set to the provided value. If this is `false` then the value for the given key will only be set if the given key is not found in the given dictionary. /// Returns `true` if the given value was successfully added to the dictionary at the given key. Returns `false` if the given key already existed in the dictionary and `overwriteExisting` is `false`. public static bool AddDictionaryValue(Dictionary dictionary, TKey key, TValue value, bool overwriteExisting = false) { if (dictionary != null) { if (overwriteExisting) { dictionary[key] = value; return true; } else { bool keyExists; GetDictionaryValue(dictionary, key, out keyExists, value, true); return !keyExists; } } return false; } /// /// The GetTypeUnknownAssembly method is used to find a Type without knowing the exact assembly it is in. /// /// The name of the type to get. /// The Type, or null if none is found. public static Type GetTypeUnknownAssembly(string typeName) { Type type = Type.GetType(typeName); if (type != null) { return type; } #if !UNITY_WSA Assembly[] foundAssemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < foundAssemblies.Length; i++) { type = foundAssemblies[i].GetType(typeName); if (type != null) { return type; } } #endif return null; } /// /// The GetEyeTextureResolutionScale method returns the render scale for the resolution. /// /// Returns a float with the render scale for the resolution. public static float GetEyeTextureResolutionScale() { #if UNITY_2017_2_OR_NEWER return XRSettings.eyeTextureResolutionScale; #else return XRSettings.renderScale; #endif } /// /// The SetEyeTextureResolutionScale method sets the render scale for the resolution. /// /// The value to set the render scale to. public static void SetEyeTextureResolutionScale(float value) { #if UNITY_2017_2_OR_NEWER XRSettings.eyeTextureResolutionScale = value; #else XRSettings.renderScale = value; #endif } /// /// The IsTypeSubclassOf checks if a given Type is a subclass of another given Type. /// /// The Type to check. /// The base Type to check. /// Returns `true` if the given type is a subclass of the given base type. public static bool IsTypeSubclassOf(Type givenType, Type givenBaseType) { #if UNITY_WSA && !UNITY_EDITOR return (givenType.GetTypeInfo().IsSubclassOf(givenBaseType)); #else return (givenType.IsSubclassOf(givenBaseType)); #endif } /// /// The GetTypeCustomAttributes method gets the custom attributes of a given type. /// /// The type to get the custom attributes for. /// The attribute type. /// Whether to inherit attributes. /// Returns an object array of custom attributes. public static object[] GetTypeCustomAttributes(Type givenType, Type attributeType, bool inherit) { #if UNITY_WSA && !UNITY_EDITOR return ((object[])givenType.GetTypeInfo().GetCustomAttributes(attributeType, inherit)); #else return (givenType.GetCustomAttributes(attributeType, inherit)); #endif } /// /// The GetBaseType method returns the base Type for the given Type. /// /// The type to return the base Type for. /// Returns the base Type. public static Type GetBaseType(Type givenType) { #if UNITY_WSA && !UNITY_EDITOR return (givenType.GetTypeInfo().BaseType); #else return (givenType.BaseType); #endif } /// /// The IsTypeAssignableFrom method determines if the given Type is assignable from the source Type. /// /// The Type to check on. /// The Type to check if the given Type is assignable from. /// Returns `true` if the given Type is assignable from the source Type. public static bool IsTypeAssignableFrom(Type givenType, Type sourceType) { #if UNITY_WSA && !UNITY_EDITOR return (givenType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo())); #else return (givenType.IsAssignableFrom(sourceType)); #endif } /// /// The GetNestedType method returns the nested Type of the given Type. /// /// The Type to check on. /// The name of the nested Type. /// Returns the nested Type. public static Type GetNestedType(Type givenType, string name) { #if UNITY_WSA && !UNITY_EDITOR return (givenType.GetTypeInfo().GetDeclaredNestedType(name).GetType()); #else return (givenType.GetNestedType(name)); #endif } /// /// The GetPropertyFirstName method returns the string name of the first property on a given Type. /// /// The type to check the first property on. /// Returns a string representation of the first property name for the given Type. public static string GetPropertyFirstName() { #if UNITY_WSA && !UNITY_EDITOR return (typeof(T).GetTypeInfo().DeclaredProperties.First().Name); #else return (typeof(T).GetProperties()[0].Name); #endif } /// /// The GetCommandLineArguements method returns the command line arguements for the environment. /// /// Returns an array of command line arguements as strings. public static string[] GetCommandLineArguements() { #if UNITY_WSA && !UNITY_EDITOR return new string[0]; #else return Environment.GetCommandLineArgs(); #endif } /// /// The GetTypesOfType method returns an array of Types for the given Type. /// /// The Type to check on. /// An array of Types found. public static Type[] GetTypesOfType(Type givenType) { #if UNITY_WSA && !UNITY_EDITOR return givenType.GetTypeInfo().Assembly.GetTypes(); #else return givenType.Assembly.GetTypes(); #endif } /// /// The GetExportedTypesOfType method returns an array of Exported Types for the given Type. /// /// The Type to check on. /// An array of Exported Types found. public static Type[] GetExportedTypesOfType(Type givenType) { #if UNITY_WSA && !UNITY_EDITOR return givenType.GetTypeInfo().Assembly.GetExportedTypes(); #else return givenType.Assembly.GetExportedTypes(); #endif } /// /// The IsTypeAbstract method determines if a given Type is abstract. /// /// The Type to check on. /// Returns `true` if the given type is abstract. public static bool IsTypeAbstract(Type givenType) { #if UNITY_WSA && !UNITY_EDITOR return givenType.GetTypeInfo().IsAbstract; #else return givenType.IsAbstract; #endif } #if UNITY_EDITOR public static BuildTargetGroup[] GetValidBuildTargetGroups() { return Enum.GetValues(typeof(BuildTargetGroup)).Cast().Where(group => { if (group == BuildTargetGroup.Unknown) { return false; } string targetGroupName = Enum.GetName(typeof(BuildTargetGroup), group); FieldInfo targetGroupFieldInfo = typeof(BuildTargetGroup).GetField(targetGroupName, BindingFlags.Public | BindingFlags.Static); bool validReturn = (targetGroupFieldInfo != null && targetGroupFieldInfo.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length == 0); #if UNITY_WSA if (targetGroupName == "Metro" || targetGroupName == "WSA") { validReturn = (targetGroupFieldInfo != null); } #endif return validReturn; }).ToArray(); } #endif /// /// The FindEvenInactiveComponentsInLoadedScenes method searches active and inactive game objects in all /// loaded scenes for components matching the type supplied. /// /// If true, will search all loaded scenes, otherwise just the active scene. /// If true, will stop searching objects as soon as a match is found. /// private static IEnumerable FindEvenInactiveComponentsInValidScenes(bool searchAllScenes, bool stopOnMatch = false) where T : Component { IEnumerable results; if (searchAllScenes) { List allSceneResults = new List(); for (int sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++) { allSceneResults.AddRange(FindEvenInactiveComponentsInScene(SceneManager.GetSceneAt(sceneIndex), stopOnMatch)); } results = allSceneResults; } else { results = FindEvenInactiveComponentsInScene(SceneManager.GetActiveScene(), stopOnMatch); } return results; } /// /// The FIndEvenInactiveComponentsInScene method searches the specified scene for components matching the type supplied. /// /// The scene to search. This scene must be valid, either loaded or loading. /// If true, will stop searching objects as soon as a match is found. /// private static IEnumerable FindEvenInactiveComponentsInScene(Scene scene, bool stopOnMatch = false) { List results = new List(); if(!scene.isLoaded) { return results; } foreach (GameObject rootObject in scene.GetRootGameObjects()) { if (stopOnMatch) { T foundComponent = rootObject.GetComponentInChildren(true); if (foundComponent != null) { results.Add(foundComponent); return results; } } else { results.AddRange(rootObject.GetComponentsInChildren(true)); } } return results; } } }