RuntimeUtility.cs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. #if !UNITY_2019_3_OR_NEWER
  2. #define CINEMACHINE_PHYSICS
  3. #define CINEMACHINE_PHYSICS_2D
  4. #endif
  5. using UnityEngine;
  6. namespace Cinemachine
  7. {
  8. /// <summary>An ad-hoc collection of helpers, used by Cinemachine
  9. /// or its editor tools in various places</summary>
  10. [DocumentationSorting(DocumentationSortingAttribute.Level.Undoc)]
  11. public static class RuntimeUtility
  12. {
  13. /// <summary>Convenience to destroy an object, using the appropriate method depending
  14. /// on whether the game is playing</summary>
  15. /// <param name="obj">The object to destroy</param>
  16. public static void DestroyObject(UnityEngine.Object obj)
  17. {
  18. if (obj != null)
  19. {
  20. #if UNITY_EDITOR
  21. if (Application.isPlaying)
  22. UnityEngine.Object.Destroy(obj);
  23. else
  24. UnityEngine.Object.DestroyImmediate(obj);
  25. #else
  26. UnityEngine.Object.Destroy(obj);
  27. #endif
  28. }
  29. }
  30. /// <summary>
  31. /// Check whether a GameObject is a prefab.
  32. /// For editor only - some things are disallowed if prefab. In runtime, will always return false.
  33. /// </summary>
  34. /// <param name="gameObject">the object to check</param>
  35. /// <returns>If editor, checks if object is a prefab or prefab instance.
  36. /// In runtime, returns false always</returns>
  37. public static bool IsPrefab(GameObject gameObject)
  38. {
  39. #if UNITY_EDITOR
  40. return UnityEditor.PrefabUtility.GetPrefabInstanceStatus(gameObject)
  41. != UnityEditor.PrefabInstanceStatus.NotAPrefab;
  42. #else
  43. return false;
  44. #endif
  45. }
  46. #if CINEMACHINE_PHYSICS
  47. private static RaycastHit[] s_HitBuffer = new RaycastHit[16];
  48. private static int[] s_PenetrationIndexBuffer = new int[16];
  49. /// <summary>
  50. /// Perform a raycast, but pass through any objects that have a given tag
  51. /// </summary>
  52. /// <param name="ray">The ray to cast</param>
  53. /// <param name="hitInfo">The returned results</param>
  54. /// <param name="rayLength">Length of the raycast</param>
  55. /// <param name="layerMask">Layers to include</param>
  56. /// <param name="ignoreTag">Tag to ignore</param>
  57. /// <returns>True if something was hit. Results in hitInfo</returns>
  58. public static bool RaycastIgnoreTag(
  59. Ray ray, out RaycastHit hitInfo, float rayLength, int layerMask, in string ignoreTag)
  60. {
  61. if (ignoreTag.Length == 0)
  62. {
  63. if (Physics.Raycast(
  64. ray, out hitInfo, rayLength, layerMask,
  65. QueryTriggerInteraction.Ignore))
  66. {
  67. return true;
  68. }
  69. }
  70. else
  71. {
  72. int closestHit = -1;
  73. int numHits = Physics.RaycastNonAlloc(
  74. ray, s_HitBuffer, rayLength, layerMask, QueryTriggerInteraction.Ignore);
  75. for (int i = 0; i < numHits; ++i)
  76. {
  77. if (s_HitBuffer[i].collider.CompareTag(ignoreTag))
  78. continue;
  79. if (closestHit < 0 || s_HitBuffer[i].distance < s_HitBuffer[closestHit].distance)
  80. closestHit = i;
  81. }
  82. if (closestHit >= 0)
  83. {
  84. hitInfo = s_HitBuffer[closestHit];
  85. if (numHits == s_HitBuffer.Length)
  86. s_HitBuffer = new RaycastHit[s_HitBuffer.Length * 2]; // full! grow for next time
  87. return true;
  88. }
  89. }
  90. hitInfo = new RaycastHit();
  91. return false;
  92. }
  93. /// <summary>
  94. /// Perform a sphere cast, but pass through objects with a given tag
  95. /// </summary>
  96. /// <param name="rayStart">Start of the ray</param>
  97. /// <param name="radius">Radius of the sphere cast</param>
  98. /// <param name="dir">Normalized direction of the ray</param>
  99. /// <param name="hitInfo">Results go here</param>
  100. /// <param name="rayLength">Length of the ray</param>
  101. /// <param name="layerMask">Layers to include</param>
  102. /// <param name="ignoreTag">Tag to ignore</param>
  103. /// <returns>True if something is hit. Results in hitInfo.</returns>
  104. public static bool SphereCastIgnoreTag(
  105. Vector3 rayStart, float radius, Vector3 dir,
  106. out RaycastHit hitInfo, float rayLength,
  107. int layerMask, in string ignoreTag)
  108. {
  109. int closestHit = -1;
  110. int numPenetrations = 0;
  111. float penetrationDistanceSum = 0;
  112. int numHits = Physics.SphereCastNonAlloc(
  113. rayStart, radius, dir, s_HitBuffer, rayLength, layerMask,
  114. QueryTriggerInteraction.Ignore);
  115. for (int i = 0; i < numHits; ++i)
  116. {
  117. var h = s_HitBuffer[i];
  118. if (ignoreTag.Length > 0 && h.collider.CompareTag(ignoreTag))
  119. continue;
  120. // Collect overlapping items
  121. if (h.distance == 0 && h.normal == -dir)
  122. {
  123. if (s_PenetrationIndexBuffer.Length > numPenetrations + 1)
  124. s_PenetrationIndexBuffer[numPenetrations++] = i;
  125. // hitInfo for overlapping colliders will have special
  126. // values that are not helpful to the caller. Fix that here.
  127. var scratchCollider = GetScratchCollider();
  128. scratchCollider.radius = radius;
  129. var c = h.collider;
  130. if (Physics.ComputePenetration(
  131. scratchCollider, rayStart, Quaternion.identity,
  132. c, c.transform.position, c.transform.rotation,
  133. out var offsetDir, out var offsetDistance))
  134. {
  135. h.point = rayStart + offsetDir * (offsetDistance - radius);
  136. h.distance = offsetDistance - radius; // will be -ve
  137. h.normal = offsetDir;
  138. s_HitBuffer[i] = h;
  139. penetrationDistanceSum += h.distance;
  140. }
  141. else
  142. {
  143. continue; // don't know what's going on, just forget about it
  144. }
  145. }
  146. if (closestHit < 0 || h.distance < s_HitBuffer[closestHit].distance)
  147. {
  148. closestHit = i;
  149. }
  150. }
  151. // Naively combine penetrating items
  152. if (numPenetrations > 1)
  153. {
  154. hitInfo = new RaycastHit();
  155. for (int i = 0; i < numPenetrations; ++i)
  156. {
  157. var h = s_HitBuffer[s_PenetrationIndexBuffer[i]];
  158. var t = h.distance / penetrationDistanceSum;
  159. hitInfo.point += h.point * t;
  160. hitInfo.distance += h.distance * t;
  161. hitInfo.normal += h.normal * t;
  162. }
  163. hitInfo.normal = hitInfo.normal.normalized;
  164. return true;
  165. }
  166. if (closestHit >= 0)
  167. {
  168. hitInfo = s_HitBuffer[closestHit];
  169. if (numHits == s_HitBuffer.Length)
  170. s_HitBuffer = new RaycastHit[s_HitBuffer.Length * 2]; // full! grow for next time
  171. return true;
  172. }
  173. hitInfo = new RaycastHit();
  174. return false;
  175. }
  176. private static SphereCollider s_ScratchCollider;
  177. private static GameObject s_ScratchColliderGameObject;
  178. internal static SphereCollider GetScratchCollider()
  179. {
  180. if (s_ScratchColliderGameObject == null)
  181. {
  182. s_ScratchColliderGameObject = new GameObject("Cinemachine Scratch Collider");
  183. s_ScratchColliderGameObject.hideFlags = HideFlags.HideAndDontSave;
  184. s_ScratchColliderGameObject.transform.position = Vector3.zero;
  185. s_ScratchColliderGameObject.SetActive(true);
  186. s_ScratchCollider = s_ScratchColliderGameObject.AddComponent<SphereCollider>();
  187. s_ScratchCollider.isTrigger = true;
  188. var rb = s_ScratchColliderGameObject.AddComponent<Rigidbody>();
  189. rb.detectCollisions = false;
  190. rb.isKinematic = true;
  191. }
  192. return s_ScratchCollider;
  193. }
  194. internal static void DestroyScratchCollider()
  195. {
  196. if (s_ScratchColliderGameObject != null)
  197. {
  198. s_ScratchColliderGameObject.SetActive(false);
  199. DestroyObject(s_ScratchColliderGameObject.GetComponent<Rigidbody>());
  200. }
  201. DestroyObject(s_ScratchCollider);
  202. DestroyObject(s_ScratchColliderGameObject);
  203. s_ScratchColliderGameObject = null;
  204. s_ScratchCollider = null;
  205. }
  206. #endif
  207. }
  208. }