CinemachineConfiner.cs 13 KB

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