using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Serialization; namespace Unity.Cinemachine { /// /// Base class for a Monobehaviour that represents a Virtual Camera within the Unity scene. /// /// This is intended to be attached to an empty Transform GameObject. /// Inherited classes can be either standalone virtual cameras such /// as CinemachineCamera, or meta-cameras such as /// CinemachineClearShot or CinemachineBlendListCamera. /// /// A CinemachineVirtualCameraBase exposes an OutputChannel property. When the behaviour is /// enabled in the game, the Virtual Camera is automatically placed in a queue /// maintained by the static CinemachineCore singleton. /// The queue is sorted by priority. When a Unity camera is equipped with a /// CinemachineBrain behaviour, the brain will choose the camera /// at the head of the queue. If you have multiple Unity cameras with CinemachineBrain /// behaviours (say in a split-screen context), then you can filter the queue by /// setting the culling flags on the virtual cameras. The culling mask of the /// Unity Camera will then act as a filter for the brain. Apart from this, /// there is nothing that prevents a virtual camera from controlling multiple /// Unity cameras simultaneously. /// [SaveDuringPlay] public abstract class CinemachineVirtualCameraBase : MonoBehaviour, ICinemachineCamera { /// /// Priority can be used to control which Cm Camera is live when multiple CM Cameras are /// active simultaneously. The most-recently-activated CinemachineCamera will take control, unless there /// is another Cm Camera active with a higher priority. In general, the most-recently-activated /// highest-priority CinemachineCamera will control the main camera. /// /// The default priority value is 0. Often it is sufficient to leave the default setting. /// In special cases where you want a CinemachineCamera to have a higher or lower priority value than 0, you can set it here. /// /// /// Example of setting priority value directly: /// /// cam.Priority.Value = 5; /// /// /// /// /// Example of using the implicit operator to set priority value: /// /// cam.Priority = 5; /// /// /// [NoSaveDuringPlay] [Tooltip("Priority can be used to control which Cm Camera is live when multiple CM Cameras are " + "active simultaneously. The most-recently-activated CinemachineCamera will take control, unless there " + "is another Cm Camera active with a higher priority. In general, the most-recently-activated " + "highest-priority CinemachineCamera will control the main camera. \n\n" + "The default priority is value 0. Often it is sufficient to leave the default setting. " + "In special cases where you want a CinemachineCamera to have a higher or lower priority value than 0, you can set it here.")] [EnabledProperty(toggleText: "(using default)")] public PrioritySettings Priority = new (); /// /// The output channel functions like Unity layers. Use it to filter the output of CinemachineCameras /// to different CinemachineBrains, for instance in a multi-screen environemnt. /// [NoSaveDuringPlay] [Tooltip("The output channel functions like Unity layers. Use it to filter the output of CinemachineCameras " + "to different CinemachineBrains, for instance in a multi-screen environemnt.")] public OutputChannels OutputChannel = OutputChannels.Default; /// Helper for upgrading from CM2 internal protected virtual bool IsDprecated => false; /// A sequence number that represents object activation order of vcams. /// Used for priority sorting. internal int ActivationId; int m_QueuePriority = int.MaxValue; /// /// This must be set every frame at the start of the pipeline to relax the virtual camera's /// attachment to the target. Range is 0...1. /// 1 is full attachment, and is the normal state. /// 0 is no attachment, and virtual camera will behave as if no Follow /// targets are set. /// [NonSerialized] public float FollowTargetAttachment; /// /// This must be set every frame at the start of the pipeline to relax the virtual camera's /// attachment to the target. Range is 0...1. /// 1 is full attachment, and is the normal state. /// 0 is no attachment, and virtual camera will behave as if no LookAt /// targets are set. /// [NonSerialized] public float LookAtTargetAttachment; /// /// How often to update a virtual camera when it is in Standby mode /// public enum StandbyUpdateMode { /// Only update if the virtual camera is Live Never, /// Update the virtual camera every frame, even when it is not Live Always, /// Update the virtual camera occasionally, the exact frequency depends /// on how many other virtual cameras are in Standby RoundRobin }; /// When the virtual camera is not live, this is how often the virtual camera will /// be updated. Set this to tune for performance. Most of the time Never is fine, unless /// the virtual camera is doing shot evaluation. /// [Tooltip("When the virtual camera is not live, this is how often the virtual camera will be updated. " + "Set this to tune for performance. Most of the time Never is fine, " + "unless the virtual camera is doing shot evaluation.")] [FormerlySerializedAs("m_StandbyUpdate")] public StandbyUpdateMode StandbyUpdate = StandbyUpdateMode.RoundRobin; // Cache for GameObject name, to avoid GC allocs string m_CachedName; bool m_WasStarted; bool m_ChildStatusUpdated = false; CinemachineVirtualCameraBase m_ParentVcam = null; Transform m_CachedFollowTarget; CinemachineVirtualCameraBase m_CachedFollowTargetVcam; ICinemachineTargetGroup m_CachedFollowTargetGroup; Transform m_CachedLookAtTarget; CinemachineVirtualCameraBase m_CachedLookAtTargetVcam; ICinemachineTargetGroup m_CachedLookAtTargetGroup; //============================================================================ // Legacy streaming support [HideInInspector, SerializeField, NoSaveDuringPlay] int m_StreamingVersion; /// /// Override this to handle any upgrades necessitated by a streaming version change. /// Note that since this method is not called from the main thread, there are many things /// it cannot do, including checking a unity object for null. /// /// The version that was streamed internal protected virtual void PerformLegacyUpgrade(int streamedVersion) { if (streamedVersion < 20220601) { if (m_LegacyPriority != 0) { Priority.Value = m_LegacyPriority; m_LegacyPriority = 0; } } } [HideInInspector, SerializeField, FormerlySerializedAs("m_Priority")] int m_LegacyPriority = 0; //============================================================================ /// /// Query components and extensions for the maximum damping time. /// Base class implementation queries extensions. /// Only used in editor for timeline scrubbing. /// /// Highest damping setting in this vcam public virtual float GetMaxDampTime() { float maxDamp = 0; if (Extensions != null) for (int i = 0; i < Extensions.Count; ++i) maxDamp = Mathf.Max(maxDamp, Extensions[i].GetMaxDampTime()); return maxDamp; } /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// This method takes the target attachment into account. For general /// damping without consideration of target attachment, use Damper.Damp() /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public float DetachedFollowTargetDamp(float initial, float dampTime, float deltaTime) { dampTime = Mathf.Lerp(Mathf.Max(1, dampTime), dampTime, FollowTargetAttachment); deltaTime = Mathf.Lerp(0, deltaTime, FollowTargetAttachment); return Damper.Damp(initial, dampTime, deltaTime); } /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// This method takes the target attachment into account. For general /// damping without consideration of target attachment, use Damper.Damp() /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public Vector3 DetachedFollowTargetDamp(Vector3 initial, Vector3 dampTime, float deltaTime) { dampTime = Vector3.Lerp(Vector3.Max(Vector3.one, dampTime), dampTime, FollowTargetAttachment); deltaTime = Mathf.Lerp(0, deltaTime, FollowTargetAttachment); return Damper.Damp(initial, dampTime, deltaTime); } /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// This method takes the target attachment into account. For general /// damping without consideration of target attachment, use Damper.Damp() /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public Vector3 DetachedFollowTargetDamp(Vector3 initial, float dampTime, float deltaTime) { dampTime = Mathf.Lerp(Mathf.Max(1, dampTime), dampTime, FollowTargetAttachment); deltaTime = Mathf.Lerp(0, deltaTime, FollowTargetAttachment); return Damper.Damp(initial, dampTime, deltaTime); } /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// This method takes the target attachment into account. For general /// damping without consideration of target attachment, use Damper.Damp() /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public float DetachedLookAtTargetDamp(float initial, float dampTime, float deltaTime) { dampTime = Mathf.Lerp(Mathf.Max(1, dampTime), dampTime, LookAtTargetAttachment); deltaTime = Mathf.Lerp(0, deltaTime, LookAtTargetAttachment); return Damper.Damp(initial, dampTime, deltaTime); } /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// This method takes the target attachment into account. For general /// damping without consideration of target attachment, use Damper.Damp() /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public Vector3 DetachedLookAtTargetDamp(Vector3 initial, Vector3 dampTime, float deltaTime) { dampTime = Vector3.Lerp(Vector3.Max(Vector3.one, dampTime), dampTime, LookAtTargetAttachment); deltaTime = Mathf.Lerp(0, deltaTime, LookAtTargetAttachment); return Damper.Damp(initial, dampTime, deltaTime); } /// Get a damped version of a quantity. This is the portion of the /// quantity that will take effect over the given time. /// This method takes the target attachment into account. For general /// damping without consideration of target attachment, use Damper.Damp() /// The amount that will be damped /// The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage /// The time over which to damp /// The damped amount. This will be the original amount scaled by /// a value between 0 and 1. public Vector3 DetachedLookAtTargetDamp(Vector3 initial, float dampTime, float deltaTime) { dampTime = Mathf.Lerp(Mathf.Max(1, dampTime), dampTime, LookAtTargetAttachment); deltaTime = Mathf.Lerp(0, deltaTime, LookAtTargetAttachment); return Damper.Damp(initial, dampTime, deltaTime); } /// /// A delegate to hook into the state calculation pipeline. /// This will be called after each pipeline stage, to allow others to hook into the pipeline. /// See CinemachineCore.Stage. /// /// The extension to add. internal void AddExtension(CinemachineExtension extension) { if (Extensions == null) Extensions = new List(); else Extensions.Remove(extension); Extensions.Add(extension); } /// Remove a Pipeline stage hook callback. /// The extension to remove. internal void RemoveExtension(CinemachineExtension extension) { if (Extensions != null) Extensions.Remove(extension); } /// The extensions connected to this vcam internal List Extensions { get; private set; } /// /// Invokes the PostPipelineStageDelegate for this camera, and up the hierarchy for all /// parent cameras (if any). /// Implementation must be sure to call this after each pipeline stage, to allow /// other services to hook into the pipeline. /// See CinemachineCore.Stage. /// /// The virtual camera being processed /// The current pipeline stage /// The current virtual camera state /// The current applicable deltaTime protected void InvokePostPipelineStageCallback( CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState newState, float deltaTime) { if (Extensions != null) { for (int i = 0; i < Extensions.Count; ++i) { var e = Extensions[i]; if (e == null) { // Object was deleted (possibly because of Undo in the editor) Extensions.RemoveAt(i); --i; } else if (e.enabled) e.InvokePostPipelineStageCallback(vcam, stage, ref newState, deltaTime); } } if (ParentCamera is CinemachineVirtualCameraBase vcamParent) vcamParent.InvokePostPipelineStageCallback(vcam, stage, ref newState, deltaTime); } /// /// Invokes the PrePipelineMutateCameraStateCallback for this camera, /// and up the hierarchy for all parent cameras (if any). /// Implementation must be sure to call this after each pipeline stage, to allow /// other services to hook into the pipeline. /// See CinemachineCore.Stage. /// /// The virtual camera being processed /// The current virtual camera state /// The current applicable deltaTime protected void InvokePrePipelineMutateCameraStateCallback( CinemachineVirtualCameraBase vcam, ref CameraState newState, float deltaTime) { if (Extensions != null) { for (int i = 0; i < Extensions.Count; ++i) { var e = Extensions[i]; if (e == null) { // Object was deleted (possibly because of Undo in the editor) Extensions.RemoveAt(i); --i; } else if (e.enabled) e.PrePipelineMutateCameraStateCallback(vcam, ref newState, deltaTime); } } if (ParentCamera is CinemachineVirtualCameraBase vcamParent) vcamParent.InvokePrePipelineMutateCameraStateCallback(vcam, ref newState, deltaTime); } /// /// Invokes the OnTransitionFromCamera for all extensions on this camera /// /// 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) /// True to request a vcam update of internal state protected bool InvokeOnTransitionInExtensions( ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime) { bool forceUpdate = false; if (Extensions != null) { for (int i = 0; i < Extensions.Count; ++i) { var e = Extensions[i]; if (e == null) { // Object was deleted (possibly because of Undo in the editor) Extensions.RemoveAt(i); --i; } else if (e.enabled && e.OnTransitionFromCamera(fromCam, worldUp, deltaTime)) forceUpdate = true; } } return forceUpdate; } /// Get the name of the Virtual Camera. Base implementation /// returns a cache of the owner GameObject's name. public string Name { get { #if UNITY_EDITOR // Allow vcam name changes when not playing if (!Application.isPlaying) m_CachedName = null; #endif m_CachedName ??= IsValid ? name : "(deleted)"; return m_CachedName; } } /// Gets a brief debug description of this virtual camera, for use when displaying debug info public virtual string Description => ""; /// Returns false if the object has been deleted public bool IsValid => !(this == null); /// The CameraState object holds all of the information /// necessary to position the Unity camera. It is the output of this class. public abstract CameraState State { get; } /// Support for meta-virtual-cameras. This is the situation where a /// virtual camera is in fact the public face of a private army of virtual cameras, which /// it manages on its own. This method gets the VirtualCamera owner, if any. /// Private armies are implemented as Transform children of the parent vcam. public ICinemachineMixer ParentCamera { get { if (!m_ChildStatusUpdated || !Application.isPlaying) UpdateStatusAsChild(); return m_ParentVcam as ICinemachineMixer; } } /// Get the LookAt target for the Aim component in the Cinemachine pipeline. public abstract Transform LookAt { get; set; } /// Get the Follow target for the Body component in the Cinemachine pipeline. public abstract Transform Follow { get; set; } /// Set this to force the next update to ignore state from the previous frame. /// This is useful, for example, if you want to cancel damping or other time-based processing. public virtual bool PreviousStateIsValid { get; set; } /// /// Update the camera's state. /// The implementation must guarantee against multiple calls per frame, and should /// use CinemachineCore.UpdateVirtualCamera(ICinemachineCamera, Vector3, float), which /// has protection against multiple calls per frame. /// /// Default world Up, set by the CinemachineBrain /// Delta time for time-based effects (ignore if less than 0) public void UpdateCameraState(Vector3 worldUp, float deltaTime) { CameraUpdateManager.UpdateVirtualCamera(this, worldUp, deltaTime); } /// Internal use only. /// Called by CinemachineCore at designated update time /// so the vcam can position itself and track its targets. /// Do not call this method. Let the framework do it at the appropriate time /// Default world Up, set by the CinemachineBrain /// Delta time for time-based effects (ignore if less than 0) public abstract void InternalUpdateCameraState(Vector3 worldUp, float deltaTime); /// public virtual void OnCameraActivated(ICinemachineCamera.ActivationEventParams evt) { if (evt.IncomingCamera == (ICinemachineCamera)this) OnTransitionFromCamera(evt.OutgoingCamera, evt.WorldUp, evt.DeltaTime); } // GML todo: get rid of OnTransitionFromCamera /// Notification that this virtual camera is going live. /// Base class implementation must be called by any overridden method. /// 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 virtual void OnTransitionFromCamera( ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime) { if (!gameObject.activeInHierarchy) PreviousStateIsValid = false; } /// /// Called on inactive object when being artificially activated by timeline. /// This is necessary because Awake() isn't called on inactive gameObjects. /// internal void EnsureStarted() { if (!m_WasStarted) { m_WasStarted = true; // Perform legacy upgrade if necessary if (m_StreamingVersion < CinemachineCore.kStreamingVersion) PerformLegacyUpgrade(m_StreamingVersion); m_StreamingVersion = CinemachineCore.kStreamingVersion; var extensions = GetComponentsInChildren(); for (int i = 0; i < extensions.Length; ++i) extensions[i].EnsureStarted(); } } #if UNITY_EDITOR [UnityEditor.Callbacks.DidReloadScripts] static void OnScriptReload() { var vcams = Resources.FindObjectsOfTypeAll( typeof(CinemachineVirtualCameraBase)) as CinemachineVirtualCameraBase[]; for (int i = 0; i < vcams.Length; ++i) vcams[i].LookAtTargetChanged = vcams[i].FollowTargetChanged = true; } #endif /// Base class implementation makes sure the priority queue remains up-to-date. protected virtual void OnTransformParentChanged() { CameraUpdateManager.CameraDisabled(this); CameraUpdateManager.CameraEnabled(this); UpdateStatusAsChild(); UpdateVcamPoolStatus(); } /// Maintains the global vcam registry. Always call the base class implementation. protected virtual void OnDestroy() { CameraUpdateManager.CameraDestroyed(this); } /// Derived classes should call base class implementation. protected virtual void Start() { m_WasStarted = true; // Perform legacy upgrade if necessary if (m_StreamingVersion < CinemachineCore.kStreamingVersion) PerformLegacyUpgrade(m_StreamingVersion); m_StreamingVersion = CinemachineCore.kStreamingVersion; } /// Base class implementation adds the virtual camera from the priority queue. protected virtual void OnEnable() { UpdateStatusAsChild(); UpdateVcamPoolStatus(); // Add to queue if (!CinemachineCore.IsLive(this)) PreviousStateIsValid = false; CameraUpdateManager.CameraEnabled(this); InvalidateCachedTargets(); // Sanity check - if another vcam component is enabled, shut down var vcamComponents = GetComponents(); for (int i = 0; i < vcamComponents.Length; ++i) { if (vcamComponents[i].enabled && vcamComponents[i] != this) { var toDeprecate = vcamComponents[i].IsDprecated ? vcamComponents[i] : this; if (!toDeprecate.IsDprecated) Debug.LogWarning(Name + " has multiple CinemachineVirtualCameraBase-derived components. Disabling " + toDeprecate.GetType().Name); toDeprecate.enabled = false; } } } /// Base class implementation makes sure the priority queue remains up-to-date. protected virtual void OnDisable() { UpdateVcamPoolStatus(); // Remove from queue CameraUpdateManager.CameraDisabled(this); } /// Base class implementation makes sure the priority queue remains up-to-date. protected virtual void Update() { if (Priority.Value != m_QueuePriority) UpdateVcamPoolStatus(); // Force a re-sort } void UpdateStatusAsChild() { m_ChildStatusUpdated = true; m_ParentVcam = null; Transform p = transform.parent; if (p != null) p.TryGetComponent(out m_ParentVcam); } /// Returns this vcam's LookAt target, or if that is null, will return /// the parent vcam's LookAt target. /// This vcam's LookAt value. /// The same value, or the parent's if null and a parent exists. public Transform ResolveLookAt(Transform localLookAt) { Transform lookAt = localLookAt; if (lookAt == null && ParentCamera is CinemachineVirtualCameraBase vcamParent) lookAt = vcamParent.LookAt; // Parent provides default return lookAt; } /// Returns this vcam's Follow target, or if that is null, will retrun /// the parent vcam's Follow target. /// This vcam's Follow value. /// The same value, or the parent's if null and a parent exists. public Transform ResolveFollow(Transform localFollow) { Transform follow = localFollow; if (follow == null && ParentCamera is CinemachineVirtualCameraBase vcamParent) follow = vcamParent.Follow; // Parent provides default return follow; } void UpdateVcamPoolStatus() { CameraUpdateManager.RemoveActiveCamera(this); if (m_ParentVcam == null && isActiveAndEnabled) CameraUpdateManager.AddActiveCamera(this); m_QueuePriority = Priority.Value; } /// When multiple virtual cameras have the highest priority, there is /// sometimes the need to push one to the top, making it the current Live camera if /// it shares the highest priority in the queue with its peers. /// /// This happens automatically when a /// new vcam is enabled: the most recent one goes to the top of the priority sub-queue. /// Use this method to push a vcam to the top of its priority peers. /// If it and its peers share the highest priority, then this vcam will become Live. [Obsolete("Please use Prioritize()")] public void MoveToTopOfPrioritySubqueue() => Prioritize(); /// When multiple Cm Cameras have the highest priority, there is /// sometimes the need to push one to the top, making it the current Live camera if /// it shares the highest priority in the queue with its peers. /// /// This happens automatically when a /// new CinemachineCamera is enabled: the most recent one goes to the top of the priority sub-queue. /// Use this method to push a camera to the top of its priority peers. /// If it and its peers share the highest priority, then this vcam will become Live. public void Prioritize() => UpdateVcamPoolStatus(); // Force a re-sort /// This is called to notify the component that a target got warped, /// so that the component 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 virtual void OnTargetObjectWarped(Transform target, Vector3 positionDelta) => OnTargetObjectWarped(this, target, positionDelta); void OnTargetObjectWarped(CinemachineVirtualCameraBase vcam, Transform target, Vector3 positionDelta) { // inform the extensions if (Extensions != null) { for (int i = 0; i < Extensions.Count; ++i) Extensions[i].OnTargetObjectWarped(vcam, target, positionDelta); } if (ParentCamera is CinemachineVirtualCameraBase vcamParent) vcamParent.OnTargetObjectWarped(vcam, target, positionDelta); } /// /// Force the virtual camera to assume a given position and orientation /// /// World-space position to take /// World-space orientation to take public virtual void ForceCameraPosition(Vector3 pos, Quaternion rot) { // inform the extensions if (Extensions != null) { for (int i = 0; i < Extensions.Count; ++i) Extensions[i].ForceCameraPosition(pos, rot); } } /// /// Create a camera state based on the current transform of this vcam /// /// Current World Up direction, as provided by the brain /// Lens settings to serve as base, will be combined with lens from brain, if any /// protected CameraState PullStateFromVirtualCamera(Vector3 worldUp, ref LensSettings lens) { CameraState state = CameraState.Default; state.RawPosition = TargetPositionCache.GetTargetPosition(transform); state.RawOrientation = TargetPositionCache.GetTargetRotation(transform); state.ReferenceUp = worldUp; CinemachineBrain brain = CinemachineCore.FindPotentialTargetBrain(this); if (brain != null && brain.OutputCamera != null) lens.PullInheritedPropertiesFromCamera(brain.OutputCamera); state.Lens = lens; return state; } void InvalidateCachedTargets() { m_CachedFollowTarget = null; m_CachedFollowTargetVcam = null; m_CachedFollowTargetGroup = null; m_CachedLookAtTarget = null; m_CachedLookAtTargetVcam = null; m_CachedLookAtTargetGroup = null; } #if UNITY_EDITOR [UnityEditor.InitializeOnLoad] class OnDomainReload { static OnDomainReload() { #if UNITY_2023_1_OR_NEWER var vcams = FindObjectsByType (FindObjectsInactive.Include, FindObjectsSortMode.None); #else var vcams = FindObjectsOfType(true); #endif for (int i = 0; i < vcams.Length; ++i) vcams[i].InvalidateCachedTargets(); } } #endif /// /// This property is true if the Follow target was changed this frame. /// public bool FollowTargetChanged { get; private set; } /// /// This property is true if the LookAtTarget was changed this frame. /// public bool LookAtTargetChanged { get; private set; } /// /// Call this from InternalUpdateCameraState() to check for changed /// targets and update the target cache. This is needed for tracking /// when a target object changes. /// public void UpdateTargetCache() { var target = ResolveFollow(Follow); FollowTargetChanged = target != m_CachedFollowTarget; if (FollowTargetChanged) { m_CachedFollowTarget = target; m_CachedFollowTargetVcam = null; m_CachedFollowTargetGroup = null; if (m_CachedFollowTarget != null) { target.TryGetComponent(out m_CachedFollowTargetVcam); target.TryGetComponent(out m_CachedFollowTargetGroup); } } target = ResolveLookAt(LookAt); LookAtTargetChanged = target != m_CachedLookAtTarget; if (LookAtTargetChanged) { m_CachedLookAtTarget = target; m_CachedLookAtTargetVcam = null; m_CachedLookAtTargetGroup = null; if (target != null) { target.TryGetComponent(out m_CachedLookAtTargetVcam); target.TryGetComponent(out m_CachedLookAtTargetGroup); } } } /// Get Follow target as ICinemachineTargetGroup, /// or null if target is not a ICinemachineTargetGroup public ICinemachineTargetGroup FollowTargetAsGroup => m_CachedFollowTargetGroup; /// Get Follow target as CinemachineVirtualCameraBase, /// or null if target is not a CinemachineVirtualCameraBase public CinemachineVirtualCameraBase FollowTargetAsVcam => m_CachedFollowTargetVcam; /// Get LookAt target as ICinemachineTargetGroup, /// or null if target is not a ICinemachineTargetGroup public ICinemachineTargetGroup LookAtTargetAsGroup => m_CachedLookAtTargetGroup; /// Get LookAt target as CinemachineVirtualCameraBase, /// or null if target is not a CinemachineVirtualCameraBase public CinemachineVirtualCameraBase LookAtTargetAsVcam => m_CachedLookAtTargetVcam; /// Get the component set for a specific stage in the pipeline. /// The stage for which we want the component /// The Cinemachine component for that stage, or null if not present. public virtual CinemachineComponentBase GetCinemachineComponent(CinemachineCore.Stage stage) => null; /// Returns true if this camera is currently live for some CinemachineBrain. public bool IsLive => CinemachineCore.IsLive(this); /// Check to see whether this camera is currently participating in a blend /// within its parent manager or in a CinemacineBrain /// True if the camera is participating in a blend public bool IsParticipatingInBlend() { if (IsLive) { var parent = ParentCamera as CinemachineCameraManagerBase; if (parent != null) return (parent.ActiveBlend != null && parent.ActiveBlend.Uses(this)) || parent.IsParticipatingInBlend(); var brain = CinemachineCore.FindPotentialTargetBrain(this); if (brain != null) return brain.ActiveBlend != null && brain.ActiveBlend.Uses(this); } return false; } /// /// Temporarily cancel damping for this frame. The camera will sanp to its target /// position when it is updated. /// /// If true, snap the camera to its target immediately, otherwise wait /// until the end of the frame when cameras are normally updated. public void CancelDamping(bool updateNow = false) { PreviousStateIsValid = false; if (updateNow) { var up = State.ReferenceUp; var brain = CinemachineCore.FindPotentialTargetBrain(this); if (brain != null) up = brain.DefaultWorldUp; InternalUpdateCameraState(up, -1); } } } }