#if !CINEMACHINE_NO_CM2_SUPPORT using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Serialization; namespace Unity.Cinemachine { /// /// This is a deprecated component. Use CinemachineCamera instead. /// [Obsolete("CinemachineVirtualCamera is deprecated. Use CinemachineCamera instead.")] [DisallowMultipleComponent] [ExecuteAlways] [ExcludeFromPreset] [AddComponentMenu("")] // Don't display in add component menu public class CinemachineVirtualCamera : CinemachineVirtualCameraBase, AxisState.IRequiresInput { /// 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. [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.")] [FormerlySerializedAs("m_LensAttributes")] public LegacyLensSettings m_Lens = LegacyLensSettings.Default; /// Hint for transitioning to and from this CinemachineCamera. Hints can be combined, although /// not all combinations make sense. In the case of conflicting hints, Cinemachine will /// make an arbitrary choice. [Tooltip("Hint for transitioning to and from this CinemachineCamera. Hints can be combined, although " + "not all combinations make sense. In the case of conflicting hints, Cinemachine will " + "make an arbitrary choice.")] public CinemachineCore.BlendHints BlendHint; /// This event fires when a transition occurs. [Tooltip("This event fires when a transition occurs")] public CinemachineLegacyCameraEvents.OnCameraLiveEvent m_OnCameraLiveEvent = new(); /// Inspector control - Use for hiding sections of the Inspector UI. [HideInInspector, SerializeField, NoSaveDuringPlay] internal string[] m_ExcludedPropertiesInInspector = new string[] { "m_Script" }; /// Inspector control - Use for enabling sections of the Inspector UI. [HideInInspector, SerializeField, NoSaveDuringPlay] internal CinemachineCore.Stage[] m_LockStageInInspector; // Legacy support =============================================================== [Serializable] struct LegacyTransitionParams { [FormerlySerializedAs("m_PositionBlending")] public int m_BlendHint; public bool m_InheritPosition; public CinemachineLegacyCameraEvents.OnCameraLiveEvent m_OnCameraLive; } [FormerlySerializedAs("m_Transitions")] [SerializeField, HideInInspector] LegacyTransitionParams m_LegacyTransitions; internal protected override void PerformLegacyUpgrade(int streamedVersion) { base.PerformLegacyUpgrade(streamedVersion); if (streamedVersion < 20221011) { if (m_LegacyTransitions.m_BlendHint != 0) { if (m_LegacyTransitions.m_BlendHint == 3) BlendHint = CinemachineCore.BlendHints.ScreenSpaceAimWhenTargetsDiffer; else BlendHint = (CinemachineCore.BlendHints)m_LegacyTransitions.m_BlendHint; m_LegacyTransitions.m_BlendHint = 0; } if (m_LegacyTransitions.m_InheritPosition) { BlendHint |= CinemachineCore.BlendHints.InheritPosition; m_LegacyTransitions.m_InheritPosition = false; } if (m_LegacyTransitions.m_OnCameraLive != null) { m_OnCameraLiveEvent = m_LegacyTransitions.m_OnCameraLive; m_LegacyTransitions.m_OnCameraLive = null; } } } // =============================================================== /// Helper for upgrading from CM2 internal protected override bool IsDprecated => true; /// 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. 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(); if (deltaTime < 0) PreviousStateIsValid = false; // Update the state by invoking the component pipeline m_State = CalculateNewState(worldUp, deltaTime); m_State.BlendHint = (CameraState.BlendHints)BlendHint; // Push the raw position back to the game object's transform, so it // moves along with the camera. 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_LensSettings = m_Lens.ToLensSettings(); m_State = PullStateFromVirtualCamera(Vector3.up, ref m_LensSettings); InvalidateComponentPipeline(); } /// 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 void OnValidate() { m_Lens.Validate(); } void OnTransformChildrenChanged() { InvalidateComponentPipeline(); } void Reset() { DestroyPipeline(); UpdateComponentPipeline(); Priority = new (); OutputChannel = OutputChannels.Default; } /// /// 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. internal 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 { var oldStuff = child.GetComponents(); foreach (var c in oldStuff) Destroy(c); if (!RuntimeUtility.IsPrefab(gameObject)) Destroy(child.gameObject); } } m_ComponentOwner = null; InvalidateComponentPipeline(); PreviousStateIsValid = false; } /// Create a default pipeline container. /// Note: copyFrom only supported in Editor, not build internal 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 if (!RuntimeUtility.IsPrefab(gameObject)) { 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 override 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(); } } } } CameraState m_State = CameraState.Default; // Current state this frame CinemachineComponentBase[] m_ComponentPipeline = null; // Serialized only to implement copy/paste of CM subcomponents. // Note however that this strategy has its limitations: the CM pipeline Components // won't be pasted onto a prefab asset outside the scene unless the prefab // is opened in Prefab edit mode. [SerializeField][HideInInspector] private Transform m_ComponentOwner = null; void UpdateComponentPipeline() { #if UNITY_EDITOR // 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(); CreatePipeline(copyFrom); m_ComponentOwner = null; } // Make sure the pipeline stays hidden, even through prefab 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) { CinemachineComponentBase[] components = child.GetComponents(); foreach (CinemachineComponentBase c in components) if (c != null && c.enabled) list.Add(c); m_ComponentOwner = child; break; } } // Make sure we have a pipeline owner if (m_ComponentOwner == null) m_ComponentOwner = CreatePipeline(null); 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 true child.hideFlags &= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector); #else if (CinemachineCore.sShowHiddenObjects) child.hideFlags &= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector); else child.hideFlags |= (HideFlags.HideInHierarchy | HideFlags.HideInInspector); #endif } } private LensSettings m_LensSettings; 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 m_LensSettings = m_Lens.ToLensSettings(); CameraState state = PullStateFromVirtualCamera(worldUp, ref m_LensSettings); 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.GetFinalPosition(); 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) { 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 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 seamlessly. /// 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 position to take /// Worldspace orientation to take public override void ForceCameraPosition(Vector3 pos, Quaternion rot) { PreviousStateIsValid = true; transform.SetPositionAndRotation(pos, 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 (fromCam != null && (State.BlendHint & CameraState.BlendHints.InheritPosition) != 0 && !CinemachineCore.IsLiveInBlend(this)) { ForceCameraPosition(fromCam.State.GetFinalPosition(), fromCam.State.GetFinalOrientation()); } 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)) forceUpdate = true; } if (forceUpdate) { InternalUpdateCameraState(worldUp, deltaTime); InternalUpdateCameraState(worldUp, deltaTime); } else UpdateCameraState(worldUp, deltaTime); m_OnCameraLiveEvent?.Invoke(this, fromCam); } /// Tells whether this vcam requires input. /// Returns true, when the vcam has an extension or components that require input. bool AxisState.IRequiresInput.RequiresInput() { if (Extensions != null) for (int i = 0; i < Extensions.Count; ++i) if (Extensions[i] is AxisState.IRequiresInput) return true; UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC if (m_ComponentPipeline != null) for (int i = 0; i < m_ComponentPipeline.Length; ++i) if (m_ComponentPipeline[i] is AxisState.IRequiresInput) return true; return false; } } } #endif