Cinemachine3rdPersonFollow.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. #if !UNITY_2019_3_OR_NEWER
  2. #define CINEMACHINE_PHYSICS
  3. #endif
  4. using UnityEngine;
  5. using Cinemachine.Utility;
  6. namespace Cinemachine
  7. {
  8. /// <summary>
  9. /// Third-person follower, with complex pivoting: horizontal about the origin,
  10. /// vertical about the shoulder.
  11. /// </summary>
  12. [AddComponentMenu("")] // Don't display in add component menu
  13. [SaveDuringPlay]
  14. public class Cinemachine3rdPersonFollow : CinemachineComponentBase
  15. {
  16. /// <summary>How responsively the camera tracks the target. Each axis (camera-local)
  17. /// can have its own setting. Value is the approximate time it takes the camera
  18. /// to catch up to the target's new position. Smaller values give a more rigid
  19. /// effect, larger values give a squishier one.</summary>
  20. [Tooltip("How responsively the camera tracks the target. Each axis (camera-local) "
  21. + "can have its own setting. Value is the approximate time it takes the camera "
  22. + "to catch up to the target's new position. Smaller values give a more "
  23. + "rigid effect, larger values give a squishier one")]
  24. public Vector3 Damping;
  25. /// <summary>Position of the shoulder pivot relative to the Follow target origin.
  26. /// This offset is in target-local space.</summary>
  27. [Header("Rig")]
  28. [Tooltip("Position of the shoulder pivot relative to the Follow target origin. "
  29. + "This offset is in target-local space")]
  30. public Vector3 ShoulderOffset;
  31. /// <summary>Vertical offset of the hand in relation to the shoulder.
  32. /// Arm length will affect the follow target's screen position
  33. /// when the camera rotates vertically.</summary>
  34. [Tooltip("Vertical offset of the hand in relation to the shoulder. "
  35. + "Arm length will affect the follow target's screen position when "
  36. + "the camera rotates vertically")]
  37. public float VerticalArmLength;
  38. /// <summary>Specifies which shoulder (left, right, or in-between) the camera is on.</summary>
  39. [Tooltip("Specifies which shoulder (left, right, or in-between) the camera is on")]
  40. [Range(0, 1)]
  41. public float CameraSide;
  42. /// <summary>How far behind the hand the camera will be placed.</summary>
  43. [Tooltip("How far behind the hand the camera will be placed")]
  44. public float CameraDistance;
  45. #if CINEMACHINE_PHYSICS
  46. /// <summary>Camera will avoid obstacles on these layers.</summary>
  47. [Header("Obstacles")]
  48. [Tooltip("Camera will avoid obstacles on these layers")]
  49. public LayerMask CameraCollisionFilter;
  50. /// <summary>
  51. /// Obstacles with this tag will be ignored. It is a good idea
  52. /// to set this field to the target's tag
  53. /// </summary>
  54. [TagField]
  55. [Tooltip("Obstacles with this tag will be ignored. "
  56. + "It is a good idea to set this field to the target's tag")]
  57. public string IgnoreTag = string.Empty;
  58. /// <summary>
  59. /// Specifies how close the camera can get to obstacles
  60. /// </summary>
  61. [Tooltip("Specifies how close the camera can get to obstacles")]
  62. [Range(0, 1)]
  63. public float CameraRadius;
  64. /// <summary>
  65. /// How gradually the camera moves to correct for occlusions.
  66. /// Higher numbers will move the camera more gradually.
  67. /// </summary>
  68. [Range(0, 10)]
  69. [Tooltip("How gradually the camera moves to correct for occlusions. " +
  70. "Higher numbers will move the camera more gradually.")]
  71. public float DampingIntoCollision;
  72. /// <summary>
  73. /// How gradually the camera returns to its normal position after having been corrected by the built-in
  74. /// collision resolution system. Higher numbers will move the camera more gradually back to normal.
  75. /// </summary>
  76. [Range(0, 10)]
  77. [Tooltip("How gradually the camera returns to its normal position after having been corrected by the built-in " +
  78. "collision resolution system. Higher numbers will move the camera more gradually back to normal.")]
  79. public float DampingFromCollision;
  80. #endif
  81. // State info
  82. Vector3 m_PreviousFollowTargetPosition;
  83. Vector3 m_DampingCorrection; // this is in local rig space
  84. #if CINEMACHINE_PHYSICS
  85. float m_CamPosCollisionCorrection;
  86. #endif
  87. void OnValidate()
  88. {
  89. CameraSide = Mathf.Clamp(CameraSide, -1.0f, 1.0f);
  90. Damping.x = Mathf.Max(0, Damping.x);
  91. Damping.y = Mathf.Max(0, Damping.y);
  92. Damping.z = Mathf.Max(0, Damping.z);
  93. #if CINEMACHINE_PHYSICS
  94. CameraRadius = Mathf.Max(0.001f, CameraRadius);
  95. DampingIntoCollision = Mathf.Max(0, DampingIntoCollision);
  96. DampingFromCollision = Mathf.Max(0, DampingFromCollision);
  97. #endif
  98. }
  99. void Reset()
  100. {
  101. ShoulderOffset = new Vector3(0.5f, -0.4f, 0.0f);
  102. VerticalArmLength = 0.4f;
  103. CameraSide = 1.0f;
  104. CameraDistance = 2.0f;
  105. Damping = new Vector3(0.1f, 0.5f, 0.3f);
  106. #if CINEMACHINE_PHYSICS
  107. CameraCollisionFilter = 0;
  108. CameraRadius = 0.2f;
  109. DampingIntoCollision = 0;
  110. DampingFromCollision = 2f;
  111. #endif
  112. }
  113. #if CINEMACHINE_PHYSICS
  114. void OnDestroy()
  115. {
  116. RuntimeUtility.DestroyScratchCollider();
  117. }
  118. #endif
  119. /// <summary>True if component is enabled and has a Follow target defined</summary>
  120. public override bool IsValid => enabled && FollowTarget != null;
  121. /// <summary>Get the Cinemachine Pipeline stage that this component implements.
  122. /// Always returns the Aim stage</summary>
  123. public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Body; } }
  124. #if CINEMACHINE_PHYSICS
  125. /// <summary>
  126. /// Report maximum damping time needed for this component.
  127. /// </summary>
  128. /// <returns>Highest damping setting in this component</returns>
  129. public override float GetMaxDampTime()
  130. {
  131. return Mathf.Max(
  132. Mathf.Max(DampingIntoCollision, DampingFromCollision),
  133. Mathf.Max(Damping.x, Mathf.Max(Damping.y, Damping.z)));
  134. }
  135. #endif
  136. /// <summary>Orients the camera to match the Follow target's orientation</summary>
  137. /// <param name="curState">The current camera state</param>
  138. /// <param name="deltaTime">Elapsed time since last frame, for damping calculations.
  139. /// If negative, previous state is reset.</param>
  140. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  141. {
  142. if (IsValid)
  143. {
  144. if (!VirtualCamera.PreviousStateIsValid)
  145. deltaTime = -1;
  146. PositionCamera(ref curState, deltaTime);
  147. }
  148. }
  149. /// <summary>This is called to notify the us that a target got warped,
  150. /// so that we can update its internal state to make the camera
  151. /// also warp seamlessy.</summary>
  152. /// <param name="target">The object that was warped</param>
  153. /// <param name="positionDelta">The amount the target's position changed</param>
  154. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  155. {
  156. base.OnTargetObjectWarped(target, positionDelta);
  157. if (target == FollowTarget)
  158. {
  159. m_PreviousFollowTargetPosition += positionDelta;
  160. }
  161. }
  162. void PositionCamera(ref CameraState curState, float deltaTime)
  163. {
  164. var up = curState.ReferenceUp;
  165. var targetPos = FollowTargetPosition;
  166. var targetRot = FollowTargetRotation;
  167. var targetForward = targetRot * Vector3.forward;
  168. var heading = GetHeading(targetRot, up);
  169. if (deltaTime < 0)
  170. {
  171. // No damping - reset damping state info
  172. m_DampingCorrection = Vector3.zero;
  173. #if CINEMACHINE_PHYSICS
  174. m_CamPosCollisionCorrection = 0;
  175. #endif
  176. }
  177. else
  178. {
  179. // Damping correction is applied to the shoulder offset - stretching the rig
  180. m_DampingCorrection += Quaternion.Inverse(heading) * (m_PreviousFollowTargetPosition - targetPos);
  181. m_DampingCorrection -= VirtualCamera.DetachedFollowTargetDamp(m_DampingCorrection, Damping, deltaTime);
  182. }
  183. m_PreviousFollowTargetPosition = targetPos;
  184. var root = targetPos;
  185. GetRawRigPositions(root, targetRot, heading, out _, out Vector3 hand);
  186. // Place the camera at the correct distance from the hand
  187. var camPos = hand - (targetForward * (CameraDistance - m_DampingCorrection.z));
  188. #if CINEMACHINE_PHYSICS
  189. // Check if hand is colliding with something, if yes, then move the hand
  190. // closer to the player. The radius is slightly enlarged, to avoid problems
  191. // next to walls
  192. float dummy = 0;
  193. var collidedHand = ResolveCollisions(root, hand, -1, CameraRadius * 1.05f, ref dummy);
  194. camPos = ResolveCollisions(
  195. collidedHand, camPos, deltaTime, CameraRadius, ref m_CamPosCollisionCorrection);
  196. #endif
  197. // Set state
  198. curState.RawPosition = camPos;
  199. curState.RawOrientation = targetRot; // not necessary, but left in to avoid breaking scenes that depend on this
  200. }
  201. /// <summary>
  202. /// Internal use only. Public for the inspector gizmo
  203. /// </summary>
  204. /// <param name="root">Root of the rig.</param>
  205. /// <param name="shoulder">Shoulder of the rig.</param>
  206. /// <param name="hand">Hand of the rig.</param>
  207. public void GetRigPositions(out Vector3 root, out Vector3 shoulder, out Vector3 hand)
  208. {
  209. var up = VirtualCamera.State.ReferenceUp;
  210. var targetRot = FollowTargetRotation;
  211. var heading = GetHeading(targetRot, up);
  212. root = m_PreviousFollowTargetPosition;
  213. GetRawRigPositions(root, targetRot, heading, out shoulder, out hand);
  214. #if CINEMACHINE_PHYSICS
  215. float dummy = 0;
  216. hand = ResolveCollisions(root, hand, -1, CameraRadius * 1.05f, ref dummy);
  217. #endif
  218. }
  219. internal static Quaternion GetHeading(Quaternion targetRot, Vector3 up)
  220. {
  221. var targetForward = targetRot * Vector3.forward;
  222. var planeForward = Vector3.Cross(up, Vector3.Cross(targetForward.ProjectOntoPlane(up), up));
  223. if (planeForward.AlmostZero())
  224. planeForward = Vector3.Cross(targetRot * Vector3.right, up);
  225. return Quaternion.LookRotation(planeForward, up);
  226. }
  227. void GetRawRigPositions(
  228. Vector3 root, Quaternion targetRot, Quaternion heading,
  229. out Vector3 shoulder, out Vector3 hand)
  230. {
  231. var shoulderOffset = ShoulderOffset;
  232. shoulderOffset.x = Mathf.Lerp(-shoulderOffset.x, shoulderOffset.x, CameraSide);
  233. shoulderOffset.x += m_DampingCorrection.x;
  234. shoulderOffset.y += m_DampingCorrection.y;
  235. shoulder = root + heading * shoulderOffset;
  236. hand = shoulder + targetRot * new Vector3(0, VerticalArmLength, 0);
  237. }
  238. #if CINEMACHINE_PHYSICS
  239. Vector3 ResolveCollisions(
  240. Vector3 root, Vector3 tip, float deltaTime,
  241. float cameraRadius, ref float collisionCorrection)
  242. {
  243. if (CameraCollisionFilter.value == 0)
  244. return tip;
  245. var dir = tip - root;
  246. var len = dir.magnitude;
  247. if (len < Epsilon)
  248. return tip;
  249. dir /= len;
  250. var result = tip;
  251. float desiredCorrection = 0;
  252. if (RuntimeUtility.SphereCastIgnoreTag(
  253. root, cameraRadius, dir, out RaycastHit hitInfo,
  254. len, CameraCollisionFilter, IgnoreTag))
  255. {
  256. var desiredResult = hitInfo.point + hitInfo.normal * cameraRadius;
  257. desiredCorrection = (desiredResult - tip).magnitude;
  258. }
  259. collisionCorrection += deltaTime < 0 ? desiredCorrection - collisionCorrection : Damper.Damp(
  260. desiredCorrection - collisionCorrection,
  261. desiredCorrection > collisionCorrection ? DampingIntoCollision : DampingFromCollision,
  262. deltaTime);
  263. // Apply the correction
  264. if (collisionCorrection > Epsilon)
  265. result -= dir * collisionCorrection;
  266. return result;
  267. }
  268. #endif
  269. }
  270. }