#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
}
}