CinemachineTrackedDolly.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. using UnityEngine;
  2. using System;
  3. using Cinemachine.Utility;
  4. using UnityEngine.Serialization;
  5. namespace Cinemachine
  6. {
  7. /// <summary>
  8. /// A Cinemachine Virtual Camera Body component that constrains camera motion
  9. /// to a CinemachinePath. The camera can move along the path.
  10. ///
  11. /// This behaviour can operate in two modes: manual positioning, and Auto-Dolly positioning.
  12. /// In Manual mode, the camera's position is specified by animating the Path Position field.
  13. /// In Auto-Dolly mode, the Path Position field is animated automatically every frame by finding
  14. /// the position on the path that's closest to the virtual camera's Follow target.
  15. /// </summary>
  16. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  17. [AddComponentMenu("")] // Don't display in add component menu
  18. [SaveDuringPlay]
  19. public class CinemachineTrackedDolly : CinemachineComponentBase
  20. {
  21. /// <summary>The path to which the camera will be constrained. This must be non-null.</summary>
  22. [Tooltip("The path to which the camera will be constrained. This must be non-null.")]
  23. public CinemachinePathBase m_Path;
  24. /// <summary>The position along the path at which the camera will be placed.
  25. /// This can be animated directly, or set automatically by the Auto-Dolly feature
  26. /// to get as close as possible to the Follow target.</summary>
  27. [Tooltip("The position along the path at which the camera will be placed. "
  28. + "This can be animated directly, or set automatically by the Auto-Dolly feature to "
  29. + "get as close as possible to the Follow target. The value is interpreted "
  30. + "according to the Position Units setting.")]
  31. public float m_PathPosition;
  32. /// <summary>How to interpret the Path Position</summary>
  33. [Tooltip("How to interpret Path Position. If set to Path Units, values are as follows: "
  34. + "0 represents the first waypoint on the path, 1 is the second, and so on. Values "
  35. + "in-between are points on the path in between the waypoints. If set to Distance, "
  36. + "then Path Position represents distance along the path.")]
  37. public CinemachinePathBase.PositionUnits m_PositionUnits = CinemachinePathBase.PositionUnits.PathUnits;
  38. /// <summary>Where to put the camera realtive to the path postion. X is perpendicular
  39. /// to the path, Y is up, and Z is parallel to the path.</summary>
  40. [Tooltip("Where to put the camera relative to the path position. X is perpendicular "
  41. + "to the path, Y is up, and Z is parallel to the path. This allows the camera to "
  42. + "be offset from the path itself (as if on a tripod, for example).")]
  43. public Vector3 m_PathOffset = Vector3.zero;
  44. /// <summary>How aggressively the camera tries to maintain the offset perpendicular to the path.
  45. /// Small numbers are more responsive, rapidly translating the camera to keep the target's
  46. /// x-axis offset. Larger numbers give a more heavy slowly responding camera.
  47. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  48. [Range(0f, 20f)]
  49. [Tooltip("How aggressively the camera tries to maintain its position in a direction "
  50. + "perpendicular to the path. Small numbers are more responsive, rapidly translating "
  51. + "the camera to keep the target's x-axis offset. Larger numbers give a more heavy "
  52. + "slowly responding camera. Using different settings per axis can yield a wide range "
  53. + "of camera behaviors.")]
  54. public float m_XDamping = 0f;
  55. /// <summary>How aggressively the camera tries to maintain the offset in the path-local up direction.
  56. /// Small numbers are more responsive, rapidly translating the camera to keep the target's
  57. /// y-axis offset. Larger numbers give a more heavy slowly responding camera.
  58. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  59. [Range(0f, 20f)]
  60. [Tooltip("How aggressively the camera tries to maintain its position in the path-local up direction. "
  61. + "Small numbers are more responsive, rapidly translating the camera to keep the target's "
  62. + "y-axis offset. Larger numbers give a more heavy slowly responding camera. Using different "
  63. + "settings per axis can yield a wide range of camera behaviors.")]
  64. public float m_YDamping = 0f;
  65. /// <summary>How aggressively the camera tries to maintain the offset parallel to the path.
  66. /// Small numbers are more responsive, rapidly translating the camera to keep the
  67. /// target's z-axis offset. Larger numbers give a more heavy slowly responding camera.
  68. /// Using different settings per axis can yield a wide range of camera behaviors</summary>
  69. [Range(0f, 20f)]
  70. [Tooltip("How aggressively the camera tries to maintain its position in a direction parallel to the path. "
  71. + "Small numbers are more responsive, rapidly translating the camera to keep the target's z-axis offset. "
  72. + "Larger numbers give a more heavy slowly responding camera. Using different settings per axis "
  73. + "can yield a wide range of camera behaviors.")]
  74. public float m_ZDamping = 1f;
  75. /// <summary>Different ways to set the camera's up vector</summary>
  76. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  77. public enum CameraUpMode
  78. {
  79. /// <summary>Leave the camera's up vector alone. It will be set according to the Brain's WorldUp.</summary>
  80. Default,
  81. /// <summary>Take the up vector from the path's up vector at the current point</summary>
  82. Path,
  83. /// <summary>Take the up vector from the path's up vector at the current point, but with the roll zeroed out</summary>
  84. PathNoRoll,
  85. /// <summary>Take the up vector from the Follow target's up vector</summary>
  86. FollowTarget,
  87. /// <summary>Take the up vector from the Follow target's up vector, but with the roll zeroed out</summary>
  88. FollowTargetNoRoll,
  89. };
  90. /// <summary>How to set the virtual camera's Up vector. This will affect the screen composition.</summary>
  91. [Tooltip("How to set the virtual camera's Up vector. This will affect the screen composition, because "
  92. + "the camera Aim behaviours will always try to respect the Up direction.")]
  93. public CameraUpMode m_CameraUp = CameraUpMode.Default;
  94. /// <summary>"How aggressively the camera tries to track the target rotation's X angle.
  95. /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
  96. [Range(0f, 20f)]
  97. [Tooltip("How aggressively the camera tries to track the target rotation's X angle. Small numbers are "
  98. + "more responsive. Larger numbers give a more heavy slowly responding camera.")]
  99. public float m_PitchDamping = 0;
  100. /// <summary>How aggressively the camera tries to track the target rotation's Y angle.
  101. /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
  102. [Range(0f, 20f)]
  103. [Tooltip("How aggressively the camera tries to track the target rotation's Y angle. Small numbers are "
  104. + "more responsive. Larger numbers give a more heavy slowly responding camera.")]
  105. public float m_YawDamping = 0;
  106. /// <summary>How aggressively the camera tries to track the target rotation's Z angle.
  107. /// Small numbers are more responsive. Larger numbers give a more heavy slowly responding camera.</summary>
  108. [Range(0f, 20f)]
  109. [Tooltip("How aggressively the camera tries to track the target rotation's Z angle. Small numbers "
  110. + "are more responsive. Larger numbers give a more heavy slowly responding camera.")]
  111. public float m_RollDamping = 0f;
  112. /// <summary>Controls how automatic dollying occurs</summary>
  113. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  114. [Serializable]
  115. public struct AutoDolly
  116. {
  117. /// <summary>If checked, will enable automatic dolly, which chooses a path position
  118. /// that is as close as possible to the Follow target.</summary>
  119. [Tooltip("If checked, will enable automatic dolly, which chooses a path position that is as "
  120. + "close as possible to the Follow target. Note: this can have significant performance impact")]
  121. public bool m_Enabled;
  122. /// <summary>Offset, in current position units, from the closest point on the path to the follow target.</summary>
  123. [Tooltip("Offset, in current position units, from the closest point on the path to the follow target")]
  124. public float m_PositionOffset;
  125. /// <summary>Search up to this many waypoints on either side of the current position. Use 0 for Entire path</summary>
  126. [Tooltip("Search up to this many waypoints on either side of the current position. Use 0 for Entire path.")]
  127. public int m_SearchRadius;
  128. /// <summary>We search between waypoints by dividing the segment into this many straight pieces.
  129. /// The higher the number, the more accurate the result, but performance is
  130. /// proportionally slower for higher numbers</summary>
  131. [FormerlySerializedAs("m_StepsPerSegment")]
  132. [Tooltip("We search between waypoints by dividing the segment into this many straight pieces. "
  133. + "he higher the number, the more accurate the result, but performance is proportionally "
  134. + "slower for higher numbers")]
  135. public int m_SearchResolution;
  136. /// <summary>Constructor with specific field values</summary>
  137. /// <param name="enabled">Whether to enable automatic dolly</param>
  138. /// <param name="positionOffset">Offset, in current position units, from the closest point on the path to the follow target</param>
  139. /// <param name="searchRadius">Search up to this many waypoints on either side of the current position</param>
  140. /// <param name="stepsPerSegment">We search between waypoints by dividing the segment into this many straight pieces</param>
  141. public AutoDolly(bool enabled, float positionOffset, int searchRadius, int stepsPerSegment)
  142. {
  143. m_Enabled = enabled;
  144. m_PositionOffset = positionOffset;
  145. m_SearchRadius = searchRadius;
  146. m_SearchResolution = stepsPerSegment;
  147. }
  148. };
  149. /// <summary>Controls how automatic dollying occurs</summary>
  150. [Tooltip("Controls how automatic dollying occurs. A Follow target is necessary to use this feature.")]
  151. public AutoDolly m_AutoDolly = new AutoDolly(false, 0, 2, 5);
  152. /// <summary>True if component is enabled and has a path</summary>
  153. public override bool IsValid { get { return enabled && m_Path != null; } }
  154. /// <summary>Get the Cinemachine Pipeline stage that this component implements.
  155. /// Always returns the Body stage</summary>
  156. public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Body; } }
  157. /// <summary>
  158. /// Report maximum damping time needed for this component.
  159. /// </summary>
  160. /// <returns>Highest damping setting in this component</returns>
  161. public override float GetMaxDampTime()
  162. {
  163. var d2 = AngularDamping;
  164. var a = Mathf.Max(m_XDamping, Mathf.Max(m_YDamping, m_ZDamping));
  165. var b = Mathf.Max(d2.x, Mathf.Max(d2.y, d2.z));
  166. return Mathf.Max(a, b);
  167. }
  168. /// <summary>Positions the virtual camera according to the transposer rules.</summary>
  169. /// <param name="curState">The current camera state</param>
  170. /// <param name="deltaTime">Used for damping. If less that 0, no damping is done.</param>
  171. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  172. {
  173. // Init previous frame state info
  174. if (deltaTime < 0 || !VirtualCamera.PreviousStateIsValid)
  175. {
  176. m_PreviousPathPosition = m_PathPosition;
  177. m_PreviousCameraPosition = curState.RawPosition;
  178. m_PreviousOrientation = curState.RawOrientation;
  179. }
  180. if (!IsValid)
  181. return;
  182. // Get the new ideal path base position
  183. if (m_AutoDolly.m_Enabled && FollowTarget != null)
  184. {
  185. float prevPos = m_Path.ToNativePathUnits(m_PreviousPathPosition, m_PositionUnits);
  186. // This works in path units
  187. m_PathPosition = m_Path.FindClosestPoint(
  188. FollowTargetPosition,
  189. Mathf.FloorToInt(prevPos),
  190. (deltaTime < 0 || m_AutoDolly.m_SearchRadius <= 0)
  191. ? -1 : m_AutoDolly.m_SearchRadius,
  192. m_AutoDolly.m_SearchResolution);
  193. m_PathPosition = m_Path.FromPathNativeUnits(m_PathPosition, m_PositionUnits);
  194. // Apply the path position offset
  195. m_PathPosition += m_AutoDolly.m_PositionOffset;
  196. }
  197. float newPathPosition = m_PathPosition;
  198. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  199. {
  200. // Normalize previous position to find the shortest path
  201. float maxUnit = m_Path.MaxUnit(m_PositionUnits);
  202. if (maxUnit > 0)
  203. {
  204. float prev = m_Path.StandardizeUnit(m_PreviousPathPosition, m_PositionUnits);
  205. float next = m_Path.StandardizeUnit(newPathPosition, m_PositionUnits);
  206. if (m_Path.Looped && Mathf.Abs(next - prev) > maxUnit / 2)
  207. {
  208. if (next > prev)
  209. prev += maxUnit;
  210. else
  211. prev -= maxUnit;
  212. }
  213. m_PreviousPathPosition = prev;
  214. newPathPosition = next;
  215. }
  216. // Apply damping along the path direction
  217. float offset = m_PreviousPathPosition - newPathPosition;
  218. offset = Damper.Damp(offset, m_ZDamping, deltaTime);
  219. newPathPosition = m_PreviousPathPosition - offset;
  220. }
  221. m_PreviousPathPosition = newPathPosition;
  222. Quaternion newPathOrientation = m_Path.EvaluateOrientationAtUnit(newPathPosition, m_PositionUnits);
  223. // Apply the offset to get the new camera position
  224. Vector3 newCameraPos = m_Path.EvaluatePositionAtUnit(newPathPosition, m_PositionUnits);
  225. Vector3 offsetX = newPathOrientation * Vector3.right;
  226. Vector3 offsetY = newPathOrientation * Vector3.up;
  227. Vector3 offsetZ = newPathOrientation * Vector3.forward;
  228. newCameraPos += m_PathOffset.x * offsetX;
  229. newCameraPos += m_PathOffset.y * offsetY;
  230. newCameraPos += m_PathOffset.z * offsetZ;
  231. // Apply damping to the remaining directions
  232. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  233. {
  234. Vector3 currentCameraPos = m_PreviousCameraPosition;
  235. Vector3 delta = (currentCameraPos - newCameraPos);
  236. Vector3 delta1 = Vector3.Dot(delta, offsetY) * offsetY;
  237. Vector3 delta0 = delta - delta1;
  238. delta0 = Damper.Damp(delta0, m_XDamping, deltaTime);
  239. delta1 = Damper.Damp(delta1, m_YDamping, deltaTime);
  240. newCameraPos = currentCameraPos - (delta0 + delta1);
  241. }
  242. curState.RawPosition = m_PreviousCameraPosition = newCameraPos;
  243. // Set the orientation and up
  244. Quaternion newOrientation
  245. = GetCameraOrientationAtPathPoint(newPathOrientation, curState.ReferenceUp);
  246. if (deltaTime >= 0 && VirtualCamera.PreviousStateIsValid)
  247. {
  248. Vector3 relative = (Quaternion.Inverse(m_PreviousOrientation)
  249. * newOrientation).eulerAngles;
  250. for (int i = 0; i < 3; ++i)
  251. if (relative[i] > 180)
  252. relative[i] -= 360;
  253. relative = Damper.Damp(relative, AngularDamping, deltaTime);
  254. newOrientation = m_PreviousOrientation * Quaternion.Euler(relative);
  255. }
  256. m_PreviousOrientation = newOrientation;
  257. curState.RawOrientation = newOrientation;
  258. if (m_CameraUp != CameraUpMode.Default)
  259. curState.ReferenceUp = curState.RawOrientation * Vector3.up;
  260. }
  261. private Quaternion GetCameraOrientationAtPathPoint(Quaternion pathOrientation, Vector3 up)
  262. {
  263. switch (m_CameraUp)
  264. {
  265. default:
  266. case CameraUpMode.Default: break;
  267. case CameraUpMode.Path: return pathOrientation;
  268. case CameraUpMode.PathNoRoll:
  269. return Quaternion.LookRotation(pathOrientation * Vector3.forward, up);
  270. case CameraUpMode.FollowTarget:
  271. if (FollowTarget != null)
  272. return FollowTargetRotation;
  273. break;
  274. case CameraUpMode.FollowTargetNoRoll:
  275. if (FollowTarget != null)
  276. return Quaternion.LookRotation(FollowTargetRotation * Vector3.forward, up);
  277. break;
  278. }
  279. return Quaternion.LookRotation(VirtualCamera.transform.rotation * Vector3.forward, up);
  280. }
  281. private Vector3 AngularDamping
  282. {
  283. get
  284. {
  285. switch (m_CameraUp)
  286. {
  287. case CameraUpMode.PathNoRoll:
  288. case CameraUpMode.FollowTargetNoRoll:
  289. return new Vector3(m_PitchDamping, m_YawDamping, 0);
  290. case CameraUpMode.Default:
  291. return Vector3.zero;
  292. default:
  293. return new Vector3(m_PitchDamping, m_YawDamping, m_RollDamping);
  294. }
  295. }
  296. }
  297. private float m_PreviousPathPosition = 0;
  298. Quaternion m_PreviousOrientation = Quaternion.identity;
  299. private Vector3 m_PreviousCameraPosition = Vector3.zero;
  300. }
  301. }