using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Serialization; namespace Cinemachine { /// /// This behaviour is intended to be attached to an empty Transform GameObject, /// and it represents a Virtual Camera within the Unity scene. /// /// The Virtual Camera will animate its Transform according to the rules contained /// in its CinemachineComponent pipeline (Aim, Body, and Noise). When the virtual /// camera is Live, the Unity camera will assume the position and orientation /// of the virtual camera. /// /// A virtual camera is not a camera. Instead, it can be thought of as a camera controller, /// not unlike a cameraman. It can drive the Unity Camera and control its position, /// orientation, lens settings, and PostProcessing effects. Each Virtual Camera owns /// its own Cinemachine Component Pipeline, through which you provide the instructions /// for dynamically tracking specific game objects. /// /// A virtual camera is very lightweight, and does no rendering of its own. It merely /// tracks interesting GameObjects, and positions itself accordingly. A typical game /// can have dozens of virtual cameras, each set up to follow a particular character /// or capture a particular event. /// /// A Virtual Camera can be in any of three states: /// /// * **Live**: The virtual camera is actively controlling the Unity Camera. The /// virtual camera is tracking its targets and being updated every frame. /// * **Standby**: The virtual camera is tracking its targets and being updated /// every frame, but no Unity Camera is actively being controlled by it. This is /// the state of a virtual camera that is enabled in the scene but perhaps at a /// lower priority than the Live virtual camera. /// * **Disabled**: The virtual camera is present but disabled in the scene. It is /// not actively tracking its targets and so consumes no processing power. However, /// the virtual camera can be made live from the Timeline. /// /// The Unity Camera can be driven by any virtual camera in the scene. The game /// logic can choose the virtual camera to make live by manipulating the virtual /// cameras' enabled flags and their priorities, based on game logic. /// /// In order to be driven by a virtual camera, the Unity Camera must have a CinemachineBrain /// behaviour, which will select the most eligible virtual camera based on its priority /// or on other criteria, and will manage blending. /// /// /// /// /// /// [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)] [DisallowMultipleComponent] [ExecuteAlways] [ExcludeFromPreset] [AddComponentMenu("Cinemachine/CinemachineVirtualCamera")] [HelpURL(Documentation.BaseURL + "manual/CinemachineVirtualCamera.html")] public class CinemachineVirtualCamera : CinemachineVirtualCameraBase { /// The object that the camera wants to look at (the Aim target). /// The Aim component of the CinemachineComponent pipeline /// will refer to this target and orient the vcam in accordance with rules and /// settings that are provided to it. /// If this is null, then the vcam's Transform orientation will be used. [Tooltip("The object that the camera wants to look at (the Aim target). " + "If this is null, then the vcam's Transform orientation will define the camera's orientation.")] [NoSaveDuringPlay] [VcamTargetProperty] public Transform m_LookAt = null; /// The object that the camera wants to move with (the Body target). /// The Body component of the CinemachineComponent pipeline /// will refer to this target and position the vcam in accordance with rules and /// settings that are provided to it. /// If this is null, then the vcam's Transform position will be used. [Tooltip("The object that the camera wants to move with (the Body target). " + "If this is null, then the vcam's Transform position will define the camera's position.")] [NoSaveDuringPlay] [VcamTargetProperty] public Transform m_Follow = null; /// Specifies the LensSettings of this Virtual Camera. /// These settings will be transferred to the Unity camera when the vcam is live. [FormerlySerializedAs("m_LensAttributes")] [Tooltip("Specifies the lens properties of this Virtual Camera. This generally " + "mirrors the Unity Camera's lens settings, and will be used to drive the " + "Unity camera when the vcam is active.")] public LensSettings m_Lens = LensSettings.Default; /// Collection of parameters that influence how this virtual camera transitions from /// other virtual cameras public TransitionParams m_Transitions; /// Legacy support [SerializeField] [HideInInspector] [FormerlySerializedAs("m_BlendHint")] [FormerlySerializedAs("m_PositionBlending")] private BlendHint m_LegacyBlendHint; /// This is the name of the hidden GameObject that will be created as a child object /// of the virtual camera. This hidden game object acts as a container for the polymorphic /// CinemachineComponent pipeline. The Inspector UI for the Virtual Camera /// provides access to this pipleline, as do the CinemachineComponent-family of /// public methods in this class. /// The lifecycle of the pipeline GameObject is managed automatically. public const string PipelineName = "cm"; /// The CameraState object holds all of the information /// necessary to position the Unity camera. It is the output of this class. override public CameraState State { get { return m_State; } } /// Get the LookAt target for the Aim component in the Cinemachine pipeline. /// If this vcam is a part of a meta-camera collection, then the owner's target /// will be used if the local target is null. override public Transform LookAt { get { return ResolveLookAt(m_LookAt); } set { m_LookAt = value; } } /// Get the Follow target for the Body component in the Cinemachine pipeline. /// If this vcam is a part of a meta-camera collection, then the owner's target /// will be used if the local target is null. override public Transform Follow { get { return ResolveFollow(m_Follow); } set { m_Follow = value; } } /// /// Query components and extensions for the maximum damping time. /// /// Highest damping setting in this vcam public override float GetMaxDampTime() { float maxDamp = base.GetMaxDampTime(); UpdateComponentPipeline(); if (m_ComponentPipeline != null) for (int i = 0; i < m_ComponentPipeline.Length; ++i) maxDamp = Mathf.Max(maxDamp, m_ComponentPipeline[i].GetMaxDampTime()); return maxDamp; } /// Internal use only. Do not call this method. /// Called by CinemachineCore at the appropriate Update time /// so the vcam can position itself and track its targets. This class will /// invoke its pipeline and generate a CameraState for this frame. /// Effective world up /// Effective deltaTime override public void InternalUpdateCameraState(Vector3 worldUp, float deltaTime) { UpdateTargetCache(); // Update the state by invoking the component pipeline m_State = CalculateNewState(worldUp, deltaTime); ApplyPositionBlendMethod(ref m_State, m_Transitions.m_BlendHint); // Push the raw position back to the game object's transform, so it // moves along with the camera. if (!m_UserIsDragging) { if (Follow != null) transform.position = State.RawPosition; if (LookAt != null) transform.rotation = State.RawOrientation; } PreviousStateIsValid = true; } /// Make sure that the pipeline cache is up-to-date. override protected void OnEnable() { base.OnEnable(); m_State = PullStateFromVirtualCamera(Vector3.up, ref m_Lens); InvalidateComponentPipeline(); // Can't add components during OnValidate if (ValidatingStreamVersion < 20170927) { if (Follow != null && GetCinemachineComponent(CinemachineCore.Stage.Body) == null) AddCinemachineComponent(); if (LookAt != null && GetCinemachineComponent(CinemachineCore.Stage.Aim) == null) AddCinemachineComponent(); } } /// Calls the DestroyPipelineDelegate for destroying the hidden /// child object, to support undo. protected override void OnDestroy() { // Make the pipeline visible instead of destroying - this is to keep Undo happy foreach (Transform child in transform) if (child.GetComponent() != null) child.gameObject.hideFlags &= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector); base.OnDestroy(); } /// Enforce bounds for fields, when changed in inspector. protected override void OnValidate() { base.OnValidate(); m_Lens.Validate(); if (m_LegacyBlendHint != BlendHint.None) { m_Transitions.m_BlendHint = m_LegacyBlendHint; m_LegacyBlendHint = BlendHint.None; } } void OnTransformChildrenChanged() { InvalidateComponentPipeline(); } void Reset() { #if UNITY_EDITOR if (UnityEditor.PrefabUtility.GetPrefabInstanceStatus(gameObject) != UnityEditor.PrefabInstanceStatus.NotAPrefab) { Debug.Log("You cannot reset a prefab instance. " + "First disconnect this instance from the prefab, or enter Prefab Edit mode"); return; } #endif DestroyPipeline(); } /// /// Override component pipeline creation. /// This needs to be done by the editor to support Undo. /// The override must do exactly the same thing as the CreatePipeline method in this class. /// public static CreatePipelineDelegate CreatePipelineOverride; /// /// Override component pipeline creation. /// This needs to be done by the editor to support Undo. /// The override must do exactly the same thing as the CreatePipeline method in /// the CinemachineVirtualCamera class. /// public delegate Transform CreatePipelineDelegate( CinemachineVirtualCamera vcam, string name, CinemachineComponentBase[] copyFrom); /// /// Override component pipeline destruction. /// This needs to be done by the editor to support Undo. /// public static DestroyPipelineDelegate DestroyPipelineOverride; /// /// Override component pipeline destruction. /// This needs to be done by the editor to support Undo. /// public delegate void DestroyPipelineDelegate(GameObject pipeline); /// Destroy any existing pipeline container. private void DestroyPipeline() { List oldPipeline = new List(); foreach (Transform child in transform) if (child.GetComponent() != null) oldPipeline.Add(child); if (!RuntimeUtility.IsPrefab(gameObject)) { foreach (Transform child in oldPipeline) { if (DestroyPipelineOverride != null) DestroyPipelineOverride(child.gameObject); else Destroy(child.gameObject); } m_ComponentOwner = null; } PreviousStateIsValid = false; } /// Create a default pipeline container. /// Note: copyFrom only supported in Editor, not build private Transform CreatePipeline(CinemachineVirtualCamera copyFrom) { CinemachineComponentBase[] components = null; if (copyFrom != null) { copyFrom.InvalidateComponentPipeline(); // make sure it's up to date components = copyFrom.GetComponentPipeline(); } Transform newPipeline = null; if (CreatePipelineOverride != null) newPipeline = CreatePipelineOverride(this, PipelineName, components); else { GameObject go = new GameObject(PipelineName); go.transform.parent = transform; go.AddComponent(); newPipeline = go.transform; } PreviousStateIsValid = false; return newPipeline; } /// /// Editor API: Call this when changing the pipeline from the editor. /// Will force a rebuild of the pipeline cache. /// public void InvalidateComponentPipeline() { m_ComponentPipeline = null; } /// Get the hidden CinemachinePipeline child object. /// The hidden CinemachinePipeline child object public Transform GetComponentOwner() { UpdateComponentPipeline(); return m_ComponentOwner; } /// Get the component pipeline owned by the hidden child pipline container. /// For most purposes, it is preferable to use the GetCinemachineComponent method. /// The component pipeline public CinemachineComponentBase[] GetComponentPipeline() { UpdateComponentPipeline(); return m_ComponentPipeline; } /// Get the component set for a specific stage. /// The stage for which we want the component /// The Cinemachine component for that stage, or null if not defined public CinemachineComponentBase GetCinemachineComponent(CinemachineCore.Stage stage) { CinemachineComponentBase[] components = GetComponentPipeline(); if (components != null) foreach (var c in components) if (c.Stage == stage) return c; return null; } /// Get an existing component of a specific type from the cinemachine pipeline. /// The type of component to get /// The component if it's present, or null public T GetCinemachineComponent() where T : CinemachineComponentBase { CinemachineComponentBase[] components = GetComponentPipeline(); if (components != null) foreach (var c in components) if (c is T) return c as T; return null; } /// Add a component to the cinemachine pipeline. /// Existing components at the new component's stage are removed /// The type of component to add /// The new component public T AddCinemachineComponent() where T : CinemachineComponentBase { // Get the existing components Transform owner = GetComponentOwner(); if (owner == null) return null; // maybe it's a prefab CinemachineComponentBase[] components = owner.GetComponents(); T component = owner.gameObject.AddComponent(); if (component != null && components != null) { // Remove the existing components at that stage CinemachineCore.Stage stage = component.Stage; for (int i = components.Length - 1; i >= 0; --i) { if (components[i].Stage == stage) { components[i].enabled = false; RuntimeUtility.DestroyObject(components[i]); } } } InvalidateComponentPipeline(); return component; } /// Remove a component from the cinemachine pipeline if it's present. /// The type of component to remove public void DestroyCinemachineComponent() where T : CinemachineComponentBase { CinemachineComponentBase[] components = GetComponentPipeline(); if (components != null) { foreach (var c in components) { if (c is T) { c.enabled = false; RuntimeUtility.DestroyObject(c); InvalidateComponentPipeline(); } } } } /// API for the editor, to make the dragging of position handles behave better. internal bool m_UserIsDragging; CameraState m_State = CameraState.Default; // Current state this frame CinemachineComponentBase[] m_ComponentPipeline = null; [SerializeField][HideInInspector] private Transform m_ComponentOwner = null; // serialized to handle copy/paste void UpdateComponentPipeline() { bool isPrefab = RuntimeUtility.IsPrefab(gameObject); #if UNITY_EDITOR // Did we just get copy/pasted? if (m_ComponentOwner != null && m_ComponentOwner.parent != transform) { if (!isPrefab) // can't paste to a prefab { CinemachineVirtualCamera copyFrom = (m_ComponentOwner.parent != null) ? m_ComponentOwner.parent.gameObject.GetComponent() : null; DestroyPipeline(); m_ComponentOwner = CreatePipeline(copyFrom); } } if (m_ComponentOwner != null) SetFlagsForHiddenChild(m_ComponentOwner.gameObject); #endif // Early out if we're up-to-date if (m_ComponentOwner != null && m_ComponentPipeline != null) return; m_ComponentOwner = null; List list = new List(); foreach (Transform child in transform) { if (child.GetComponent() != null) { m_ComponentOwner = child; CinemachineComponentBase[] components = child.GetComponents(); foreach (CinemachineComponentBase c in components) if (c.enabled) list.Add(c); } } // Make sure we have a pipeline owner if (m_ComponentOwner == null && !isPrefab) m_ComponentOwner = CreatePipeline(null); // Make sure the pipeline stays hidden, even through prefab if (m_ComponentOwner != null) SetFlagsForHiddenChild(m_ComponentOwner.gameObject); if (m_ComponentOwner != null && m_ComponentOwner.gameObject != null) { // Sort the pipeline list.Sort((c1, c2) => (int)c1.Stage - (int)c2.Stage); m_ComponentPipeline = list.ToArray(); } } static internal void SetFlagsForHiddenChild(GameObject child) { if (child != null) { if (CinemachineCore.sShowHiddenObjects) child.hideFlags &= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector); else child.hideFlags |= (HideFlags.HideInHierarchy | HideFlags.HideInInspector); } } private Transform mCachedLookAtTarget; private CinemachineVirtualCameraBase mCachedLookAtTargetVcam; private CameraState CalculateNewState(Vector3 worldUp, float deltaTime) { FollowTargetAttachment = 1; LookAtTargetAttachment = 1; // Initialize the camera state, in case the game object got moved in the editor CameraState state = PullStateFromVirtualCamera(worldUp, ref m_Lens); Transform lookAtTarget = LookAt; if (lookAtTarget != mCachedLookAtTarget) { mCachedLookAtTarget = lookAtTarget; mCachedLookAtTargetVcam = null; if (lookAtTarget != null) mCachedLookAtTargetVcam = lookAtTarget.GetComponent(); } if (lookAtTarget != null) { if (mCachedLookAtTargetVcam != null) state.ReferenceLookAt = mCachedLookAtTargetVcam.State.FinalPosition; else state.ReferenceLookAt = TargetPositionCache.GetTargetPosition(lookAtTarget); } // Update the state by invoking the component pipeline UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC // Extensions first InvokePrePipelineMutateCameraStateCallback(this, ref state, deltaTime); // Then components if (m_ComponentPipeline == null) { state.BlendHint |= CameraState.BlendHintValue.IgnoreLookAtTarget; for (var stage = CinemachineCore.Stage.Body; stage <= CinemachineCore.Stage.Finalize; ++stage) InvokePostPipelineStageCallback(this, stage, ref state, deltaTime); } else { for (int i = 0; i < m_ComponentPipeline.Length; ++i) if (m_ComponentPipeline[i] != null) m_ComponentPipeline[i].PrePipelineMutateCameraState(ref state, deltaTime); int componentIndex = 0; CinemachineComponentBase postAimBody = null; for (var stage = CinemachineCore.Stage.Body; stage <= CinemachineCore.Stage.Finalize; ++stage) { var c = componentIndex < m_ComponentPipeline.Length ? m_ComponentPipeline[componentIndex] : null; if (c != null && stage == c.Stage) { ++componentIndex; if (stage == CinemachineCore.Stage.Body && c.BodyAppliesAfterAim) { postAimBody = c; continue; // do the body stage of the pipeline after Aim } c.MutateCameraState(ref state, deltaTime); } InvokePostPipelineStageCallback(this, stage, ref state, deltaTime); if (stage == CinemachineCore.Stage.Aim) { if (c == null) state.BlendHint |= CameraState.BlendHintValue.IgnoreLookAtTarget; // If we have saved a Body for after Aim, do it now if (postAimBody != null) { postAimBody.MutateCameraState(ref state, deltaTime); InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Body, ref state, deltaTime); } } } } return state; } /// This is called to notify the vcam that a target got warped, /// so that the vcam 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) { if (target == Follow) { transform.position += positionDelta; m_State.RawPosition += positionDelta; } UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC if (m_ComponentPipeline != null) { for (int i = 0; i < m_ComponentPipeline.Length; ++i) m_ComponentPipeline[i].OnTargetObjectWarped(target, positionDelta); } base.OnTargetObjectWarped(target, positionDelta); } /// /// Force the virtual camera to assume a given position and orientation /// /// Worldspace pposition to take /// Worldspace orientation to take public override void ForceCameraPosition(Vector3 pos, Quaternion rot) { PreviousStateIsValid = true; transform.position = pos; transform.rotation = rot; m_State.RawPosition = pos; m_State.RawOrientation = rot; UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC if (m_ComponentPipeline != null) for (int i = 0; i < m_ComponentPipeline.Length; ++i) m_ComponentPipeline[i].ForceCameraPosition(pos, rot); base.ForceCameraPosition(pos, rot); } // This is a hack for FreeLook rigs - to be removed internal void SetStateRawPosition(Vector3 pos) { m_State.RawPosition = pos; } /// If we are transitioning from another vcam, grab the position from it. /// The camera being deactivated. May be null. /// Default world Up, set by the CinemachineBrain /// Delta time for time-based effects (ignore if less than or equal to 0) public override void OnTransitionFromCamera( ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime) { base.OnTransitionFromCamera(fromCam, worldUp, deltaTime); InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime); bool forceUpdate = false; if (m_Transitions.m_InheritPosition && fromCam != null && !CinemachineCore.Instance.IsLiveInBlend(this)) ForceCameraPosition(fromCam.State.FinalPosition, fromCam.State.FinalOrientation); UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC if (m_ComponentPipeline != null) { for (int i = 0; i < m_ComponentPipeline.Length; ++i) if (m_ComponentPipeline[i].OnTransitionFromCamera( fromCam, worldUp, deltaTime, ref m_Transitions)) forceUpdate = true; } if (forceUpdate) { InternalUpdateCameraState(worldUp, deltaTime); InternalUpdateCameraState(worldUp, deltaTime); } else UpdateCameraState(worldUp, deltaTime); if (m_Transitions.m_OnCameraLive != null) m_Transitions.m_OnCameraLive.Invoke(this, fromCam); } /// /// Returns true, when the vcam has an extension or components that require input. /// internal override bool RequiresUserInput() { if (base.RequiresUserInput()) return true; return m_ComponentPipeline != null && m_ComponentPipeline.Any(c => c != null && c.RequiresUserInput); } // This prevents the sensor size from dirtying the scene in the event of aspect ratio change internal override void OnBeforeSerialize() { if (!m_Lens.IsPhysicalCamera) m_Lens.SensorSize = Vector2.one; } } }