|
// 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
|
|
|
|
/// <summary>
|
|
/// The Shared Methods script is a collection of reusable static methods that are used across a range of different scripts.
|
|
/// </summary>
|
|
public static class VRTK_SharedMethods
|
|
{
|
|
/// <summary>
|
|
/// The GetBounds methods returns the bounds of the transform including all children in world space.
|
|
/// </summary>
|
|
/// <param name="transform"></param>
|
|
/// <param name="excludeRotation">Resets the rotation of the transform temporarily to 0 to eliminate skewed bounds.</param>
|
|
/// <param name="excludeTransform">Does not consider the stated object when calculating the bounds.</param>
|
|
/// <returns>The bounds of the transform.</returns>
|
|
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<Renderer>();
|
|
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<BoxCollider>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The IsLowest method checks to see if the given value is the lowest number in the given array of values.
|
|
/// </summary>
|
|
/// <param name="value">The value to check to see if it is lowest.</param>
|
|
/// <param name="others">The array of values to check against.</param>
|
|
/// <returns>Returns true if the value is lower than all numbers in the given array, returns false if it is not the lowest.</returns>
|
|
public static bool IsLowest(float value, float[] others)
|
|
{
|
|
for (int i = 0; i < others.Length; i++)
|
|
{
|
|
if (others[i] <= value)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The AddCameraFade method finds the headset camera and adds a headset fade script to it.
|
|
/// </summary>
|
|
/// <returns>The transform of the headset camera.</returns>
|
|
public static Transform AddCameraFade()
|
|
{
|
|
Transform camera = VRTK_DeviceFinder.HeadsetCamera();
|
|
VRTK_SDK_Bridge.AddHeadsetFade(camera);
|
|
return camera;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The CreateColliders method attempts to add box colliders to all child objects in the given object that have a renderer but no collider.
|
|
/// </summary>
|
|
/// <param name="obj">The game object to attempt to add the colliders to.</param>
|
|
public static void CreateColliders(GameObject obj)
|
|
{
|
|
Renderer[] renderers = obj.GetComponentsInChildren<Renderer>();
|
|
for (int i = 0; i < renderers.Length; i++)
|
|
{
|
|
Renderer renderer = renderers[i];
|
|
if (renderer.gameObject.GetComponent<Collider>() == null)
|
|
{
|
|
renderer.gameObject.AddComponent<BoxCollider>();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The ColliderExclude method reduces the colliders in the setA array by those matched in the setB array.
|
|
/// </summary>
|
|
/// <param name="setA">The array that contains all of the relevant colliders.</param>
|
|
/// <param name="setB">The array that contains the colliders to remove from setA.</param>
|
|
/// <returns>A Collider array that is a subset of setA that doesn't contain the colliders from setB.</returns>
|
|
public static Collider[] ColliderExclude(Collider[] setA, Collider[] setB)
|
|
{
|
|
return setA.Except(setB).ToArray<Collider>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetCollidersInGameObjects method iterates through a GameObject array and returns all of the unique found colliders for all GameObejcts.
|
|
/// </summary>
|
|
/// <param name="gameObjects">An array of GameObjects to get the colliders for.</param>
|
|
/// <param name="searchChildren">If this is `true` then the given GameObjects will also have their child GameObjects searched for colliders.</param>
|
|
/// <param name="includeInactive">If this is `true` then the inactive GameObjects in the array will also be checked for Colliders. Only relevant if `searchChildren` is `true`.</param>
|
|
/// <returns>An array of Colliders that are found in the given GameObject array.</returns>
|
|
public static Collider[] GetCollidersInGameObjects(GameObject[] gameObjects, bool searchChildren, bool includeInactive)
|
|
{
|
|
HashSet<Collider> foundColliders = new HashSet<Collider>();
|
|
for (int i = 0; i < gameObjects.Length; i++)
|
|
{
|
|
Collider[] gameObjectColliders = (searchChildren ? gameObjects[i].GetComponentsInChildren<Collider>(includeInactive) : gameObjects[i].GetComponents<Collider>());
|
|
for (int j = 0; j < gameObjectColliders.Length; j++)
|
|
{
|
|
foundColliders.Add(gameObjectColliders[j]);
|
|
}
|
|
}
|
|
return foundColliders.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The CloneComponent method takes a source component and copies it to the given destination game object.
|
|
/// </summary>
|
|
/// <param name="source">The component to copy.</param>
|
|
/// <param name="destination">The game object to copy the component to.</param>
|
|
/// <param name="copyProperties">Determines whether the properties of the component as well as the fields should be copied.</param>
|
|
/// <returns>The component that has been cloned onto the given game object.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The ColorDarken method takes a given colour and darkens it by the given percentage.
|
|
/// </summary>
|
|
/// <param name="color">The source colour to apply the darken to.</param>
|
|
/// <param name="percent">The percent to darken the colour by.</param>
|
|
/// <returns>The new colour with the darken applied.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The RoundFloat method is used to round a given float to the given decimal places.
|
|
/// </summary>
|
|
/// <param name="givenFloat">The float to round.</param>
|
|
/// <param name="decimalPlaces">The number of decimal places to round to.</param>
|
|
/// <param name="rawFidelity">If this is true then the decimal places must be given in the decimal multiplier, e.g. 10 for 1dp, 100 for 2dp, etc.</param>
|
|
/// <returns>The rounded float.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The IsEditTime method determines if the state of Unity is in the Unity Editor and the scene is not in play mode.
|
|
/// </summary>
|
|
/// <returns>Returns true if Unity is in the Unity Editor and not in play mode.</returns>
|
|
public static bool IsEditTime()
|
|
{
|
|
#if UNITY_EDITOR
|
|
return !EditorApplication.isPlayingOrWillChangePlaymode;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The Mod method is used to find the remainder of the sum a/b.
|
|
/// </summary>
|
|
/// <param name="a">The dividend value.</param>
|
|
/// <param name="b">The divisor value.</param>
|
|
/// <returns>The remainder value.</returns>
|
|
public static float Mod(float a, float b)
|
|
{
|
|
return a - b * Mathf.Floor(a / b);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the first GameObject with a given name and an ancestor that has a specific component.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <typeparam name="T">The component type that needs to be on an ancestor of the wanted GameObject. Must be a subclass of `Component`.</typeparam>
|
|
/// <param name="gameObjectName">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`.</param>
|
|
/// <param name="searchAllScenes">If this is true, all loaded scenes will be searched. If this is false, only the active scene will be searched.</param>
|
|
/// <returns>The GameObject with name `gameObjectName` and an ancestor that has a `T`. If no such GameObject is found then `null` is returned.</returns>
|
|
public static GameObject FindEvenInactiveGameObject<T>(string gameObjectName = null, bool searchAllScenes = false) where T : Component
|
|
{
|
|
if (string.IsNullOrEmpty(gameObjectName))
|
|
{
|
|
T foundComponent = FindEvenInactiveComponentsInValidScenes<T>(searchAllScenes, true).FirstOrDefault();
|
|
return foundComponent == null ? null : foundComponent.gameObject;
|
|
}
|
|
|
|
return FindEvenInactiveComponentsInValidScenes<T>(searchAllScenes)
|
|
.Select(component =>
|
|
{
|
|
Transform transform = component.gameObject.transform.Find(gameObjectName);
|
|
return transform == null ? null : transform.gameObject;
|
|
})
|
|
.FirstOrDefault(gameObject => gameObject != null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds all components of a given type.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <typeparam name="T">The component type to search for. Must be a subclass of `Component`.</typeparam>
|
|
/// <param name="searchAllScenes">If this is true, all loaded scenes will be searched. If this is false, only the active scene will be searched.</param>
|
|
/// <returns>All the found components. If no component is found an empty array is returned.</returns>
|
|
public static T[] FindEvenInactiveComponents<T>(bool searchAllScenes = false) where T : Component
|
|
{
|
|
IEnumerable<T> results = FindEvenInactiveComponentsInValidScenes<T>(searchAllScenes);
|
|
return results.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the first component of a given type.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <typeparam name="T">The component type to search for. Must be a subclass of `Component`.</typeparam>
|
|
/// <param name="searchAllScenes">If this is true, all loaded scenes will be searched. If this is false, only the active scene will be searched.</param>
|
|
/// <returns>The found component. If no component is found `null` is returned.</returns>
|
|
public static T FindEvenInactiveComponent<T>(bool searchAllScenes = false) where T : Component
|
|
{
|
|
return FindEvenInactiveComponentsInValidScenes<T>(searchAllScenes, true).FirstOrDefault();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GenerateVRTKObjectName method is used to create a standard name string for any VRTK generated object.
|
|
/// </summary>
|
|
/// <param name="autoGen">An additiona [AUTOGEN] prefix will be added if this is true.</param>
|
|
/// <param name="replacements">A collection of parameters to add to the generated name.</param>
|
|
/// <returns>The generated name string.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetGPUTimeLastFrame retrieves the time spent by the GPU last frame, in seconds, as reported by the VR SDK.
|
|
/// </summary>
|
|
/// <returns>The total GPU time utilized last frame as measured by the VR subsystem.</returns>
|
|
public static float GetGPUTimeLastFrame()
|
|
{
|
|
#if UNITY_5_6_OR_NEWER
|
|
float gpuTimeLastFrame;
|
|
return (XRStats.TryGetGPUTimeLastFrame(out gpuTimeLastFrame) ? gpuTimeLastFrame : 0f);
|
|
#else
|
|
return XRStats.gpuTimeLastFrame;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="vectorA">The Vector2 to compare against.</param>
|
|
/// <param name="vectorB">The Vector2 to compare with</param>
|
|
/// <param name="compareFidelity">The number of decimal places to use when doing the comparison on the float elements within the Vector2.</param>
|
|
/// <returns>Returns `true` if the given Vector2 objects match based on the given fidelity.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="vectorA">The Vector3 to compare against.</param>
|
|
/// <param name="vectorB">The Vector3 to compare with</param>
|
|
/// <param name="threshold">The distance in which the two Vector3 objects can be within to be considered true</param>
|
|
/// <returns>Returns `true` if the given Vector3 objects are within the given threshold distance.</returns>
|
|
public static bool Vector3ShallowCompare(Vector3 vectorA, Vector3 vectorB, float threshold)
|
|
{
|
|
return (Vector3.Distance(vectorA, vectorB) < threshold);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The NumberPercent method is used to determine the percentage of a given value.
|
|
/// </summary>
|
|
/// <param name="value">The value to determine the percentage from</param>
|
|
/// <param name="percent">The percentage to find within the given value.</param>
|
|
/// <returns>A float containing the percentage value based on the given input.</returns>
|
|
public static float NumberPercent(float value, float percent)
|
|
{
|
|
percent = Mathf.Clamp(percent, 0f, 100f);
|
|
return (percent == 0f ? value : (value - (percent / 100f)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The SetGlobalScale method is used to set a transform scale based on a global scale instead of a local scale.
|
|
/// </summary>
|
|
/// <param name="transform">The reference to the transform to scale.</param>
|
|
/// <param name="globalScale">A Vector3 of a global scale to apply to the given transform.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The VectorHeading method calculates the current heading of the target position in relation to the origin position.
|
|
/// </summary>
|
|
/// <param name="originPosition">The point to use as the originating position for the heading calculation.</param>
|
|
/// <param name="targetPosition">The point to use as the target position for the heading calculation.</param>
|
|
/// <returns>A Vector3 containing the heading changes of the target position in relation to the origin position.</returns>
|
|
public static Vector3 VectorHeading(Vector3 originPosition, Vector3 targetPosition)
|
|
{
|
|
return targetPosition - originPosition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The VectorDirection method calculates the direction the target position is in relation to the origin position.
|
|
/// </summary>
|
|
/// <param name="originPosition">The point to use as the originating position for the direction calculation.</param>
|
|
/// <param name="targetPosition">The point to use as the target position for the direction calculation.</param>
|
|
/// <returns>A Vector3 containing the direction of the target position in relation to the origin position.</returns>
|
|
public static Vector3 VectorDirection(Vector3 originPosition, Vector3 targetPosition)
|
|
{
|
|
Vector3 heading = VectorHeading(originPosition, targetPosition);
|
|
return heading * DividerToMultiplier(heading.magnitude);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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)
|
|
/// </summary>
|
|
/// <param name="value">The number to convert into the multplier value.</param>
|
|
/// <returns>The calculated number that can replace the divider number in a multiplication sum.</returns>
|
|
public static float DividerToMultiplier(float value)
|
|
{
|
|
return (value != 0f ? 1f / value : 1f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The NormalizeValue method takes a given value between a specified range and returns the normalized value between 0f and 1f.
|
|
/// </summary>
|
|
/// <param name="value">The actual value to normalize.</param>
|
|
/// <param name="minValue">The minimum value the actual value can be.</param>
|
|
/// <param name="maxValue">The maximum value the actual value can be.</param>
|
|
/// <param name="threshold">The threshold to force to the minimum or maximum value if the normalized value is within the threhold limits.</param>
|
|
/// <returns></returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The AxisDirection method returns the relevant direction Vector3 based on the axis index in relation to x,y,z.
|
|
/// </summary>
|
|
/// <param name="axisIndex">The axis index of the axis. `0 = x` `1 = y` `2 = z`</param>
|
|
/// <param name="givenTransform">An optional Transform to get the Axis Direction for. If this is `null` then the World directions will be used.</param>
|
|
/// <returns>The direction Vector3 based on the given axis index.</returns>
|
|
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)];
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <typeparam name="TValue">The datatype for the list value.</typeparam>
|
|
/// <param name="list">The list to retrieve the value from.</param>
|
|
/// <param name="value">The value to attempt to add to the list.</param>
|
|
/// <param name="preventDuplicates">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.</param>
|
|
/// <returns>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`.</returns>
|
|
public static bool AddListValue<TValue>(List<TValue> list, TValue value, bool preventDuplicates = false)
|
|
{
|
|
if (list != null && (!preventDuplicates || !list.Contains(value)))
|
|
{
|
|
list.Add(value);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <typeparam name="TKey">The datatype for the dictionary key.</typeparam>
|
|
/// <typeparam name="TValue">The datatype for the dictionary value.</typeparam>
|
|
/// <param name="dictionary">The dictionary to retrieve the value from.</param>
|
|
/// <param name="key">The key to retrieve the value for.</param>
|
|
/// <param name="defaultValue">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`).</param>
|
|
/// <param name="setMissingKey">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.</param>
|
|
/// <returns>The found value for the given key in the given dictionary, or the default value if no key is found.</returns>
|
|
public static TValue GetDictionaryValue<TKey, TValue>(Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default(TValue), bool setMissingKey = false)
|
|
{
|
|
bool keyExists;
|
|
return GetDictionaryValue(dictionary, key, out keyExists, defaultValue, setMissingKey);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <typeparam name="TKey">The datatype for the dictionary key.</typeparam>
|
|
/// <typeparam name="TValue">The datatype for the dictionary value.</typeparam>
|
|
/// <param name="dictionary">The dictionary to retrieve the value from.</param>
|
|
/// <param name="key">The key to retrieve the value for.</param>
|
|
/// <param name="keyExists">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.</param>
|
|
/// <param name="defaultValue">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`).</param>
|
|
/// <param name="setMissingKey">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.</param>
|
|
/// <returns>The found value for the given key in the given dictionary, or the default value if no key is found.</returns>
|
|
public static TValue GetDictionaryValue<TKey, TValue>(Dictionary<TKey, TValue> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <typeparam name="TKey">The datatype for the dictionary key.</typeparam>
|
|
/// <typeparam name="TValue">The datatype for the dictionary value.</typeparam>
|
|
/// <param name="dictionary">The dictionary to set the value for.</param>
|
|
/// <param name="key">The key to set the value for.</param>
|
|
/// <param name="value">The value to set at the given key in the given dictionary.</param>
|
|
/// <param name="overwriteExisting">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.</param>
|
|
/// <returns>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`.</returns>
|
|
public static bool AddDictionaryValue<TKey, TValue>(Dictionary<TKey, TValue> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetTypeUnknownAssembly method is used to find a Type without knowing the exact assembly it is in.
|
|
/// </summary>
|
|
/// <param name="typeName">The name of the type to get.</param>
|
|
/// <returns>The Type, or null if none is found.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetEyeTextureResolutionScale method returns the render scale for the resolution.
|
|
/// </summary>
|
|
/// <returns>Returns a float with the render scale for the resolution.</returns>
|
|
public static float GetEyeTextureResolutionScale()
|
|
{
|
|
#if UNITY_2017_2_OR_NEWER
|
|
return XRSettings.eyeTextureResolutionScale;
|
|
#else
|
|
return XRSettings.renderScale;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The SetEyeTextureResolutionScale method sets the render scale for the resolution.
|
|
/// </summary>
|
|
/// <param name="value">The value to set the render scale to.</param>
|
|
public static void SetEyeTextureResolutionScale(float value)
|
|
{
|
|
#if UNITY_2017_2_OR_NEWER
|
|
XRSettings.eyeTextureResolutionScale = value;
|
|
#else
|
|
XRSettings.renderScale = value;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The IsTypeSubclassOf checks if a given Type is a subclass of another given Type.
|
|
/// </summary>
|
|
/// <param name="givenType">The Type to check.</param>
|
|
/// <param name="givenBaseType">The base Type to check.</param>
|
|
/// <returns>Returns `true` if the given type is a subclass of the given base type.</returns>
|
|
public static bool IsTypeSubclassOf(Type givenType, Type givenBaseType)
|
|
{
|
|
#if UNITY_WSA && !UNITY_EDITOR
|
|
return (givenType.GetTypeInfo().IsSubclassOf(givenBaseType));
|
|
#else
|
|
return (givenType.IsSubclassOf(givenBaseType));
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetTypeCustomAttributes method gets the custom attributes of a given type.
|
|
/// </summary>
|
|
/// <param name="givenType">The type to get the custom attributes for.</param>
|
|
/// <param name="attributeType">The attribute type.</param>
|
|
/// <param name="inherit">Whether to inherit attributes.</param>
|
|
/// <returns>Returns an object array of custom attributes.</returns>
|
|
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
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetBaseType method returns the base Type for the given Type.
|
|
/// </summary>
|
|
/// <param name="givenType">The type to return the base Type for.</param>
|
|
/// <returns>Returns the base Type.</returns>
|
|
public static Type GetBaseType(Type givenType)
|
|
{
|
|
#if UNITY_WSA && !UNITY_EDITOR
|
|
return (givenType.GetTypeInfo().BaseType);
|
|
#else
|
|
return (givenType.BaseType);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The IsTypeAssignableFrom method determines if the given Type is assignable from the source Type.
|
|
/// </summary>
|
|
/// <param name="givenType">The Type to check on.</param>
|
|
/// <param name="sourceType">The Type to check if the given Type is assignable from.</param>
|
|
/// <returns>Returns `true` if the given Type is assignable from the source Type.</returns>
|
|
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
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetNestedType method returns the nested Type of the given Type.
|
|
/// </summary>
|
|
/// <param name="givenType">The Type to check on.</param>
|
|
/// <param name="name">The name of the nested Type.</param>
|
|
/// <returns>Returns the nested Type.</returns>
|
|
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
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetPropertyFirstName method returns the string name of the first property on a given Type.
|
|
/// </summary>
|
|
/// <typeparam name="T">The type to check the first property on.</typeparam>
|
|
/// <returns>Returns a string representation of the first property name for the given Type.</returns>
|
|
public static string GetPropertyFirstName<T>()
|
|
{
|
|
#if UNITY_WSA && !UNITY_EDITOR
|
|
return (typeof(T).GetTypeInfo().DeclaredProperties.First().Name);
|
|
#else
|
|
return (typeof(T).GetProperties()[0].Name);
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetCommandLineArguements method returns the command line arguements for the environment.
|
|
/// </summary>
|
|
/// <returns>Returns an array of command line arguements as strings.</returns>
|
|
public static string[] GetCommandLineArguements()
|
|
{
|
|
#if UNITY_WSA && !UNITY_EDITOR
|
|
return new string[0];
|
|
#else
|
|
|
|
return Environment.GetCommandLineArgs();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetTypesOfType method returns an array of Types for the given Type.
|
|
/// </summary>
|
|
/// <param name="givenType">The Type to check on.</param>
|
|
/// <returns>An array of Types found.</returns>
|
|
public static Type[] GetTypesOfType(Type givenType)
|
|
{
|
|
#if UNITY_WSA && !UNITY_EDITOR
|
|
return givenType.GetTypeInfo().Assembly.GetTypes();
|
|
#else
|
|
|
|
return givenType.Assembly.GetTypes();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The GetExportedTypesOfType method returns an array of Exported Types for the given Type.
|
|
/// </summary>
|
|
/// <param name="givenType">The Type to check on.</param>
|
|
/// <returns>An array of Exported Types found.</returns>
|
|
public static Type[] GetExportedTypesOfType(Type givenType)
|
|
{
|
|
#if UNITY_WSA && !UNITY_EDITOR
|
|
return givenType.GetTypeInfo().Assembly.GetExportedTypes();
|
|
#else
|
|
|
|
return givenType.Assembly.GetExportedTypes();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// The IsTypeAbstract method determines if a given Type is abstract.
|
|
/// </summary>
|
|
/// <param name="givenType">The Type to check on.</param>
|
|
/// <returns>Returns `true` if the given type is abstract.</returns>
|
|
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<BuildTargetGroup>().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
|
|
|
|
/// <summary>
|
|
/// The FindEvenInactiveComponentsInLoadedScenes method searches active and inactive game objects in all
|
|
/// loaded scenes for components matching the type supplied.
|
|
/// </summary>
|
|
/// <param name="searchAllScenes">If true, will search all loaded scenes, otherwise just the active scene.</param>
|
|
/// <param name="stopOnMatch">If true, will stop searching objects as soon as a match is found.</param>
|
|
/// <returns></returns>
|
|
private static IEnumerable<T> FindEvenInactiveComponentsInValidScenes<T>(bool searchAllScenes, bool stopOnMatch = false) where T : Component
|
|
{
|
|
IEnumerable<T> results;
|
|
if (searchAllScenes)
|
|
{
|
|
List<T> allSceneResults = new List<T>();
|
|
for (int sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++)
|
|
{
|
|
allSceneResults.AddRange(FindEvenInactiveComponentsInScene<T>(SceneManager.GetSceneAt(sceneIndex), stopOnMatch));
|
|
}
|
|
results = allSceneResults;
|
|
}
|
|
else
|
|
{
|
|
results = FindEvenInactiveComponentsInScene<T>(SceneManager.GetActiveScene(), stopOnMatch);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The FIndEvenInactiveComponentsInScene method searches the specified scene for components matching the type supplied.
|
|
/// </summary>
|
|
/// <param name="scene">The scene to search. This scene must be valid, either loaded or loading.</param>
|
|
/// <param name="stopOnMatch">If true, will stop searching objects as soon as a match is found.</param>
|
|
/// <returns></returns>
|
|
private static IEnumerable<T> FindEvenInactiveComponentsInScene<T>(Scene scene, bool stopOnMatch = false)
|
|
{
|
|
List<T> results = new List<T>();
|
|
if(!scene.isLoaded)
|
|
{
|
|
return results;
|
|
}
|
|
|
|
foreach (GameObject rootObject in scene.GetRootGameObjects())
|
|
{
|
|
if (stopOnMatch)
|
|
{
|
|
T foundComponent = rootObject.GetComponentInChildren<T>(true);
|
|
if (foundComponent != null)
|
|
{
|
|
results.Add(foundComponent);
|
|
return results;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
results.AddRange(rootObject.GetComponentsInChildren<T>(true));
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
}
|
|
}
|