using System; using System.Collections.Generic; using System.Linq; using Cinemachine.Utility; using UnityEngine; using UnityEngine.Serialization; namespace Cinemachine { /// <summary> /// 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 CinemachineVirtualCamera, or meta-cameras such as /// CinemachineClearShot or CinemachineFreeLook. /// /// A CinemachineVirtualCameraBase exposes a Priority 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. /// </summary> [SaveDuringPlay] public abstract class CinemachineVirtualCameraBase : MonoBehaviour, ICinemachineCamera, ISerializationCallbackReceiver { /// <summary>Inspector control - Use for hiding sections of the Inspector UI.</summary> [HideInInspector, SerializeField, NoSaveDuringPlay] public string[] m_ExcludedPropertiesInInspector = new string[] { "m_Script" }; /// <summary>Inspector control - Use for enabling sections of the Inspector UI.</summary> [HideInInspector, SerializeField, NoSaveDuringPlay] public CinemachineCore.Stage[] m_LockStageInInspector; /// <summary>Version that was last streamed, for upgrading legacy</summary> public int ValidatingStreamVersion { get { return m_OnValidateCalled ? m_ValidatingStreamVersion : CinemachineCore.kStreamingVersion; } private set { m_ValidatingStreamVersion = value; } } private int m_ValidatingStreamVersion = 0; private bool m_OnValidateCalled = false; [HideInInspector, SerializeField, NoSaveDuringPlay] private int m_StreamingVersion; /// <summary>The priority will determine which camera becomes active based on the /// state of other cameras and this camera. Higher numbers have greater priority. /// </summary> [NoSaveDuringPlay] [Tooltip("The priority will determine which camera becomes active based on the state of " + "other cameras and this camera. Higher numbers have greater priority.")] public int m_Priority = 10; /// <summary>A sequence number that represents object activation order of vcams. /// Used for priority sorting.</summary> internal int m_ActivationId; /// <summary> /// 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. /// </summary> [NonSerialized] public float FollowTargetAttachment; /// <summary> /// 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. /// </summary> [NonSerialized] public float LookAtTargetAttachment; /// <summary> /// How often to update a virtual camera when it is in Standby mode /// </summary> public enum StandbyUpdateMode { /// <summary>Only update if the virtual camera is Live</summary> Never, /// <summary>Update the virtual camera every frame, even when it is not Live</summary> Always, /// <summary>Update the virtual camera occasionally, the exact frequency depends /// on how many other virtual cameras are in Standby</summary> RoundRobin }; /// <summary>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. /// </summary> [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.")] public StandbyUpdateMode m_StandbyUpdate = StandbyUpdateMode.RoundRobin; /// <summary> /// Query components and extensions for the maximum damping time. /// Base class implementation queries extensions. /// Only used in editor for timeline scrubbing. /// </summary> /// <returns>Highest damping setting in this vcam</returns> public virtual float GetMaxDampTime() { float maxDamp = 0; if (mExtensions != null) for (int i = 0; i < mExtensions.Count; ++i) maxDamp = Mathf.Max(maxDamp, mExtensions[i].GetMaxDampTime()); return maxDamp; } /// <summary>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()</summary> /// <param name="initial">The amount that will be damped</param> /// <param name="dampTime">The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage</param> /// <param name="deltaTime">The time over which to damp</param> /// <returns>The damped amount. This will be the original amount scaled by /// a value between 0 and 1.</returns> 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); } /// <summary>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()</summary> /// <param name="initial">The amount that will be damped</param> /// <param name="dampTime">The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage</param> /// <param name="deltaTime">The time over which to damp</param> /// <returns>The damped amount. This will be the original amount scaled by /// a value between 0 and 1.</returns> 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); } /// <summary>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()</summary> /// <param name="initial">The amount that will be damped</param> /// <param name="dampTime">The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage</param> /// <param name="deltaTime">The time over which to damp</param> /// <returns>The damped amount. This will be the original amount scaled by /// a value between 0 and 1.</returns> 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); } /// <summary>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()</summary> /// <param name="initial">The amount that will be damped</param> /// <param name="dampTime">The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage</param> /// <param name="deltaTime">The time over which to damp</param> /// <returns>The damped amount. This will be the original amount scaled by /// a value between 0 and 1.</returns> 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); } /// <summary>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()</summary> /// <param name="initial">The amount that will be damped</param> /// <param name="dampTime">The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage</param> /// <param name="deltaTime">The time over which to damp</param> /// <returns>The damped amount. This will be the original amount scaled by /// a value between 0 and 1.</returns> 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); } /// <summary>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()</summary> /// <param name="initial">The amount that will be damped</param> /// <param name="dampTime">The rate of damping. This is the time it would /// take to reduce the original amount to a negligible percentage</param> /// <param name="deltaTime">The time over which to damp</param> /// <returns>The damped amount. This will be the original amount scaled by /// a value between 0 and 1.</returns> 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); } /// <summary> /// 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. /// </summary> /// <param name="extension">The extension to add.</param> public virtual void AddExtension(CinemachineExtension extension) { if (mExtensions == null) mExtensions = new List<CinemachineExtension>(); else mExtensions.Remove(extension); mExtensions.Add(extension); } /// <summary>Remove a Pipeline stage hook callback.</summary> /// <param name="extension">The extension to remove.</param> public virtual void RemoveExtension(CinemachineExtension extension) { if (mExtensions != null) mExtensions.Remove(extension); } /// <summary> The extensions connected to this vcam</summary> internal List<CinemachineExtension> mExtensions { get; private set; } /// <summary> /// Invokes the PostPipelineStageDelegate for this camera, and up the hierarchy for all /// parent cameras (if any). /// Implementaion must be sure to call this after each pipeline stage, to allow /// other services to hook into the pipeline. /// See CinemachineCore.Stage. /// </summary> /// <param name="vcam">The virtual camera being processed</param> /// <param name="stage">The current pipeline stage</param> /// <param name="newState">The current virtual camera state</param> /// <param name="deltaTime">The current applicable deltaTime</param> protected void InvokePostPipelineStageCallback( CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState newState, float deltaTime) { if (mExtensions != null) { for (int i = 0; i < mExtensions.Count; ++i) { var e = mExtensions[i]; if (e == null) { // Object was deleted (possibly because of Undo in the editor) mExtensions.RemoveAt(i); --i; } else if (e.enabled) e.InvokePostPipelineStageCallback(vcam, stage, ref newState, deltaTime); } } CinemachineVirtualCameraBase parent = ParentCamera as CinemachineVirtualCameraBase; if (parent != null) parent.InvokePostPipelineStageCallback(vcam, stage, ref newState, deltaTime); } /// <summary> /// Invokes the PrePipelineMutateCameraStateCallback for this camera, /// and up the hierarchy for all parent cameras (if any). /// Implementaion must be sure to call this after each pipeline stage, to allow /// other services to hook into the pipeline. /// See CinemachineCore.Stage. /// </summary> /// <param name="vcam">The virtual camera being processed</param> /// <param name="newState">The current virtual camera state</param> /// <param name="deltaTime">The current applicable deltaTime</param> protected void InvokePrePipelineMutateCameraStateCallback( CinemachineVirtualCameraBase vcam, ref CameraState newState, float deltaTime) { if (mExtensions != null) { for (int i = 0; i < mExtensions.Count; ++i) { var e = mExtensions[i]; if (e == null) { // Object was deleted (possibly because of Undo in the editor) mExtensions.RemoveAt(i); --i; } else if (e.enabled) e.PrePipelineMutateCameraStateCallback(vcam, ref newState, deltaTime); } } CinemachineVirtualCameraBase parent = ParentCamera as CinemachineVirtualCameraBase; if (parent != null) parent.InvokePrePipelineMutateCameraStateCallback(vcam, ref newState, deltaTime); } /// <summary> /// Invokes the OnTransitionFromCamera for all extensions on this camera /// </summary> /// <param name="fromCam">The camera being deactivated. May be null.</param> /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param> /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param> /// <returns>True to request a vcam update of internal state</returns> protected bool InvokeOnTransitionInExtensions( ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime) { bool forceUpdate = false; if (mExtensions != null) { for (int i = 0; i < mExtensions.Count; ++i) { var e = mExtensions[i]; if (e == null) { // Object was deleted (possibly because of Undo in the editor) mExtensions.RemoveAt(i); --i; } else if (e.enabled && e.OnTransitionFromCamera(fromCam, worldUp, deltaTime)) forceUpdate = true; } } return forceUpdate; } /// <summary>Get the name of the Virtual Camera. Base implementation /// returns the owner GameObject's name.</summary> public string Name => name; /// <summary>Gets a brief debug description of this virtual camera, for use when displayiong debug info</summary> public virtual string Description => ""; /// <summary>Get the Priority of the virtual camera. This determines its placement /// in the CinemachineCore's queue of eligible shots.</summary> public int Priority { get => m_Priority; set => m_Priority = value; } /// <summary>Hint for blending to and from this virtual camera</summary> public enum BlendHint { /// <summary>Standard linear position and aim blend</summary> None, /// <summary>Spherical blend about LookAt target position if there is a LookAt target, linear blend between LookAt targets</summary> SphericalPosition, /// <summary>Cylindrical blend about LookAt target position if there is a LookAt target (vertical co-ordinate is linearly interpolated), linear blend between LookAt targets</summary> CylindricalPosition, /// <summary>Standard linear position blend, radial blend between LookAt targets</summary> ScreenSpaceAimWhenTargetsDiffer } /// <summary>Applies a position blend hint to a camera state</summary> /// <param name="state">The state to apply the hint to</param> /// <param name="hint">The hint to apply</param> protected void ApplyPositionBlendMethod(ref CameraState state, BlendHint hint) { switch (hint) { default: break; case BlendHint.SphericalPosition: state.BlendHint |= CameraState.BlendHintValue.SphericalPositionBlend; break; case BlendHint.CylindricalPosition: state.BlendHint |= CameraState.BlendHintValue.CylindricalPositionBlend; break; case BlendHint.ScreenSpaceAimWhenTargetsDiffer: state.BlendHint |= CameraState.BlendHintValue.RadialAimBlend; break; } } /// <summary>The GameObject owner of the Virtual Camera behaviour.</summary> public GameObject VirtualCameraGameObject { get { if (this == null) return null; // object deleted return gameObject; } } /// <summary>Returns false if the object has been deleted</summary> public bool IsValid => !(this == null); /// <summary>The CameraState object holds all of the information /// necessary to position the Unity camera. It is the output of this class.</summary> public abstract CameraState State { get; } /// <summary>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.</summary> public ICinemachineCamera ParentCamera { get { if (!mSlaveStatusUpdated || !Application.isPlaying) UpdateSlaveStatus(); return m_parentVcam; } } /// <summary>Check whether the vcam a live child of this camera. /// This base class implementation always returns false.</summary> /// <param name="vcam">The Virtual Camera to check</param> /// <param name="dominantChildOnly">If truw, will only return true if this vcam is the dominat live child</param> /// <returns>True if the vcam is currently actively influencing the state of this vcam</returns> public virtual bool IsLiveChild(ICinemachineCamera vcam, bool dominantChildOnly = false) { return false; } /// <summary>Get the LookAt target for the Aim component in the Cinemachine pipeline.</summary> public abstract Transform LookAt { get; set; } /// <summary>Get the Follow target for the Body component in the Cinemachine pipeline.</summary> public abstract Transform Follow { get; set; } /// <summary>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.</summary> public virtual bool PreviousStateIsValid { get; set; } /// <summary> /// 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. /// </summary> /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param> /// <param name="deltaTime">Delta time for time-based effects (ignore if less than 0)</param> public void UpdateCameraState(Vector3 worldUp, float deltaTime) { CinemachineCore.Instance.UpdateVirtualCamera(this, worldUp, deltaTime); } /// <summary>Internal use only. Do not call this method. /// 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</summary> /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param> /// <param name="deltaTime">Delta time for time-based effects (ignore if less than 0)</param> public abstract void InternalUpdateCameraState(Vector3 worldUp, float deltaTime); /// <summary> Collection of parameters that influence how this virtual camera transitions from /// other virtual cameras </summary> [Serializable] public struct TransitionParams { /// <summary>Hint for blending positions to and from this virtual camera</summary> [Tooltip("Hint for blending positions to and from this virtual camera")] [FormerlySerializedAs("m_PositionBlending")] public BlendHint m_BlendHint; /// <summary>When this virtual camera goes Live, attempt to force the position to be the same as the current position of the Unity Camera</summary> [Tooltip("When this virtual camera goes Live, attempt to force the position to be the same as the current position of the Unity Camera")] public bool m_InheritPosition; /// <summary>This event fires when the virtual camera goes Live</summary> [Tooltip("This event fires when the virtual camera goes Live")] public CinemachineBrain.VcamActivatedEvent m_OnCameraLive; } /// <summary>Notification that this virtual camera is going live. /// Base class implementation must be called by any overridden method.</summary> /// <param name="fromCam">The camera being deactivated. May be null.</param> /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param> /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param> public virtual void OnTransitionFromCamera( ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime) { if (!gameObject.activeInHierarchy) PreviousStateIsValid = false; } /// <summary>Maintains the global vcam registry. Always call the base class implementation.</summary> protected virtual void OnDestroy() { CinemachineCore.Instance.CameraDestroyed(this); } /// <summary>Base class implementation makes sure the priority queue remains up-to-date.</summary> protected virtual void OnTransformParentChanged() { CinemachineCore.Instance.CameraDisabled(this); CinemachineCore.Instance.CameraEnabled(this); UpdateSlaveStatus(); UpdateVcamPoolStatus(); } bool m_WasStarted; /// <summary>Derived classes should call base class implementation.</summary> protected virtual void Start() { m_WasStarted = true; } /// <summary> /// Returns true, when the vcam has an extension that requires user input. /// </summary> internal virtual bool RequiresUserInput() { return mExtensions != null && mExtensions.Any(extension => extension != null && extension.RequiresUserInput); } /// <summary> /// Called on inactive object when being artificially activated by timeline. /// This is necessary because Awake() isn't called on inactive gameObjects. /// </summary> internal void EnsureStarted() { if (!m_WasStarted) { m_WasStarted = true; var extensions = GetComponentsInChildren<CinemachineExtension>(); 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[]; foreach (var vcam in vcams) vcam.LookAtTargetChanged = vcam.FollowTargetChanged = true; } #endif /// <summary> /// Locate the first component that implements AxisState.IInputAxisProvider. /// </summary> /// <returns>The first AxisState.IInputAxisProvider or null if none</returns> public AxisState.IInputAxisProvider GetInputAxisProvider() { var components = GetComponentsInChildren<MonoBehaviour>(); for (int i = 0; i < components.Length; ++i) { var provider = components[i] as AxisState.IInputAxisProvider; if (provider != null) return provider; } return null; } /// <summary>Enforce bounds for fields, when changed in inspector. /// Call base class implementation at the beginning of overridden method. /// After base method is called, ValidatingStreamVersion will be valid.</summary> protected virtual void OnValidate() { m_OnValidateCalled = true; ValidatingStreamVersion = m_StreamingVersion; m_StreamingVersion = CinemachineCore.kStreamingVersion; } /// <summary>Base class implementation adds the virtual camera from the priority queue.</summary> protected virtual void OnEnable() { UpdateSlaveStatus(); UpdateVcamPoolStatus(); // Add to queue if (!CinemachineCore.Instance.IsLive(this)) PreviousStateIsValid = false; CinemachineCore.Instance.CameraEnabled(this); InvalidateCachedTargets(); // Sanity check - if another vcam component is enabled, shut down var vcamComponents = GetComponents<CinemachineVirtualCameraBase>(); for (int i = 0; i < vcamComponents.Length; ++i) { if (vcamComponents[i].enabled && vcamComponents[i] != this) { Debug.LogError(Name + " has multiple CinemachineVirtualCameraBase-derived components. Disabling " + GetType().Name + "."); enabled = false; } } } /// <summary>Base class implementation makes sure the priority queue remains up-to-date.</summary> protected virtual void OnDisable() { UpdateVcamPoolStatus(); // Remove from queue CinemachineCore.Instance.CameraDisabled(this); } /// <summary>Base class implementation makes sure the priority queue remains up-to-date.</summary> protected virtual void Update() { if (m_Priority != m_QueuePriority) { UpdateVcamPoolStatus(); // Force a re-sort } } private bool mSlaveStatusUpdated = false; private CinemachineVirtualCameraBase m_parentVcam = null; private void UpdateSlaveStatus() { mSlaveStatusUpdated = true; m_parentVcam = null; Transform p = transform.parent; if (p != null) { #if UNITY_2019_2_OR_NEWER p.TryGetComponent(out m_parentVcam); #else m_parentVcam = p.GetComponent<CinemachineVirtualCameraBase>(); #endif } } /// <summary>Returns this vcam's LookAt target, or if that is null, will retrun /// the parent vcam's LookAt target.</summary> /// <param name="localLookAt">This vcam's LookAt value.</param> /// <returns>The same value, or the parent's if null and a parent exists.</returns> public Transform ResolveLookAt(Transform localLookAt) { Transform lookAt = localLookAt; if (lookAt == null && ParentCamera != null) lookAt = ParentCamera.LookAt; // Parent provides default return lookAt; } /// <summary>Returns this vcam's Follow target, or if that is null, will retrun /// the parent vcam's Follow target.</summary> /// <param name="localFollow">This vcam's Follow value.</param> /// <returns>The same value, or the parent's if null and a parent exists.</returns> public Transform ResolveFollow(Transform localFollow) { Transform follow = localFollow; if (follow == null && ParentCamera != null) follow = ParentCamera.Follow; // Parent provides default return follow; } private int m_QueuePriority = int.MaxValue; private void UpdateVcamPoolStatus() { CinemachineCore.Instance.RemoveActiveCamera(this); if (m_parentVcam == null && isActiveAndEnabled) CinemachineCore.Instance.AddActiveCamera(this); m_QueuePriority = m_Priority; } /// <summary>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 subqueue. /// 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.</summary> public void MoveToTopOfPrioritySubqueue() { UpdateVcamPoolStatus(); // Force a re-sort } /// <summary>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 seamlessy.</summary> /// <param name="target">The object that was warped</param> /// <param name="positionDelta">The amount the target's position changed</param> public virtual void OnTargetObjectWarped(Transform target, Vector3 positionDelta) { // inform the extensions if (mExtensions != null) { for (int i = 0; i < mExtensions.Count; ++i) mExtensions[i].OnTargetObjectWarped(target, positionDelta); } } /// <summary> /// Force the virtual camera to assume a given position and orientation /// </summary> /// <param name="pos">Worldspace pposition to take</param> /// <param name="rot">Worldspace orientation to take</param> public virtual void ForceCameraPosition(Vector3 pos, Quaternion rot) { // inform the extensions if (mExtensions != null) { for (int i = 0; i < mExtensions.Count; ++i) mExtensions[i].ForceCameraPosition(pos, rot); } } // Doesn't really belong here but putting it here to avoid changing // the API (belongs in the caller of CreateBlend). GML todo: Fix in next version. float m_blendStartPosition; // This is a total hack, because of missing API call. GML todo: Fix in next version. bool GetInheritPosition(ICinemachineCamera cam) { if (cam is CinemachineVirtualCamera) return (cam as CinemachineVirtualCamera).m_Transitions.m_InheritPosition; if (cam is CinemachineFreeLook) return (cam as CinemachineFreeLook).m_Transitions.m_InheritPosition; return false; } /// <summary>Create a blend between 2 virtual cameras, taking into account /// any existing active blend, with special case handling if the new blend is /// effectively an undo of the current blend</summary> /// <param name="camA">Outgoing virtual camera</param> /// <param name="camB">Incoming virtual camera</param> /// <param name="blendDef">Definition of the blend to create</param> /// <param name="activeBlend">The current active blend</param> /// <returns>The new blend</returns> protected CinemachineBlend CreateBlend( ICinemachineCamera camA, ICinemachineCamera camB, CinemachineBlendDefinition blendDef, CinemachineBlend activeBlend) { if (blendDef.BlendCurve == null || blendDef.BlendTime <= 0 || (camA == null && camB == null)) { m_blendStartPosition = 0; return null; } if (activeBlend != null) { // Special case: if backing out of a blend-in-progress // with the same blend in reverse, adjust the blend time // to cancel out the progress made in the opposite direction if (activeBlend != null && !activeBlend.IsComplete && activeBlend.CamA == camB && activeBlend.CamB == camA) { // How far have we blended? That is what we must undo var progress = m_blendStartPosition + (1 - m_blendStartPosition) * activeBlend.TimeInBlend / activeBlend.Duration; blendDef.m_Time *= progress; m_blendStartPosition = 1 - progress; } else m_blendStartPosition = 0; if (GetInheritPosition(camB)) camA = null; // otherwise we get a pop when camB is moved else camA = new BlendSourceVirtualCamera(activeBlend); } if (camA == null) camA = new StaticPointVirtualCamera(State, "(none)"); return new CinemachineBlend( camA, camB, blendDef.BlendCurve, blendDef.BlendTime, 0); } /// <summary> /// Create a camera state based on the current transform of this vcam /// </summary> /// <param name="worldUp">Current World Up direction, as provided by the brain</param> /// <param name="lens">Lens settings to serve as base, will be combined with lens from brain, if any</param> /// <returns></returns> 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.Instance.FindPotentialTargetBrain(this); if (brain != null) lens.SnapshotCameraReadOnlyProperties(brain.OutputCamera); state.Lens = lens; return state; } private Transform m_CachedFollowTarget; private CinemachineVirtualCameraBase m_CachedFollowTargetVcam; private ICinemachineTargetGroup m_CachedFollowTargetGroup; private Transform m_CachedLookAtTarget; private CinemachineVirtualCameraBase m_CachedLookAtTargetVcam; private ICinemachineTargetGroup m_CachedLookAtTargetGroup; private 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<CinemachineVirtualCameraBase> (FindObjectsInactive.Include, FindObjectsSortMode.None); #elif UNITY_2020_1_OR_NEWER var vcams = FindObjectsOfType<CinemachineVirtualCameraBase>(true); #else var vcams = FindObjectsOfType<CinemachineVirtualCameraBase>(); #endif foreach (var vcam in vcams) vcam.InvalidateCachedTargets(); } } #endif /// <summary> /// This property is true if the Follow target was changed this frame. /// </summary> public bool FollowTargetChanged { get; private set; } /// <summary> /// This property is true if the LookAttarget was changed this frame. /// </summary> public bool LookAtTargetChanged { get; private set; } /// <summary> /// Call this from InternalUpdateCameraState() to check for changed /// targets and update the target cache. This is needed for tracking /// when a target object changes. /// </summary> protected 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<CinemachineVirtualCameraBase>(out m_CachedFollowTargetVcam); target.TryGetComponent<ICinemachineTargetGroup>(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<CinemachineVirtualCameraBase>(out m_CachedLookAtTargetVcam); target.TryGetComponent<ICinemachineTargetGroup>(out m_CachedLookAtTargetGroup); } } } /// <summary>Get Follow target as ICinemachineTargetGroup, /// or null if target is not a ICinemachineTargetGroup</summary> public ICinemachineTargetGroup AbstractFollowTargetGroup => m_CachedFollowTargetGroup; /// <summary>Get Follow target as CinemachineVirtualCameraBase, /// or null if target is not a CinemachineVirtualCameraBase</summary> public CinemachineVirtualCameraBase FollowTargetAsVcam => m_CachedFollowTargetVcam; /// <summary>Get LookAt target as ICinemachineTargetGroup, /// or null if target is not a ICinemachineTargetGroup</summary> public ICinemachineTargetGroup AbstractLookAtTargetGroup => m_CachedLookAtTargetGroup; /// <summary>Get LookAt target as CinemachineVirtualCameraBase, /// or null if target is not a CinemachineVirtualCameraBase</summary> public CinemachineVirtualCameraBase LookAtTargetAsVcam => m_CachedLookAtTargetVcam; /// <summary>Pre-Serialization handler - delegates to derived classes</summary> void ISerializationCallbackReceiver.OnBeforeSerialize() => OnBeforeSerialize(); /// <summary>Post-Serialization handler - performs legacy upgrade</summary> void ISerializationCallbackReceiver.OnAfterDeserialize() { if (m_StreamingVersion < CinemachineCore.kStreamingVersion) LegacyUpgrade(m_StreamingVersion); m_StreamingVersion = CinemachineCore.kStreamingVersion; } /// <summary> /// Override this to handle any upgrades necessitated by a streaming version change /// </summary> /// <param name="streamedVersion">The version that was streamed</param> internal protected virtual void LegacyUpgrade(int streamedVersion) {} internal virtual void OnBeforeSerialize() {} /// <summary> /// Temporarily cancel damping for this frame. The camera will sanp to its target /// position when it is updated. /// </summary> /// <param name="updateNow">If true, snap the camera to its target immediately, otherwise wait /// until the end of the frame when cameras are normally updated.</param> public void CancelDamping(bool updateNow = false) { PreviousStateIsValid = false; if (updateNow) { var up = State.ReferenceUp; var brain = CinemachineCore.Instance.FindPotentialTargetBrain(this); if (brain != null) up = brain.DefaultWorldUp; InternalUpdateCameraState(up, -1); } } } }