using Cinemachine.Utility; using System; using System.Collections.Generic; 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(1, DocumentationSortingAttribute.Level.UserRef)] [ExecuteInEditMode, DisallowMultipleComponent] [AddComponentMenu("Cinemachine/CinemachineVirtualCamera")] 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] 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] 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.")] [LensSettingsProperty] public LensSettings m_Lens = LensSettings.Default; /// 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 CinemachinePipeline. /// 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 CinemachinePipeline. /// 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; } } /// Called by CinemachineCore at LateUpdate time /// so the vcam can position itself and track its targets. This class will /// invoke its pipeline and generate a CameraState for this frame. override public void UpdateCameraState(Vector3 worldUp, float deltaTime) { //UnityEngine.Profiling.Profiler.BeginSample("CinemachineVirtualCamera.UpdateCameraState"); if (!PreviousStateIsValid) deltaTime = -1; // Reset the base camera state, in case the game object got moved in the editor if (deltaTime < 0) m_State = PullStateFromVirtualCamera(worldUp); // not in gameplay // Update the state by invoking the component pipeline m_State = CalculateNewState(worldUp, deltaTime); // Push the raw position back to the game object's transform, so it // moves along with the camera. if (!UserIsDragging) { if (Follow != null) transform.position = State.RawPosition; if (LookAt != null) transform.rotation = State.RawOrientation; } PreviousStateIsValid = true; //UnityEngine.Profiling.Profiler.EndSample(); } /// Make sure that the pipeline cache is up-to-date. override protected void OnEnable() { base.OnEnable(); 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(); } void OnTransformChildrenChanged() { InvalidateComponentPipeline(); } void Reset() { 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); 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. 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; // If copying, transfer the components if (components != null) foreach (Component c in components) ReflectionHelpers.CopyFields(c, go.AddComponent(c.GetType())); } 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. 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. 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. 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. public T AddCinemachineComponent() where T : CinemachineComponentBase { // Get the existing components Transform owner = GetComponentOwner(); 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; DestroyImmediate(components[i]); } } } InvalidateComponentPipeline(); return component; } /// Remove a component from the cinemachine pipeline. public void DestroyCinemachineComponent() where T : CinemachineComponentBase { CinemachineComponentBase[] components = GetComponentPipeline(); if (components != null) { foreach (var c in components) { if (c is T) { c.enabled = false; DestroyImmediate(c); InvalidateComponentPipeline(); } } } } /// API for the editor, to make the dragging of position handles behave better. public bool UserIsDragging { get; set; } /// API for the editor, to process a position drag from the user. public void OnPositionDragged(Vector3 delta) { CinemachineComponentBase[] components = GetComponentPipeline(); if (components != null) for (int i = 0; i < components.Length; ++i) components[i].OnPositionDragged(delta); } 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() { // Did we just get copy/pasted? if (m_ComponentOwner != null && m_ComponentOwner.parent != transform) { CinemachineVirtualCamera copyFrom = (m_ComponentOwner.parent != null) ? m_ComponentOwner.parent.gameObject.GetComponent() : null; DestroyPipeline(); m_ComponentOwner = CreatePipeline(copyFrom); } // 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) list.Add(c); } } // Make sure we have a pipeline owner if (m_ComponentOwner == null) m_ComponentOwner = CreatePipeline(null); // Make sure the pipeline stays hidden, even through prefab if (CinemachineCore.sShowHiddenObjects) m_ComponentOwner.gameObject.hideFlags &= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector); else m_ComponentOwner.gameObject.hideFlags |= (HideFlags.HideInHierarchy | HideFlags.HideInInspector); // Sort the pipeline list.Sort((c1, c2) => (int)c1.Stage - (int)c2.Stage); m_ComponentPipeline = list.ToArray(); } private CameraState CalculateNewState(Vector3 worldUp, float deltaTime) { // Initialize the camera state, in case the game object got moved in the editor CameraState state = PullStateFromVirtualCamera(worldUp); if (LookAt != null) state.ReferenceLookAt = LookAt.position; // Update the state by invoking the component pipeline CinemachineCore.Stage curStage = CinemachineCore.Stage.Body; UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC if (m_ComponentPipeline != null) { for (int i = 0; i < m_ComponentPipeline.Length; ++i) m_ComponentPipeline[i].PrePipelineMutateCameraState(ref state); for (int i = 0; i < m_ComponentPipeline.Length; ++i) { curStage = AdvancePipelineStage( ref state, deltaTime, curStage, (int)m_ComponentPipeline[i].Stage); m_ComponentPipeline[i].MutateCameraState(ref state, deltaTime); } } int numStages = 3; //Enum.GetValues(typeof(CinemachineCore.Stage)).Length; AdvancePipelineStage(ref state, deltaTime, curStage, numStages); return state; } private CinemachineCore.Stage AdvancePipelineStage( ref CameraState state, float deltaTime, CinemachineCore.Stage curStage, int maxStage) { while ((int)curStage < maxStage) { InvokePostPipelineStageCallback(this, curStage, ref state, deltaTime); ++curStage; } return curStage; } private CameraState PullStateFromVirtualCamera(Vector3 worldUp) { CameraState state = CameraState.Default; state.RawPosition = transform.position; state.RawOrientation = transform.rotation; state.ReferenceUp = worldUp; CinemachineBrain brain = CinemachineCore.Instance.FindPotentialTargetBrain(this); m_Lens.Aspect = brain != null ? brain.OutputCamera.aspect : 1; m_Lens.Orthographic = brain != null ? brain.OutputCamera.orthographic : false; state.Lens = m_Lens; return state; } // This is a hack for FreeLook rigs - to be removed internal void SetStateRawPosition(Vector3 pos) { m_State.RawPosition = pos; } } }