#if !UNITY_2019_3_OR_NEWER #define CINEMACHINE_PHYSICS #endif using UnityEngine; using Cinemachine.Utility; namespace Cinemachine { /// /// Third-person follower, with complex pivoting: horizontal about the origin, /// vertical about the shoulder. /// [AddComponentMenu("")] // Don't display in add component menu [SaveDuringPlay] public class Cinemachine3rdPersonFollow : CinemachineComponentBase { /// How responsively the camera tracks the target. Each axis (camera-local) /// can have its own setting. Value is the approximate time it takes the camera /// to catch up to the target's new position. Smaller values give a more rigid /// effect, larger values give a squishier one. [Tooltip("How responsively the camera tracks the target. Each axis (camera-local) " + "can have its own setting. Value is the approximate time it takes the camera " + "to catch up to the target's new position. Smaller values give a more " + "rigid effect, larger values give a squishier one")] public Vector3 Damping; /// Position of the shoulder pivot relative to the Follow target origin. /// This offset is in target-local space. [Header("Rig")] [Tooltip("Position of the shoulder pivot relative to the Follow target origin. " + "This offset is in target-local space")] public Vector3 ShoulderOffset; /// Vertical offset of the hand in relation to the shoulder. /// Arm length will affect the follow target's screen position /// when the camera rotates vertically. [Tooltip("Vertical offset of the hand in relation to the shoulder. " + "Arm length will affect the follow target's screen position when " + "the camera rotates vertically")] public float VerticalArmLength; /// Specifies which shoulder (left, right, or in-between) the camera is on. [Tooltip("Specifies which shoulder (left, right, or in-between) the camera is on")] [Range(0, 1)] public float CameraSide; /// How far baehind the hand the camera will be placed. [Tooltip("How far baehind the hand the camera will be placed")] public float CameraDistance; #if CINEMACHINE_PHYSICS /// Camera will avoid obstacles on these layers. [Header("Obstacles")] [Tooltip("Camera will avoid obstacles on these layers")] public LayerMask CameraCollisionFilter; /// /// Obstacles with this tag will be ignored. It is a good idea /// to set this field to the target's tag /// [TagField] [Tooltip("Obstacles with this tag will be ignored. " + "It is a good idea to set this field to the target's tag")] public string IgnoreTag = string.Empty; /// /// Specifies how close the camera can get to obstacles /// [Tooltip("Specifies how close the camera can get to obstacles")] [Range(0, 1)] public float CameraRadius; /// /// How gradually the camera moves to correct for occlusions. /// Higher numbers will move the camera more gradually. /// [Range(0, 10)] [Tooltip("How gradually the camera moves to correct for occlusions. " + "Higher numbers will move the camera more gradually.")] public float DampingIntoCollision; /// /// How gradually the camera returns to its normal position after having been corrected by the built-in /// collision resolution system. Higher numbers will move the camera more gradually back to normal. /// [Range(0, 10)] [Tooltip("How gradually the camera returns to its normal position after having been corrected by the built-in " + "collision resolution system. Higher numbers will move the camera more gradually back to normal.")] public float DampingFromCollision; #endif // State info Vector3 m_PreviousFollowTargetPosition; Vector3 m_DampingCorrection; // this is in local rig space #if CINEMACHINE_PHYSICS float m_CamPosCollisionCorrection; #endif void OnValidate() { CameraSide = Mathf.Clamp(CameraSide, -1.0f, 1.0f); Damping.x = Mathf.Max(0, Damping.x); Damping.y = Mathf.Max(0, Damping.y); Damping.z = Mathf.Max(0, Damping.z); #if CINEMACHINE_PHYSICS CameraRadius = Mathf.Max(0.001f, CameraRadius); DampingIntoCollision = Mathf.Max(0, DampingIntoCollision); DampingFromCollision = Mathf.Max(0, DampingFromCollision); #endif } void Reset() { ShoulderOffset = new Vector3(0.5f, -0.4f, 0.0f); VerticalArmLength = 0.4f; CameraSide = 1.0f; CameraDistance = 2.0f; Damping = new Vector3(0.1f, 0.5f, 0.3f); #if CINEMACHINE_PHYSICS CameraCollisionFilter = 0; CameraRadius = 0.2f; DampingIntoCollision = 0; DampingFromCollision = 2f; #endif } #if CINEMACHINE_PHYSICS void OnDestroy() { RuntimeUtility.DestroyScratchCollider(); } #endif /// True if component is enabled and has a Follow target defined public override bool IsValid => enabled && FollowTarget != null; /// Get the Cinemachine Pipeline stage that this component implements. /// Always returns the Aim stage public override CinemachineCore.Stage Stage { get { return CinemachineCore.Stage.Body; } } #if CINEMACHINE_PHYSICS /// /// Report maximum damping time needed for this component. /// /// Highest damping setting in this component public override float GetMaxDampTime() { return Mathf.Max( Mathf.Max(DampingIntoCollision, DampingFromCollision), Mathf.Max(Damping.x, Mathf.Max(Damping.y, Damping.z))); } #endif /// Orients the camera to match the Follow target's orientation /// The current camera state /// Elapsed time since last frame, for damping calculations. /// If negative, previous state is reset. public override void MutateCameraState(ref CameraState curState, float deltaTime) { if (IsValid) { if (!VirtualCamera.PreviousStateIsValid) deltaTime = -1; PositionCamera(ref curState, deltaTime); } } /// This is called to notify the us that a target got warped, /// so that we can update its internal state to make the camera /// also warp seamlessy. /// The object that was warped /// The amount the target's position changed public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta) { base.OnTargetObjectWarped(target, positionDelta); if (target == FollowTarget) { m_PreviousFollowTargetPosition += positionDelta; } } void PositionCamera(ref CameraState curState, float deltaTime) { var up = curState.ReferenceUp; var targetPos = FollowTargetPosition; var targetRot = FollowTargetRotation; var targetForward = targetRot * Vector3.forward; var heading = GetHeading(targetRot, up); if (deltaTime < 0) { // No damping - reset damping state info m_DampingCorrection = Vector3.zero; #if CINEMACHINE_PHYSICS m_CamPosCollisionCorrection = 0; #endif } else { // Damping correction is applied to the shoulder offset - stretching the rig m_DampingCorrection += Quaternion.Inverse(heading) * (m_PreviousFollowTargetPosition - targetPos); m_DampingCorrection -= VirtualCamera.DetachedFollowTargetDamp(m_DampingCorrection, Damping, deltaTime); } m_PreviousFollowTargetPosition = targetPos; var root = targetPos; GetRawRigPositions(root, targetRot, heading, out _, out Vector3 hand); // Place the camera at the correct distance from the hand var camPos = hand - (targetForward * (CameraDistance - m_DampingCorrection.z)); #if CINEMACHINE_PHYSICS // Check if hand is colliding with something, if yes, then move the hand // closer to the player. The radius is slightly enlarged, to avoid problems // next to walls float dummy = 0; var collidedHand = ResolveCollisions(root, hand, -1, CameraRadius * 1.05f, ref dummy); camPos = ResolveCollisions( collidedHand, camPos, deltaTime, CameraRadius, ref m_CamPosCollisionCorrection); #endif // Set state curState.RawPosition = camPos; curState.RawOrientation = targetRot; // not necessary, but left in to avoid breaking scenes that depend on this } /// /// Internal use only. Public for the inspector gizmo /// /// Root of the rig. /// Shoulder of the rig. /// Hand of the rig. public void GetRigPositions(out Vector3 root, out Vector3 shoulder, out Vector3 hand) { var up = VirtualCamera.State.ReferenceUp; var targetRot = FollowTargetRotation; var heading = GetHeading(targetRot, up); root = m_PreviousFollowTargetPosition; GetRawRigPositions(root, targetRot, heading, out shoulder, out hand); #if CINEMACHINE_PHYSICS float dummy = 0; hand = ResolveCollisions(root, hand, -1, CameraRadius * 1.05f, ref dummy); #endif } internal static Quaternion GetHeading(Quaternion targetRot, Vector3 up) { var targetForward = targetRot * Vector3.forward; var planeForward = Vector3.Cross(up, Vector3.Cross(targetForward.ProjectOntoPlane(up), up)); if (planeForward.AlmostZero()) planeForward = Vector3.Cross(targetRot * Vector3.right, up); return Quaternion.LookRotation(planeForward, up); } void GetRawRigPositions( Vector3 root, Quaternion targetRot, Quaternion heading, out Vector3 shoulder, out Vector3 hand) { var shoulderOffset = ShoulderOffset; shoulderOffset.x = Mathf.Lerp(-shoulderOffset.x, shoulderOffset.x, CameraSide); shoulderOffset.x += m_DampingCorrection.x; shoulderOffset.y += m_DampingCorrection.y; shoulder = root + heading * shoulderOffset; hand = shoulder + targetRot * new Vector3(0, VerticalArmLength, 0); } #if CINEMACHINE_PHYSICS Vector3 ResolveCollisions( Vector3 root, Vector3 tip, float deltaTime, float cameraRadius, ref float collisionCorrection) { if (CameraCollisionFilter.value == 0) { return tip; } var dir = tip - root; var len = dir.magnitude; dir /= len; var result = tip; float desiredCorrection = 0; if (RuntimeUtility.SphereCastIgnoreTag( root, cameraRadius, dir, out RaycastHit hitInfo, len, CameraCollisionFilter, IgnoreTag)) { var desiredResult = hitInfo.point + hitInfo.normal * cameraRadius; desiredCorrection = (desiredResult - tip).magnitude; } collisionCorrection += deltaTime < 0 ? desiredCorrection - collisionCorrection : Damper.Damp( desiredCorrection - collisionCorrection, desiredCorrection > collisionCorrection ? DampingIntoCollision : DampingFromCollision, deltaTime); // Apply the correction if (collisionCorrection > Epsilon) result -= dir * collisionCorrection; return result; } #endif } }