Assignment for RMIT Mixed Reality in 2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

837 lines
42 KiB

  1. // Shared Methods|Utilities|90060
  2. namespace VRTK
  3. {
  4. using UnityEngine;
  5. using UnityEngine.SceneManagement;
  6. #if UNITY_EDITOR
  7. using UnityEditor;
  8. #endif
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Linq;
  12. using System.Reflection;
  13. #if UNITY_2017_2_OR_NEWER
  14. using UnityEngine.XR;
  15. #else
  16. using XRSettings = UnityEngine.VR.VRSettings;
  17. using XRStats = UnityEngine.VR.VRStats;
  18. #endif
  19. /// <summary>
  20. /// The Shared Methods script is a collection of reusable static methods that are used across a range of different scripts.
  21. /// </summary>
  22. public static class VRTK_SharedMethods
  23. {
  24. /// <summary>
  25. /// The GetBounds methods returns the bounds of the transform including all children in world space.
  26. /// </summary>
  27. /// <param name="transform"></param>
  28. /// <param name="excludeRotation">Resets the rotation of the transform temporarily to 0 to eliminate skewed bounds.</param>
  29. /// <param name="excludeTransform">Does not consider the stated object when calculating the bounds.</param>
  30. /// <returns>The bounds of the transform.</returns>
  31. public static Bounds GetBounds(Transform transform, Transform excludeRotation = null, Transform excludeTransform = null)
  32. {
  33. Quaternion oldRotation = Quaternion.identity;
  34. if (excludeRotation != null)
  35. {
  36. oldRotation = excludeRotation.rotation;
  37. excludeRotation.rotation = Quaternion.identity;
  38. }
  39. bool boundsInitialized = false;
  40. Bounds bounds = new Bounds(transform.position, Vector3.zero);
  41. Renderer[] renderers = transform.GetComponentsInChildren<Renderer>();
  42. for (int i = 0; i < renderers.Length; i++)
  43. {
  44. Renderer renderer = renderers[i];
  45. if (excludeTransform != null && renderer.transform.IsChildOf(excludeTransform))
  46. {
  47. continue;
  48. }
  49. // do late initialization in case initial transform does not contain any renderers
  50. if (!boundsInitialized)
  51. {
  52. bounds = new Bounds(renderer.transform.position, Vector3.zero);
  53. boundsInitialized = true;
  54. }
  55. bounds.Encapsulate(renderer.bounds);
  56. }
  57. if (bounds.size.magnitude == 0)
  58. {
  59. // do second pass as there were no renderers, this time with colliders
  60. BoxCollider[] colliders = transform.GetComponentsInChildren<BoxCollider>();
  61. for (int i = 0; i < colliders.Length; i++)
  62. {
  63. BoxCollider collider = colliders[i];
  64. if (excludeTransform != null && collider.transform.IsChildOf(excludeTransform))
  65. {
  66. continue;
  67. }
  68. // do late initialization in case initial transform does not contain any colliders
  69. if (!boundsInitialized)
  70. {
  71. bounds = new Bounds(collider.transform.position, Vector3.zero);
  72. boundsInitialized = true;
  73. }
  74. bounds.Encapsulate(collider.bounds);
  75. }
  76. }
  77. if (excludeRotation != null)
  78. {
  79. excludeRotation.rotation = oldRotation;
  80. }
  81. return bounds;
  82. }
  83. /// <summary>
  84. /// The IsLowest method checks to see if the given value is the lowest number in the given array of values.
  85. /// </summary>
  86. /// <param name="value">The value to check to see if it is lowest.</param>
  87. /// <param name="others">The array of values to check against.</param>
  88. /// <returns>Returns true if the value is lower than all numbers in the given array, returns false if it is not the lowest.</returns>
  89. public static bool IsLowest(float value, float[] others)
  90. {
  91. for (int i = 0; i < others.Length; i++)
  92. {
  93. if (others[i] <= value)
  94. {
  95. return false;
  96. }
  97. }
  98. return true;
  99. }
  100. /// <summary>
  101. /// The AddCameraFade method finds the headset camera and adds a headset fade script to it.
  102. /// </summary>
  103. /// <returns>The transform of the headset camera.</returns>
  104. public static Transform AddCameraFade()
  105. {
  106. Transform camera = VRTK_DeviceFinder.HeadsetCamera();
  107. VRTK_SDK_Bridge.AddHeadsetFade(camera);
  108. return camera;
  109. }
  110. /// <summary>
  111. /// The CreateColliders method attempts to add box colliders to all child objects in the given object that have a renderer but no collider.
  112. /// </summary>
  113. /// <param name="obj">The game object to attempt to add the colliders to.</param>
  114. public static void CreateColliders(GameObject obj)
  115. {
  116. Renderer[] renderers = obj.GetComponentsInChildren<Renderer>();
  117. for (int i = 0; i < renderers.Length; i++)
  118. {
  119. Renderer renderer = renderers[i];
  120. if (renderer.gameObject.GetComponent<Collider>() == null)
  121. {
  122. renderer.gameObject.AddComponent<BoxCollider>();
  123. }
  124. }
  125. }
  126. /// <summary>
  127. /// The ColliderExclude method reduces the colliders in the setA array by those matched in the setB array.
  128. /// </summary>
  129. /// <param name="setA">The array that contains all of the relevant colliders.</param>
  130. /// <param name="setB">The array that contains the colliders to remove from setA.</param>
  131. /// <returns>A Collider array that is a subset of setA that doesn't contain the colliders from setB.</returns>
  132. public static Collider[] ColliderExclude(Collider[] setA, Collider[] setB)
  133. {
  134. return setA.Except(setB).ToArray<Collider>();
  135. }
  136. /// <summary>
  137. /// The GetCollidersInGameObjects method iterates through a GameObject array and returns all of the unique found colliders for all GameObejcts.
  138. /// </summary>
  139. /// <param name="gameObjects">An array of GameObjects to get the colliders for.</param>
  140. /// <param name="searchChildren">If this is `true` then the given GameObjects will also have their child GameObjects searched for colliders.</param>
  141. /// <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>
  142. /// <returns>An array of Colliders that are found in the given GameObject array.</returns>
  143. public static Collider[] GetCollidersInGameObjects(GameObject[] gameObjects, bool searchChildren, bool includeInactive)
  144. {
  145. HashSet<Collider> foundColliders = new HashSet<Collider>();
  146. for (int i = 0; i < gameObjects.Length; i++)
  147. {
  148. Collider[] gameObjectColliders = (searchChildren ? gameObjects[i].GetComponentsInChildren<Collider>(includeInactive) : gameObjects[i].GetComponents<Collider>());
  149. for (int j = 0; j < gameObjectColliders.Length; j++)
  150. {
  151. foundColliders.Add(gameObjectColliders[j]);
  152. }
  153. }
  154. return foundColliders.ToArray();
  155. }
  156. /// <summary>
  157. /// The CloneComponent method takes a source component and copies it to the given destination game object.
  158. /// </summary>
  159. /// <param name="source">The component to copy.</param>
  160. /// <param name="destination">The game object to copy the component to.</param>
  161. /// <param name="copyProperties">Determines whether the properties of the component as well as the fields should be copied.</param>
  162. /// <returns>The component that has been cloned onto the given game object.</returns>
  163. public static Component CloneComponent(Component source, GameObject destination, bool copyProperties = false)
  164. {
  165. Component tmpComponent = destination.gameObject.AddComponent(source.GetType());
  166. if (copyProperties)
  167. {
  168. PropertyInfo[] foundProperties = source.GetType().GetProperties();
  169. for (int i = 0; i < foundProperties.Length; i++)
  170. {
  171. PropertyInfo foundProperty = foundProperties[i];
  172. if (foundProperty.CanWrite)
  173. {
  174. foundProperty.SetValue(tmpComponent, foundProperty.GetValue(source, null), null);
  175. }
  176. }
  177. }
  178. FieldInfo[] foundFields = source.GetType().GetFields();
  179. for (int i = 0; i < foundFields.Length; i++)
  180. {
  181. FieldInfo foundField = foundFields[i];
  182. foundField.SetValue(tmpComponent, foundField.GetValue(source));
  183. }
  184. return tmpComponent;
  185. }
  186. /// <summary>
  187. /// The ColorDarken method takes a given colour and darkens it by the given percentage.
  188. /// </summary>
  189. /// <param name="color">The source colour to apply the darken to.</param>
  190. /// <param name="percent">The percent to darken the colour by.</param>
  191. /// <returns>The new colour with the darken applied.</returns>
  192. public static Color ColorDarken(Color color, float percent)
  193. {
  194. return new Color(NumberPercent(color.r, percent), NumberPercent(color.g, percent), NumberPercent(color.b, percent), color.a);
  195. }
  196. /// <summary>
  197. /// The RoundFloat method is used to round a given float to the given decimal places.
  198. /// </summary>
  199. /// <param name="givenFloat">The float to round.</param>
  200. /// <param name="decimalPlaces">The number of decimal places to round to.</param>
  201. /// <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>
  202. /// <returns>The rounded float.</returns>
  203. public static float RoundFloat(float givenFloat, int decimalPlaces, bool rawFidelity = false)
  204. {
  205. float roundBy = (rawFidelity ? decimalPlaces : Mathf.Pow(10.0f, decimalPlaces));
  206. return Mathf.Round(givenFloat * roundBy) / roundBy;
  207. }
  208. /// <summary>
  209. /// The IsEditTime method determines if the state of Unity is in the Unity Editor and the scene is not in play mode.
  210. /// </summary>
  211. /// <returns>Returns true if Unity is in the Unity Editor and not in play mode.</returns>
  212. public static bool IsEditTime()
  213. {
  214. #if UNITY_EDITOR
  215. return !EditorApplication.isPlayingOrWillChangePlaymode;
  216. #else
  217. return false;
  218. #endif
  219. }
  220. /// <summary>
  221. /// The Mod method is used to find the remainder of the sum a/b.
  222. /// </summary>
  223. /// <param name="a">The dividend value.</param>
  224. /// <param name="b">The divisor value.</param>
  225. /// <returns>The remainder value.</returns>
  226. public static float Mod(float a, float b)
  227. {
  228. return a - b * Mathf.Floor(a / b);
  229. }
  230. /// <summary>
  231. /// Finds the first GameObject with a given name and an ancestor that has a specific component.
  232. /// </summary>
  233. /// <remarks>
  234. /// This method returns active as well as inactive GameObjects in all scenes. It doesn't return assets.
  235. /// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead.
  236. /// </remarks>
  237. /// <typeparam name="T">The component type that needs to be on an ancestor of the wanted GameObject. Must be a subclass of `Component`.</typeparam>
  238. /// <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>
  239. /// <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>
  240. /// <returns>The GameObject with name `gameObjectName` and an ancestor that has a `T`. If no such GameObject is found then `null` is returned.</returns>
  241. public static GameObject FindEvenInactiveGameObject<T>(string gameObjectName = null, bool searchAllScenes = false) where T : Component
  242. {
  243. if (string.IsNullOrEmpty(gameObjectName))
  244. {
  245. T foundComponent = FindEvenInactiveComponentsInValidScenes<T>(searchAllScenes, true).FirstOrDefault();
  246. return foundComponent == null ? null : foundComponent.gameObject;
  247. }
  248. return FindEvenInactiveComponentsInValidScenes<T>(searchAllScenes)
  249. .Select(component =>
  250. {
  251. Transform transform = component.gameObject.transform.Find(gameObjectName);
  252. return transform == null ? null : transform.gameObject;
  253. })
  254. .FirstOrDefault(gameObject => gameObject != null);
  255. }
  256. /// <summary>
  257. /// Finds all components of a given type.
  258. /// </summary>
  259. /// <remarks>
  260. /// This method returns components from active as well as inactive GameObjects in all scenes. It doesn't return assets.
  261. /// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead.
  262. /// </remarks>
  263. /// <typeparam name="T">The component type to search for. Must be a subclass of `Component`.</typeparam>
  264. /// <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>
  265. /// <returns>All the found components. If no component is found an empty array is returned.</returns>
  266. public static T[] FindEvenInactiveComponents<T>(bool searchAllScenes = false) where T : Component
  267. {
  268. IEnumerable<T> results = FindEvenInactiveComponentsInValidScenes<T>(searchAllScenes);
  269. return results.ToArray();
  270. }
  271. /// <summary>
  272. /// Finds the first component of a given type.
  273. /// </summary>
  274. /// <remarks>
  275. /// This method returns components from active as well as inactive GameObjects in all scenes. It doesn't return assets.
  276. /// For performance reasons it is recommended to not use this function every frame. Cache the result in a member variable at startup instead.
  277. /// </remarks>
  278. /// <typeparam name="T">The component type to search for. Must be a subclass of `Component`.</typeparam>
  279. /// <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>
  280. /// <returns>The found component. If no component is found `null` is returned.</returns>
  281. public static T FindEvenInactiveComponent<T>(bool searchAllScenes = false) where T : Component
  282. {
  283. return FindEvenInactiveComponentsInValidScenes<T>(searchAllScenes, true).FirstOrDefault();
  284. }
  285. /// <summary>
  286. /// The GenerateVRTKObjectName method is used to create a standard name string for any VRTK generated object.
  287. /// </summary>
  288. /// <param name="autoGen">An additiona [AUTOGEN] prefix will be added if this is true.</param>
  289. /// <param name="replacements">A collection of parameters to add to the generated name.</param>
  290. /// <returns>The generated name string.</returns>
  291. public static string GenerateVRTKObjectName(bool autoGen, params object[] replacements)
  292. {
  293. string toFormat = "[VRTK]";
  294. if (autoGen)
  295. {
  296. toFormat += "[AUTOGEN]";
  297. }
  298. for (int i = 0; i < replacements.Length; i++)
  299. {
  300. toFormat += "[{" + i + "}]";
  301. }
  302. return string.Format(toFormat, replacements);
  303. }
  304. /// <summary>
  305. /// The GetGPUTimeLastFrame retrieves the time spent by the GPU last frame, in seconds, as reported by the VR SDK.
  306. /// </summary>
  307. /// <returns>The total GPU time utilized last frame as measured by the VR subsystem.</returns>
  308. public static float GetGPUTimeLastFrame()
  309. {
  310. #if UNITY_5_6_OR_NEWER
  311. float gpuTimeLastFrame;
  312. return (XRStats.TryGetGPUTimeLastFrame(out gpuTimeLastFrame) ? gpuTimeLastFrame : 0f);
  313. #else
  314. return XRStats.gpuTimeLastFrame;
  315. #endif
  316. }
  317. /// <summary>
  318. /// 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.
  319. /// </summary>
  320. /// <param name="vectorA">The Vector2 to compare against.</param>
  321. /// <param name="vectorB">The Vector2 to compare with</param>
  322. /// <param name="compareFidelity">The number of decimal places to use when doing the comparison on the float elements within the Vector2.</param>
  323. /// <returns>Returns `true` if the given Vector2 objects match based on the given fidelity.</returns>
  324. public static bool Vector2ShallowCompare(Vector2 vectorA, Vector2 vectorB, int compareFidelity)
  325. {
  326. Vector2 distanceVector = vectorA - vectorB;
  327. return (Math.Round(Mathf.Abs(distanceVector.x), compareFidelity, MidpointRounding.AwayFromZero) < float.Epsilon &&
  328. Math.Round(Mathf.Abs(distanceVector.y), compareFidelity, MidpointRounding.AwayFromZero) < float.Epsilon);
  329. }
  330. /// <summary>
  331. /// 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.
  332. /// </summary>
  333. /// <param name="vectorA">The Vector3 to compare against.</param>
  334. /// <param name="vectorB">The Vector3 to compare with</param>
  335. /// <param name="threshold">The distance in which the two Vector3 objects can be within to be considered true</param>
  336. /// <returns>Returns `true` if the given Vector3 objects are within the given threshold distance.</returns>
  337. public static bool Vector3ShallowCompare(Vector3 vectorA, Vector3 vectorB, float threshold)
  338. {
  339. return (Vector3.Distance(vectorA, vectorB) < threshold);
  340. }
  341. /// <summary>
  342. /// The NumberPercent method is used to determine the percentage of a given value.
  343. /// </summary>
  344. /// <param name="value">The value to determine the percentage from</param>
  345. /// <param name="percent">The percentage to find within the given value.</param>
  346. /// <returns>A float containing the percentage value based on the given input.</returns>
  347. public static float NumberPercent(float value, float percent)
  348. {
  349. percent = Mathf.Clamp(percent, 0f, 100f);
  350. return (percent == 0f ? value : (value - (percent / 100f)));
  351. }
  352. /// <summary>
  353. /// The SetGlobalScale method is used to set a transform scale based on a global scale instead of a local scale.
  354. /// </summary>
  355. /// <param name="transform">The reference to the transform to scale.</param>
  356. /// <param name="globalScale">A Vector3 of a global scale to apply to the given transform.</param>
  357. public static void SetGlobalScale(this Transform transform, Vector3 globalScale)
  358. {
  359. transform.localScale = Vector3.one;
  360. transform.localScale = new Vector3(globalScale.x / transform.lossyScale.x, globalScale.y / transform.lossyScale.y, globalScale.z / transform.lossyScale.z);
  361. }
  362. /// <summary>
  363. /// The VectorHeading method calculates the current heading of the target position in relation to the origin position.
  364. /// </summary>
  365. /// <param name="originPosition">The point to use as the originating position for the heading calculation.</param>
  366. /// <param name="targetPosition">The point to use as the target position for the heading calculation.</param>
  367. /// <returns>A Vector3 containing the heading changes of the target position in relation to the origin position.</returns>
  368. public static Vector3 VectorHeading(Vector3 originPosition, Vector3 targetPosition)
  369. {
  370. return targetPosition - originPosition;
  371. }
  372. /// <summary>
  373. /// The VectorDirection method calculates the direction the target position is in relation to the origin position.
  374. /// </summary>
  375. /// <param name="originPosition">The point to use as the originating position for the direction calculation.</param>
  376. /// <param name="targetPosition">The point to use as the target position for the direction calculation.</param>
  377. /// <returns>A Vector3 containing the direction of the target position in relation to the origin position.</returns>
  378. public static Vector3 VectorDirection(Vector3 originPosition, Vector3 targetPosition)
  379. {
  380. Vector3 heading = VectorHeading(originPosition, targetPosition);
  381. return heading * DividerToMultiplier(heading.magnitude);
  382. }
  383. /// <summary>
  384. /// 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)
  385. /// </summary>
  386. /// <param name="value">The number to convert into the multplier value.</param>
  387. /// <returns>The calculated number that can replace the divider number in a multiplication sum.</returns>
  388. public static float DividerToMultiplier(float value)
  389. {
  390. return (value != 0f ? 1f / value : 1f);
  391. }
  392. /// <summary>
  393. /// The NormalizeValue method takes a given value between a specified range and returns the normalized value between 0f and 1f.
  394. /// </summary>
  395. /// <param name="value">The actual value to normalize.</param>
  396. /// <param name="minValue">The minimum value the actual value can be.</param>
  397. /// <param name="maxValue">The maximum value the actual value can be.</param>
  398. /// <param name="threshold">The threshold to force to the minimum or maximum value if the normalized value is within the threhold limits.</param>
  399. /// <returns></returns>
  400. public static float NormalizeValue(float value, float minValue, float maxValue, float threshold = 0f)
  401. {
  402. float normalizedMax = maxValue - minValue;
  403. float normalizedValue = normalizedMax - (maxValue - value);
  404. float result = normalizedValue * DividerToMultiplier(normalizedMax); ;
  405. result = (result < threshold ? 0f : result);
  406. result = (result > 1f - threshold ? 1f : result);
  407. return Mathf.Clamp(result, 0f, 1f);
  408. }
  409. /// <summary>
  410. /// The AxisDirection method returns the relevant direction Vector3 based on the axis index in relation to x,y,z.
  411. /// </summary>
  412. /// <param name="axisIndex">The axis index of the axis. `0 = x` `1 = y` `2 = z`</param>
  413. /// <param name="givenTransform">An optional Transform to get the Axis Direction for. If this is `null` then the World directions will be used.</param>
  414. /// <returns>The direction Vector3 based on the given axis index.</returns>
  415. public static Vector3 AxisDirection(int axisIndex, Transform givenTransform = null)
  416. {
  417. Vector3[] worldDirections = (givenTransform != null ? new Vector3[] { givenTransform.right, givenTransform.up, givenTransform.forward } : new Vector3[] { Vector3.right, Vector3.up, Vector3.forward });
  418. return worldDirections[(int)Mathf.Clamp(axisIndex, 0f, worldDirections.Length)];
  419. }
  420. /// <summary>
  421. /// 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.
  422. /// </summary>
  423. /// <typeparam name="TValue">The datatype for the list value.</typeparam>
  424. /// <param name="list">The list to retrieve the value from.</param>
  425. /// <param name="value">The value to attempt to add to the list.</param>
  426. /// <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>
  427. /// <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>
  428. public static bool AddListValue<TValue>(List<TValue> list, TValue value, bool preventDuplicates = false)
  429. {
  430. if (list != null && (!preventDuplicates || !list.Contains(value)))
  431. {
  432. list.Add(value);
  433. return true;
  434. }
  435. return false;
  436. }
  437. /// <summary>
  438. /// 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.
  439. /// </summary>
  440. /// <typeparam name="TKey">The datatype for the dictionary key.</typeparam>
  441. /// <typeparam name="TValue">The datatype for the dictionary value.</typeparam>
  442. /// <param name="dictionary">The dictionary to retrieve the value from.</param>
  443. /// <param name="key">The key to retrieve the value for.</param>
  444. /// <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>
  445. /// <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>
  446. /// <returns>The found value for the given key in the given dictionary, or the default value if no key is found.</returns>
  447. public static TValue GetDictionaryValue<TKey, TValue>(Dictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue = default(TValue), bool setMissingKey = false)
  448. {
  449. bool keyExists;
  450. return GetDictionaryValue(dictionary, key, out keyExists, defaultValue, setMissingKey);
  451. }
  452. /// <summary>
  453. /// 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.
  454. /// </summary>
  455. /// <typeparam name="TKey">The datatype for the dictionary key.</typeparam>
  456. /// <typeparam name="TValue">The datatype for the dictionary value.</typeparam>
  457. /// <param name="dictionary">The dictionary to retrieve the value from.</param>
  458. /// <param name="key">The key to retrieve the value for.</param>
  459. /// <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>
  460. /// <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>
  461. /// <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>
  462. /// <returns>The found value for the given key in the given dictionary, or the default value if no key is found.</returns>
  463. public static TValue GetDictionaryValue<TKey, TValue>(Dictionary<TKey, TValue> dictionary, TKey key, out bool keyExists, TValue defaultValue = default(TValue), bool setMissingKey = false)
  464. {
  465. keyExists = false;
  466. if (dictionary == null)
  467. {
  468. return defaultValue;
  469. }
  470. TValue outputValue;
  471. if (dictionary.TryGetValue(key, out outputValue))
  472. {
  473. keyExists = true;
  474. }
  475. else
  476. {
  477. if (setMissingKey)
  478. {
  479. dictionary.Add(key, defaultValue);
  480. }
  481. outputValue = defaultValue;
  482. }
  483. return outputValue;
  484. }
  485. /// <summary>
  486. /// 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.
  487. /// </summary>
  488. /// <typeparam name="TKey">The datatype for the dictionary key.</typeparam>
  489. /// <typeparam name="TValue">The datatype for the dictionary value.</typeparam>
  490. /// <param name="dictionary">The dictionary to set the value for.</param>
  491. /// <param name="key">The key to set the value for.</param>
  492. /// <param name="value">The value to set at the given key in the given dictionary.</param>
  493. /// <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>
  494. /// <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>
  495. public static bool AddDictionaryValue<TKey, TValue>(Dictionary<TKey, TValue> dictionary, TKey key, TValue value, bool overwriteExisting = false)
  496. {
  497. if (dictionary != null)
  498. {
  499. if (overwriteExisting)
  500. {
  501. dictionary[key] = value;
  502. return true;
  503. }
  504. else
  505. {
  506. bool keyExists;
  507. GetDictionaryValue(dictionary, key, out keyExists, value, true);
  508. return !keyExists;
  509. }
  510. }
  511. return false;
  512. }
  513. /// <summary>
  514. /// The GetTypeUnknownAssembly method is used to find a Type without knowing the exact assembly it is in.
  515. /// </summary>
  516. /// <param name="typeName">The name of the type to get.</param>
  517. /// <returns>The Type, or null if none is found.</returns>
  518. public static Type GetTypeUnknownAssembly(string typeName)
  519. {
  520. Type type = Type.GetType(typeName);
  521. if (type != null)
  522. {
  523. return type;
  524. }
  525. #if !UNITY_WSA
  526. Assembly[] foundAssemblies = AppDomain.CurrentDomain.GetAssemblies();
  527. for (int i = 0; i < foundAssemblies.Length; i++)
  528. {
  529. type = foundAssemblies[i].GetType(typeName);
  530. if (type != null)
  531. {
  532. return type;
  533. }
  534. }
  535. #endif
  536. return null;
  537. }
  538. /// <summary>
  539. /// The GetEyeTextureResolutionScale method returns the render scale for the resolution.
  540. /// </summary>
  541. /// <returns>Returns a float with the render scale for the resolution.</returns>
  542. public static float GetEyeTextureResolutionScale()
  543. {
  544. #if UNITY_2017_2_OR_NEWER
  545. return XRSettings.eyeTextureResolutionScale;
  546. #else
  547. return XRSettings.renderScale;
  548. #endif
  549. }
  550. /// <summary>
  551. /// The SetEyeTextureResolutionScale method sets the render scale for the resolution.
  552. /// </summary>
  553. /// <param name="value">The value to set the render scale to.</param>
  554. public static void SetEyeTextureResolutionScale(float value)
  555. {
  556. #if UNITY_2017_2_OR_NEWER
  557. XRSettings.eyeTextureResolutionScale = value;
  558. #else
  559. XRSettings.renderScale = value;
  560. #endif
  561. }
  562. /// <summary>
  563. /// The IsTypeSubclassOf checks if a given Type is a subclass of another given Type.
  564. /// </summary>
  565. /// <param name="givenType">The Type to check.</param>
  566. /// <param name="givenBaseType">The base Type to check.</param>
  567. /// <returns>Returns `true` if the given type is a subclass of the given base type.</returns>
  568. public static bool IsTypeSubclassOf(Type givenType, Type givenBaseType)
  569. {
  570. #if UNITY_WSA && !UNITY_EDITOR
  571. return (givenType.GetTypeInfo().IsSubclassOf(givenBaseType));
  572. #else
  573. return (givenType.IsSubclassOf(givenBaseType));
  574. #endif
  575. }
  576. /// <summary>
  577. /// The GetTypeCustomAttributes method gets the custom attributes of a given type.
  578. /// </summary>
  579. /// <param name="givenType">The type to get the custom attributes for.</param>
  580. /// <param name="attributeType">The attribute type.</param>
  581. /// <param name="inherit">Whether to inherit attributes.</param>
  582. /// <returns>Returns an object array of custom attributes.</returns>
  583. public static object[] GetTypeCustomAttributes(Type givenType, Type attributeType, bool inherit)
  584. {
  585. #if UNITY_WSA && !UNITY_EDITOR
  586. return ((object[])givenType.GetTypeInfo().GetCustomAttributes(attributeType, inherit));
  587. #else
  588. return (givenType.GetCustomAttributes(attributeType, inherit));
  589. #endif
  590. }
  591. /// <summary>
  592. /// The GetBaseType method returns the base Type for the given Type.
  593. /// </summary>
  594. /// <param name="givenType">The type to return the base Type for.</param>
  595. /// <returns>Returns the base Type.</returns>
  596. public static Type GetBaseType(Type givenType)
  597. {
  598. #if UNITY_WSA && !UNITY_EDITOR
  599. return (givenType.GetTypeInfo().BaseType);
  600. #else
  601. return (givenType.BaseType);
  602. #endif
  603. }
  604. /// <summary>
  605. /// The IsTypeAssignableFrom method determines if the given Type is assignable from the source Type.
  606. /// </summary>
  607. /// <param name="givenType">The Type to check on.</param>
  608. /// <param name="sourceType">The Type to check if the given Type is assignable from.</param>
  609. /// <returns>Returns `true` if the given Type is assignable from the source Type.</returns>
  610. public static bool IsTypeAssignableFrom(Type givenType, Type sourceType)
  611. {
  612. #if UNITY_WSA && !UNITY_EDITOR
  613. return (givenType.GetTypeInfo().IsAssignableFrom(sourceType.GetTypeInfo()));
  614. #else
  615. return (givenType.IsAssignableFrom(sourceType));
  616. #endif
  617. }
  618. /// <summary>
  619. /// The GetNestedType method returns the nested Type of the given Type.
  620. /// </summary>
  621. /// <param name="givenType">The Type to check on.</param>
  622. /// <param name="name">The name of the nested Type.</param>
  623. /// <returns>Returns the nested Type.</returns>
  624. public static Type GetNestedType(Type givenType, string name)
  625. {
  626. #if UNITY_WSA && !UNITY_EDITOR
  627. return (givenType.GetTypeInfo().GetDeclaredNestedType(name).GetType());
  628. #else
  629. return (givenType.GetNestedType(name));
  630. #endif
  631. }
  632. /// <summary>
  633. /// The GetPropertyFirstName method returns the string name of the first property on a given Type.
  634. /// </summary>
  635. /// <typeparam name="T">The type to check the first property on.</typeparam>
  636. /// <returns>Returns a string representation of the first property name for the given Type.</returns>
  637. public static string GetPropertyFirstName<T>()
  638. {
  639. #if UNITY_WSA && !UNITY_EDITOR
  640. return (typeof(T).GetTypeInfo().DeclaredProperties.First().Name);
  641. #else
  642. return (typeof(T).GetProperties()[0].Name);
  643. #endif
  644. }
  645. /// <summary>
  646. /// The GetCommandLineArguements method returns the command line arguements for the environment.
  647. /// </summary>
  648. /// <returns>Returns an array of command line arguements as strings.</returns>
  649. public static string[] GetCommandLineArguements()
  650. {
  651. #if UNITY_WSA && !UNITY_EDITOR
  652. return new string[0];
  653. #else
  654. return Environment.GetCommandLineArgs();
  655. #endif
  656. }
  657. /// <summary>
  658. /// The GetTypesOfType method returns an array of Types for the given Type.
  659. /// </summary>
  660. /// <param name="givenType">The Type to check on.</param>
  661. /// <returns>An array of Types found.</returns>
  662. public static Type[] GetTypesOfType(Type givenType)
  663. {
  664. #if UNITY_WSA && !UNITY_EDITOR
  665. return givenType.GetTypeInfo().Assembly.GetTypes();
  666. #else
  667. return givenType.Assembly.GetTypes();
  668. #endif
  669. }
  670. /// <summary>
  671. /// The GetExportedTypesOfType method returns an array of Exported Types for the given Type.
  672. /// </summary>
  673. /// <param name="givenType">The Type to check on.</param>
  674. /// <returns>An array of Exported Types found.</returns>
  675. public static Type[] GetExportedTypesOfType(Type givenType)
  676. {
  677. #if UNITY_WSA && !UNITY_EDITOR
  678. return givenType.GetTypeInfo().Assembly.GetExportedTypes();
  679. #else
  680. return givenType.Assembly.GetExportedTypes();
  681. #endif
  682. }
  683. /// <summary>
  684. /// The IsTypeAbstract method determines if a given Type is abstract.
  685. /// </summary>
  686. /// <param name="givenType">The Type to check on.</param>
  687. /// <returns>Returns `true` if the given type is abstract.</returns>
  688. public static bool IsTypeAbstract(Type givenType)
  689. {
  690. #if UNITY_WSA && !UNITY_EDITOR
  691. return givenType.GetTypeInfo().IsAbstract;
  692. #else
  693. return givenType.IsAbstract;
  694. #endif
  695. }
  696. #if UNITY_EDITOR
  697. public static BuildTargetGroup[] GetValidBuildTargetGroups()
  698. {
  699. return Enum.GetValues(typeof(BuildTargetGroup)).Cast<BuildTargetGroup>().Where(group =>
  700. {
  701. if (group == BuildTargetGroup.Unknown)
  702. {
  703. return false;
  704. }
  705. string targetGroupName = Enum.GetName(typeof(BuildTargetGroup), group);
  706. FieldInfo targetGroupFieldInfo = typeof(BuildTargetGroup).GetField(targetGroupName, BindingFlags.Public | BindingFlags.Static);
  707. bool validReturn = (targetGroupFieldInfo != null && targetGroupFieldInfo.GetCustomAttributes(typeof(ObsoleteAttribute), false).Length == 0);
  708. #if UNITY_WSA
  709. if (targetGroupName == "Metro" || targetGroupName == "WSA")
  710. {
  711. validReturn = (targetGroupFieldInfo != null);
  712. }
  713. #endif
  714. return validReturn;
  715. }).ToArray();
  716. }
  717. #endif
  718. /// <summary>
  719. /// The FindEvenInactiveComponentsInLoadedScenes method searches active and inactive game objects in all
  720. /// loaded scenes for components matching the type supplied.
  721. /// </summary>
  722. /// <param name="searchAllScenes">If true, will search all loaded scenes, otherwise just the active scene.</param>
  723. /// <param name="stopOnMatch">If true, will stop searching objects as soon as a match is found.</param>
  724. /// <returns></returns>
  725. private static IEnumerable<T> FindEvenInactiveComponentsInValidScenes<T>(bool searchAllScenes, bool stopOnMatch = false) where T : Component
  726. {
  727. IEnumerable<T> results;
  728. if (searchAllScenes)
  729. {
  730. List<T> allSceneResults = new List<T>();
  731. for (int sceneIndex = 0; sceneIndex < SceneManager.sceneCount; sceneIndex++)
  732. {
  733. allSceneResults.AddRange(FindEvenInactiveComponentsInScene<T>(SceneManager.GetSceneAt(sceneIndex), stopOnMatch));
  734. }
  735. results = allSceneResults;
  736. }
  737. else
  738. {
  739. results = FindEvenInactiveComponentsInScene<T>(SceneManager.GetActiveScene(), stopOnMatch);
  740. }
  741. return results;
  742. }
  743. /// <summary>
  744. /// The FIndEvenInactiveComponentsInScene method searches the specified scene for components matching the type supplied.
  745. /// </summary>
  746. /// <param name="scene">The scene to search. This scene must be valid, either loaded or loading.</param>
  747. /// <param name="stopOnMatch">If true, will stop searching objects as soon as a match is found.</param>
  748. /// <returns></returns>
  749. private static IEnumerable<T> FindEvenInactiveComponentsInScene<T>(Scene scene, bool stopOnMatch = false)
  750. {
  751. List<T> results = new List<T>();
  752. if(!scene.isLoaded)
  753. {
  754. return results;
  755. }
  756. foreach (GameObject rootObject in scene.GetRootGameObjects())
  757. {
  758. if (stopOnMatch)
  759. {
  760. T foundComponent = rootObject.GetComponentInChildren<T>(true);
  761. if (foundComponent != null)
  762. {
  763. results.Add(foundComponent);
  764. return results;
  765. }
  766. }
  767. else
  768. {
  769. results.AddRange(rootObject.GetComponentsInChildren<T>(true));
  770. }
  771. }
  772. return results;
  773. }
  774. }
  775. }