Cinemachine3rdPersonFollow.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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 baehind the hand the camera will be placed.</summary>
  43. [Tooltip("How far baehind 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. #else
  65. // Keep here for code simplicity
  66. internal float CameraRadius;
  67. #endif
  68. // State info
  69. Vector3 m_PreviousFollowTargetPosition;
  70. Vector3 m_DampingCorrection; // this is in local rig space
  71. float m_CamPosCollisionCorrection;
  72. void OnValidate()
  73. {
  74. CameraSide = Mathf.Clamp(CameraSide, -1.0f, 1.0f);
  75. Damping.x = Mathf.Max(0, Damping.x);
  76. Damping.y = Mathf.Max(0, Damping.y);
  77. Damping.z = Mathf.Max(0, Damping.z);
  78. CameraRadius = Mathf.Max(0.001f, CameraRadius);
  79. }
  80. void Reset()
  81. {
  82. ShoulderOffset = new Vector3(0.5f, -0.4f, 0.0f);
  83. VerticalArmLength = 0.4f;
  84. CameraSide = 1.0f;
  85. CameraDistance = 2.0f;
  86. Damping = new Vector3(0.1f, 0.5f, 0.3f);
  87. #if CINEMACHINE_PHYSICS
  88. CameraCollisionFilter = 0;
  89. CameraRadius = 0.2f;
  90. #else
  91. CameraRadius = 0.02f;
  92. #endif
  93. }
  94. #if CINEMACHINE_PHYSICS
  95. void OnDestroy()
  96. {
  97. RuntimeUtility.DestroyScratchCollider();
  98. }
  99. #endif
  100. /// <summary>True if component is enabled and has a Follow target defined</summary>
  101. public override bool IsValid => enabled && FollowTarget != null;
  102. /// <summary>Get the Cinemachine Pipeline stage that this component implements.
  103. /// Always returns the Aim stage</summary>
  104. public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Body; } }
  105. /// <summary>
  106. /// Report maximum damping time needed for this component.
  107. /// </summary>
  108. /// <returns>Highest damping setting in this component</returns>
  109. public override float GetMaxDampTime()
  110. {
  111. return Mathf.Max(Damping.x, Mathf.Max(Damping.y, Damping.z));
  112. }
  113. /// <summary>Orients the camera to match the Follow target's orientation</summary>
  114. /// <param name="curState">The current camera state</param>
  115. /// <param name="deltaTime">Elapsed time since last frame, for damping calculations.
  116. /// If negative, previous state is reset.</param>
  117. public override void MutateCameraState(ref CameraState curState, float deltaTime)
  118. {
  119. if (IsValid)
  120. {
  121. if (!VirtualCamera.PreviousStateIsValid)
  122. deltaTime = -1;
  123. PositionCamera(ref curState, deltaTime);
  124. }
  125. }
  126. /// <summary>This is called to notify the us that a target got warped,
  127. /// so that we can update its internal state to make the camera
  128. /// also warp seamlessy.</summary>
  129. /// <param name="target">The object that was warped</param>
  130. /// <param name="positionDelta">The amount the target's position changed</param>
  131. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  132. {
  133. base.OnTargetObjectWarped(target, positionDelta);
  134. if (target == FollowTarget)
  135. {
  136. m_PreviousFollowTargetPosition += positionDelta;
  137. }
  138. }
  139. void PositionCamera(ref CameraState curState, float deltaTime)
  140. {
  141. var up = curState.ReferenceUp;
  142. var targetPos = FollowTargetPosition;
  143. var targetRot = FollowTargetRotation;
  144. var targetForward = targetRot * Vector3.forward;
  145. var heading = GetHeading(targetForward, up);
  146. if (deltaTime < 0)
  147. {
  148. // No damping - reset damping state info
  149. m_DampingCorrection = Vector3.zero;
  150. }
  151. else
  152. {
  153. // Damping correction is applied to the shoulder offset - stretching the rig
  154. m_DampingCorrection += Quaternion.Inverse(heading) * (m_PreviousFollowTargetPosition - targetPos);
  155. m_DampingCorrection -= VirtualCamera.DetachedFollowTargetDamp(m_DampingCorrection, Damping, deltaTime);
  156. }
  157. m_PreviousFollowTargetPosition = targetPos;
  158. var root = targetPos;
  159. GetRawRigPositions(root, targetRot, heading, out _, out Vector3 hand);
  160. // Check if hand is colliding with something, if yes, then move the hand
  161. // closer to the player. The radius is slightly enlarged, to avoid problems
  162. // next to walls
  163. var collidedHand = ResolveCollisions(root, hand, CameraRadius * 1.05f);
  164. // Place the camera at the correct distance from the hand
  165. Vector3 camPos = hand - (targetForward * (CameraDistance - m_DampingCorrection.z));
  166. camPos = ResolveCollisions(collidedHand, camPos, CameraRadius);
  167. // Set state
  168. curState.RawPosition = camPos;
  169. curState.RawOrientation = targetRot; // not necessary, but left in to avoid breaking scenes that depend on this
  170. curState.ReferenceUp = up;
  171. }
  172. /// <summary>
  173. /// Internal use only. Public for the inspector gizmo
  174. /// </summary>
  175. /// <param name="root">Root of the rig.</param>
  176. /// <param name="shoulder">Shoulder of the rig.</param>
  177. /// <param name="hand">Hand of the rig.</param>
  178. public void GetRigPositions(out Vector3 root, out Vector3 shoulder, out Vector3 hand)
  179. {
  180. var up = VirtualCamera.State.ReferenceUp;
  181. var targetRot = FollowTargetRotation;
  182. var targetForward = targetRot * Vector3.forward;
  183. var heading = GetHeading(targetForward, up);
  184. root = m_PreviousFollowTargetPosition;
  185. GetRawRigPositions(root, targetRot, heading, out shoulder, out hand);
  186. hand = ResolveCollisions(root, hand, CameraRadius * 1.05f);
  187. }
  188. Quaternion GetHeading(Vector3 targetForward, Vector3 up)
  189. {
  190. var planeForward = targetForward.ProjectOntoPlane(up);
  191. planeForward = Vector3.Cross(up, Vector3.Cross(planeForward, up));
  192. return Quaternion.LookRotation(planeForward, up);
  193. }
  194. void GetRawRigPositions(
  195. Vector3 root, Quaternion targetRot, Quaternion heading,
  196. out Vector3 shoulder, out Vector3 hand)
  197. {
  198. var shoulderPivotReflected = Vector3.Reflect(ShoulderOffset, Vector3.right);
  199. var shoulderOffset = Vector3.Lerp(shoulderPivotReflected, ShoulderOffset, CameraSide);
  200. shoulderOffset.x += m_DampingCorrection.x;
  201. shoulderOffset.y += m_DampingCorrection.y;
  202. shoulder = root + heading * shoulderOffset;
  203. hand = shoulder + targetRot * new Vector3(0, VerticalArmLength, 0);
  204. }
  205. Vector3 ResolveCollisions(Vector3 root, Vector3 tip, float cameraRadius)
  206. {
  207. #if CINEMACHINE_PHYSICS
  208. if (CameraCollisionFilter.value == 0)
  209. {
  210. return tip;
  211. }
  212. var dir = tip - root;
  213. var len = dir.magnitude;
  214. dir /= len;
  215. var result = tip;
  216. float desiredCorrection = 0;
  217. if (RuntimeUtility.SphereCastIgnoreTag(
  218. root, cameraRadius, dir, out RaycastHit hitInfo,
  219. len, CameraCollisionFilter, IgnoreTag))
  220. {
  221. var desiredResult = hitInfo.point + hitInfo.normal * cameraRadius;
  222. desiredCorrection = (desiredResult - tip).magnitude;
  223. }
  224. // Apply the correction
  225. if (desiredCorrection > Epsilon)
  226. result -= dir * desiredCorrection;
  227. return result;
  228. #else
  229. return tip;
  230. #endif
  231. }
  232. }
  233. }