using UnityEngine; using Cinemachine.Utility; namespace Cinemachine { /// /// This is a CinemachineComponent in the Aim section of the component pipeline. /// Its job is to aim the camera at a target object, with configurable offsets, damping, /// and composition rules. /// /// In addition, if the target is a CinemachineTargetGroup, the behaviour /// will adjust the FOV and the camera distance to ensure that the entire group of targets /// is framed properly. /// [DocumentationSorting(4, DocumentationSortingAttribute.Level.UserRef)] [ExecuteInEditMode] // for OnGUI [AddComponentMenu("")] // Don't display in add component menu [RequireComponent(typeof(CinemachinePipeline))] [SaveDuringPlay] public class CinemachineGroupComposer : CinemachineComposer { /// How much of the screen to fill with the bounding box of the targets. [Space] [Tooltip("The bounding box of the targets should occupy this amount of the screen space. 1 means fill the whole screen. 0.5 means fill half the screen, etc.")] public float m_GroupFramingSize = 0.8f; /// What screen dimensions to consider when framing [DocumentationSorting(4.01f, DocumentationSortingAttribute.Level.UserRef)] public enum FramingMode { /// Consider only the horizontal dimension. Vertical framing is ignored. Horizontal, /// Consider only the vertical dimension. Horizontal framing is ignored. Vertical, /// The larger of the horizontal and vertical dimensions will dominate, to get the best fit. HorizontalAndVertical }; /// What screen dimensions to consider when framing [Tooltip("What screen dimensions to consider when framing. Can be Horizontal, Vertical, or both")] public FramingMode m_FramingMode = FramingMode.HorizontalAndVertical; /// How aggressively the camera tries to frame the group. /// Small numbers are more responsive [Range(0, 20)] [Tooltip("How aggressively the camera tries to frame the group. Small numbers are more responsive, rapidly adjusting the camera to keep the group in the frame. Larger numbers give a more heavy slowly responding camera.")] public float m_FrameDamping = 2f; /// How to adjust the camera to get the desired framing public enum AdjustmentMode { /// Do not move the camera, only adjust the FOV. ZoomOnly, /// Just move the camera, don't change the FOV. DollyOnly, /// Move the camera as much as permitted by the ranges, then /// adjust the FOV if necessary to make the shot. DollyThenZoom }; /// How to adjust the camera to get the desired framing [Tooltip("How to adjust the camera to get the desired framing. You can zoom, dolly in/out, or do both.")] public AdjustmentMode m_AdjustmentMode = AdjustmentMode.DollyThenZoom; /// How much closer to the target can the camera go? [Tooltip("The maximum distance toward the target that this behaviour is allowed to move the camera.")] public float m_MaxDollyIn = 5000f; /// How much farther from the target can the camera go? [Tooltip("The maximum distance away the target that this behaviour is allowed to move the camera.")] public float m_MaxDollyOut = 5000f; /// Set this to limit how close to the target the camera can get [Tooltip("Set this to limit how close to the target the camera can get.")] public float m_MinimumDistance = 1; /// Set this to limit how far from the taregt the camera can get [Tooltip("Set this to limit how far from the target the camera can get.")] public float m_MaximumDistance = 5000f; /// If adjusting FOV, will not set the FOV lower than this [Range(1, 179)] [Tooltip("If adjusting FOV, will not set the FOV lower than this.")] public float m_MinimumFOV = 3; /// If adjusting FOV, will not set the FOV higher than this [Range(1, 179)] [Tooltip("If adjusting FOV, will not set the FOV higher than this.")] public float m_MaximumFOV = 60; /// If adjusting Orthographic Size, will not set it lower than this [Tooltip("If adjusting Orthographic Size, will not set it lower than this.")] public float m_MinimumOrthoSize = 1; /// If adjusting Orthographic Size, will not set it higher than this [Tooltip("If adjusting Orthographic Size, will not set it higher than this.")] public float m_MaximumOrthoSize = 100; private void OnValidate() { m_GroupFramingSize = Mathf.Max(Epsilon, m_GroupFramingSize); m_MaxDollyIn = Mathf.Max(0, m_MaxDollyIn); m_MaxDollyOut = Mathf.Max(0, m_MaxDollyOut); m_MinimumDistance = Mathf.Max(0, m_MinimumDistance); m_MaximumDistance = Mathf.Max(m_MinimumDistance, m_MaximumDistance); m_MinimumFOV = Mathf.Max(1, m_MinimumFOV); m_MaximumFOV = Mathf.Clamp(m_MaximumFOV, m_MinimumFOV, 179); m_MinimumOrthoSize = Mathf.Max(0.01f, m_MinimumOrthoSize); m_MaximumOrthoSize = Mathf.Max(m_MinimumOrthoSize, m_MaximumOrthoSize); } /// Get LookAt target as CinemachineTargetGroup, or null if target is not a group public CinemachineTargetGroup TargetGroup { get { Transform lookAt = LookAtTarget; if (lookAt != null) return lookAt.GetComponent(); return null; } } /// Applies the composer rules and orients the camera accordingly /// The current camera state /// Used for calculating damping. If less than /// zero, then target will snap to the center of the dead zone. public override void MutateCameraState(ref CameraState curState, float deltaTime) { // Can't do anything without a group to look at CinemachineTargetGroup group = TargetGroup; if (group == null) { base.MutateCameraState(ref curState, deltaTime); return; } if (!IsValid || !curState.HasLookAt) { m_prevTargetHeight = 0; return; } curState.ReferenceLookAt = GetLookAtPointAndSetTrackedPoint(group.transform.position); Vector3 currentOffset = TrackedPoint - curState.RawPosition; float currentDistance = currentOffset.magnitude; if (currentDistance < Epsilon) return; // navel-gazing, get outa here //UnityEngine.Profiling.Profiler.BeginSample("CinemachineGroupComposer.MutateCameraState"); // Get the camera axis Vector3 fwd = currentOffset.AlmostZero() ? Vector3.forward : currentOffset.normalized; // Get the bounding box from that POV in view space, and find its width Bounds bounds = group.BoundingBox; m_lastBoundsMatrix = Matrix4x4.TRS( bounds.center - (fwd * bounds.extents.magnitude), Quaternion.LookRotation(fwd, curState.ReferenceUp), Vector3.one); m_LastBounds = group.GetViewSpaceBoundingBox(m_lastBoundsMatrix); float targetHeight = GetTargetHeight(m_LastBounds); Vector3 targetPos = m_lastBoundsMatrix.MultiplyPoint3x4(m_LastBounds.center); // Apply damping if (deltaTime >= 0) { float delta = targetHeight - m_prevTargetHeight; delta = Damper.Damp(delta, m_FrameDamping, deltaTime); targetHeight = m_prevTargetHeight + delta; } m_prevTargetHeight = targetHeight; // Move the camera if (!curState.Lens.Orthographic && m_AdjustmentMode != AdjustmentMode.ZoomOnly) { // What distance would be needed to get the target height, at the current FOV float currentFOV = curState.Lens.FieldOfView; float targetDistance = targetHeight / (2f * Mathf.Tan(currentFOV * Mathf.Deg2Rad / 2f)); // target the near surface of the bounding box float cameraDistance = targetDistance + m_LastBounds.extents.z; // Clamp to respect min/max distance settings cameraDistance = Mathf.Clamp( cameraDistance, currentDistance - m_MaxDollyIn, currentDistance + m_MaxDollyOut); cameraDistance = Mathf.Clamp(cameraDistance, m_MinimumDistance, m_MaximumDistance); // Apply curState.PositionCorrection += targetPos - fwd * cameraDistance - curState.RawPosition; } // Apply zoom if (curState.Lens.Orthographic || m_AdjustmentMode != AdjustmentMode.DollyOnly) { float nearBoundsDistance = (TrackedPoint - curState.CorrectedPosition).magnitude - m_LastBounds.extents.z; float currentFOV = 179; if (nearBoundsDistance > Epsilon) currentFOV = 2f * Mathf.Atan(targetHeight / (2 * nearBoundsDistance)) * Mathf.Rad2Deg; LensSettings lens = curState.Lens; lens.FieldOfView = Mathf.Clamp(currentFOV, m_MinimumFOV, m_MaximumFOV); lens.OrthographicSize = Mathf.Clamp(targetHeight / 2, m_MinimumOrthoSize, m_MaximumOrthoSize); curState.Lens = lens; } // Now compose normally base.MutateCameraState(ref curState, deltaTime); //UnityEngine.Profiling.Profiler.EndSample(); } float m_prevTargetHeight; // State for damping /// For editor visulaization of the calculated bounding box of the group public Bounds m_LastBounds { get; private set; } /// For editor visualization of the calculated bounding box of the group public Matrix4x4 m_lastBoundsMatrix { get; private set; } float GetTargetHeight(Bounds b) { float framingSize = Mathf.Max(Epsilon, m_GroupFramingSize); switch (m_FramingMode) { case FramingMode.Horizontal: return Mathf.Max(Epsilon, b.size.x )/ (framingSize * VcamState.Lens.Aspect); case FramingMode.Vertical: return Mathf.Max(Epsilon, b.size.y) / framingSize; default: case FramingMode.HorizontalAndVertical: return Mathf.Max( Mathf.Max(Epsilon, b.size.x) / (framingSize * VcamState.Lens.Aspect), Mathf.Max(Epsilon, b.size.y) / framingSize); } } } }