CinemachineConfiner.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. #if !UNITY_2019_3_OR_NEWER
  2. #define CINEMACHINE_PHYSICS
  3. #define CINEMACHINE_PHYSICS_2D
  4. #endif
  5. using UnityEngine;
  6. using System.Collections.Generic;
  7. using Cinemachine.Utility;
  8. using System;
  9. namespace Cinemachine
  10. {
  11. #if CINEMACHINE_PHYSICS || CINEMACHINE_PHYSICS_2D
  12. /// <summary>
  13. /// An add-on module for Cinemachine Virtual Camera that post-processes
  14. /// the final position of the virtual camera. It will confine the virtual
  15. /// camera's position to the volume specified in the Bounding Volume field.
  16. /// </summary>
  17. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  18. [AddComponentMenu("")] // Hide in menu
  19. [SaveDuringPlay]
  20. #if UNITY_2018_3_OR_NEWER
  21. [ExecuteAlways]
  22. #else
  23. [ExecuteInEditMode]
  24. #endif
  25. [DisallowMultipleComponent]
  26. [HelpURL(Documentation.BaseURL + "manual/CinemachineConfiner.html")]
  27. public class CinemachineConfiner : CinemachineExtension
  28. {
  29. #if CINEMACHINE_PHYSICS && CINEMACHINE_PHYSICS_2D
  30. /// <summary>The confiner can operate using a 2D bounding shape or a 3D bounding volume</summary>
  31. public enum Mode
  32. {
  33. /// <summary>Use a 2D bounding shape, suitable for an orthographic camera</summary>
  34. Confine2D,
  35. /// <summary>Use a 3D bounding shape, suitable for perspective cameras</summary>
  36. Confine3D
  37. };
  38. /// <summary>The confiner can operate using a 2D bounding shape or a 3D bounding volume</summary>
  39. [Tooltip("The confiner can operate using a 2D bounding shape or a 3D bounding volume")]
  40. public Mode m_ConfineMode;
  41. #endif
  42. #if CINEMACHINE_PHYSICS
  43. /// <summary>The volume within which the camera is to be contained.</summary>
  44. [Tooltip("The volume within which the camera is to be contained")]
  45. public Collider m_BoundingVolume;
  46. #endif
  47. #if CINEMACHINE_PHYSICS_2D
  48. /// <summary>The 2D shape within which the camera is to be contained.</summary>
  49. [Tooltip("The 2D shape within which the camera is to be contained")]
  50. public Collider2D m_BoundingShape2D;
  51. private Collider2D m_BoundingShape2DCache;
  52. #endif
  53. /// <summary>If camera is orthographic, screen edges will be confined to the volume.</summary>
  54. [Tooltip("If camera is orthographic, screen edges will be confined to the volume. "
  55. + "If not checked, then only the camera center will be confined")]
  56. public bool m_ConfineScreenEdges = true;
  57. /// <summary>How gradually to return the camera to the bounding volume if it goes beyond the borders</summary>
  58. [Tooltip("How gradually to return the camera to the bounding volume if it goes beyond the borders. "
  59. + "Higher numbers are more gradual.")]
  60. [Range(0, 10)]
  61. public float m_Damping = 0;
  62. /// <summary>See whether the virtual camera has been moved by the confiner</summary>
  63. /// <param name="vcam">The virtual camera in question. This might be different from the
  64. /// virtual camera that owns the confiner, in the event that the camera has children</param>
  65. /// <returns>True if the virtual camera has been repositioned</returns>
  66. public bool CameraWasDisplaced(CinemachineVirtualCameraBase vcam)
  67. {
  68. return GetCameraDisplacementDistance(vcam) > 0;
  69. }
  70. /// <summary>See how far virtual camera has been moved by the confiner</summary>
  71. /// <param name="vcam">The virtual camera in question. This might be different from the
  72. /// virtual camera that owns the confiner, in the event that the camera has children</param>
  73. /// <returns>True if the virtual camera has been repositioned</returns>
  74. public float GetCameraDisplacementDistance(CinemachineVirtualCameraBase vcam)
  75. {
  76. return GetExtraState<VcamExtraState>(vcam).confinerDisplacement;
  77. }
  78. private void OnValidate()
  79. {
  80. m_Damping = Mathf.Max(0, m_Damping);
  81. }
  82. /// <summary>
  83. /// Called when connecting to a virtual camera
  84. /// </summary>
  85. /// <param name="connect">True if connecting, false if disconnecting</param>
  86. protected override void ConnectToVcam(bool connect)
  87. {
  88. base.ConnectToVcam(connect);
  89. }
  90. class VcamExtraState
  91. {
  92. public Vector3 m_previousDisplacement;
  93. public float confinerDisplacement;
  94. };
  95. /// <summary>Check if the bounding volume is defined</summary>
  96. public bool IsValid
  97. {
  98. get
  99. {
  100. #if CINEMACHINE_PHYSICS && !CINEMACHINE_PHYSICS_2D
  101. return m_BoundingVolume != null;
  102. #elif CINEMACHINE_PHYSICS_2D && !CINEMACHINE_PHYSICS
  103. return m_BoundingShape2D != null;
  104. #else
  105. return (m_ConfineMode == Mode.Confine3D && m_BoundingVolume != null)
  106. || (m_ConfineMode == Mode.Confine2D && m_BoundingShape2D != null);
  107. #endif
  108. }
  109. }
  110. /// <summary>
  111. /// Report maximum damping time needed for this component.
  112. /// </summary>
  113. /// <returns>Highest damping setting in this component</returns>
  114. public override float GetMaxDampTime()
  115. {
  116. return m_Damping;
  117. }
  118. /// <summary>
  119. /// Callback to do the camera confining
  120. /// </summary>
  121. /// <param name="vcam">The virtual camera being processed</param>
  122. /// <param name="stage">The current pipeline stage</param>
  123. /// <param name="state">The current virtual camera state</param>
  124. /// <param name="deltaTime">The current applicable deltaTime</param>
  125. protected override void PostPipelineStageCallback(
  126. CinemachineVirtualCameraBase vcam,
  127. CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
  128. {
  129. if (IsValid && stage == CinemachineCore.Stage.Body)
  130. {
  131. var extra = GetExtraState<VcamExtraState>(vcam);
  132. Vector3 displacement;
  133. if (m_ConfineScreenEdges && state.Lens.Orthographic)
  134. displacement = ConfineScreenEdges(vcam, ref state);
  135. else
  136. displacement = ConfinePoint(state.CorrectedPosition);
  137. if (m_Damping > 0 && deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  138. {
  139. Vector3 delta = displacement - extra.m_previousDisplacement;
  140. delta = Damper.Damp(delta, m_Damping, deltaTime);
  141. displacement = extra.m_previousDisplacement + delta;
  142. }
  143. extra.m_previousDisplacement = displacement;
  144. state.PositionCorrection += displacement;
  145. extra.confinerDisplacement = displacement.magnitude;
  146. }
  147. }
  148. private List<List<Vector2>> m_pathCache;
  149. private int m_pathTotalPointCount;
  150. /// <summary>Call this if the bounding shape's points change at runtime</summary>
  151. public void InvalidatePathCache()
  152. {
  153. #if CINEMACHINE_PHYSICS_2D
  154. m_pathCache = null;
  155. m_BoundingShape2DCache = null;
  156. #endif
  157. }
  158. bool ValidatePathCache()
  159. {
  160. #if CINEMACHINE_PHYSICS_2D
  161. if (m_BoundingShape2DCache != m_BoundingShape2D)
  162. {
  163. InvalidatePathCache();
  164. m_BoundingShape2DCache = m_BoundingShape2D;
  165. }
  166. Type colliderType = m_BoundingShape2D == null ? null: m_BoundingShape2D.GetType();
  167. if (colliderType == typeof(PolygonCollider2D))
  168. {
  169. PolygonCollider2D poly = m_BoundingShape2D as PolygonCollider2D;
  170. if (m_pathCache == null || m_pathCache.Count != poly.pathCount || m_pathTotalPointCount != poly.GetTotalPointCount())
  171. {
  172. m_pathCache = new List<List<Vector2>>();
  173. for (int i = 0; i < poly.pathCount; ++i)
  174. {
  175. Vector2[] path = poly.GetPath(i);
  176. List<Vector2> dst = new List<Vector2>();
  177. for (int j = 0; j < path.Length; ++j)
  178. dst.Add(path[j]);
  179. m_pathCache.Add(dst);
  180. }
  181. m_pathTotalPointCount = poly.GetTotalPointCount();
  182. }
  183. return true;
  184. }
  185. else if (colliderType == typeof(CompositeCollider2D))
  186. {
  187. CompositeCollider2D poly = m_BoundingShape2D as CompositeCollider2D;
  188. if (m_pathCache == null || m_pathCache.Count != poly.pathCount || m_pathTotalPointCount != poly.pointCount)
  189. {
  190. m_pathCache = new List<List<Vector2>>();
  191. Vector2[] path = new Vector2[poly.pointCount];
  192. var lossyScale = m_BoundingShape2D.transform.lossyScale;
  193. Vector2 revertCompositeColliderScale = new Vector2(
  194. 1f / lossyScale.x,
  195. 1f / lossyScale.y);
  196. for (int i = 0; i < poly.pathCount; ++i)
  197. {
  198. int numPoints = poly.GetPath(i, path);
  199. List<Vector2> dst = new List<Vector2>();
  200. for (int j = 0; j < numPoints; ++j)
  201. dst.Add(path[j] * revertCompositeColliderScale);
  202. m_pathCache.Add(dst);
  203. }
  204. m_pathTotalPointCount = poly.pointCount;
  205. }
  206. return true;
  207. }
  208. #endif
  209. InvalidatePathCache();
  210. return false;
  211. }
  212. private Vector3 ConfinePoint(Vector3 camPos)
  213. {
  214. #if CINEMACHINE_PHYSICS
  215. // 3D version
  216. #if CINEMACHINE_PHYSICS_2D
  217. if (m_ConfineMode == Mode.Confine3D)
  218. #endif
  219. return m_BoundingVolume.ClosestPoint(camPos) - camPos;
  220. #endif
  221. #if CINEMACHINE_PHYSICS_2D
  222. // 2D version
  223. Vector2 p = camPos;
  224. Vector2 closest = p;
  225. if (m_BoundingShape2D.OverlapPoint(camPos))
  226. return Vector3.zero;
  227. // Find the nearest point on the shape's boundary
  228. if (!ValidatePathCache())
  229. return Vector3.zero;
  230. float bestDistance = float.MaxValue;
  231. for (int i = 0; i < m_pathCache.Count; ++i)
  232. {
  233. int numPoints = m_pathCache[i].Count;
  234. if (numPoints > 0)
  235. {
  236. Vector2 v0 = m_BoundingShape2D.transform.TransformPoint(m_pathCache[i][numPoints - 1]);
  237. for (int j = 0; j < numPoints; ++j)
  238. {
  239. Vector2 v = m_BoundingShape2D.transform.TransformPoint(m_pathCache[i][j]);
  240. Vector2 c = Vector2.Lerp(v0, v, p.ClosestPointOnSegment(v0, v));
  241. float d = Vector2.SqrMagnitude(p - c);
  242. if (d < bestDistance)
  243. {
  244. bestDistance = d;
  245. closest = c;
  246. }
  247. v0 = v;
  248. }
  249. }
  250. }
  251. return closest - p;
  252. #endif
  253. }
  254. // Camera must be orthographic
  255. private Vector3 ConfineScreenEdges(CinemachineVirtualCameraBase vcam, ref CameraState state)
  256. {
  257. Quaternion rot = Quaternion.Inverse(state.CorrectedOrientation);
  258. float dy = state.Lens.OrthographicSize;
  259. float dx = dy * state.Lens.Aspect;
  260. Vector3 vx = (rot * Vector3.right) * dx;
  261. Vector3 vy = (rot * Vector3.up) * dy;
  262. Vector3 displacement = Vector3.zero;
  263. Vector3 camPos = state.CorrectedPosition;
  264. Vector3 lastD = Vector3.zero;
  265. const int kMaxIter = 12;
  266. for (int i = 0; i < kMaxIter; ++i)
  267. {
  268. Vector3 d = ConfinePoint((camPos - vy) - vx);
  269. if (d.AlmostZero())
  270. d = ConfinePoint((camPos + vy) + vx);
  271. if (d.AlmostZero())
  272. d = ConfinePoint((camPos - vy) + vx);
  273. if (d.AlmostZero())
  274. d = ConfinePoint((camPos + vy) - vx);
  275. if (d.AlmostZero())
  276. break;
  277. if ((d + lastD).AlmostZero())
  278. {
  279. displacement += d * 0.5f; // confiner too small: center it
  280. break;
  281. }
  282. displacement += d;
  283. camPos += d;
  284. lastD = d;
  285. }
  286. return displacement;
  287. }
  288. }
  289. #endif
  290. }