using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Serialization; namespace Unity.Cinemachine { /// /// This is a virtual camera "manager" that owns and manages a collection /// of child Cm Cameras. /// public abstract class CinemachineCameraManagerBase : CinemachineVirtualCameraBase, ICinemachineMixer { /// If enabled, a default target will be available. It will be used /// if a child rig needs a target and doesn't specify one itself. [Serializable] public struct DefaultTargetSettings { /// If enabled, a default target will be available. It will be used /// if a child rig needs a target and doesn't specify one itself. [Tooltip("If enabled, a default target will be available. It will be used " + "if a child rig needs a target and doesn't specify one itself.")] public bool Enabled; /// Default target for the camera children, which may be used if the child rig /// does not specify a target of its own. [NoSaveDuringPlay] [Tooltip("Default target for the camera children, which may be used if the child rig " + "does not specify a target of its own.")] public CameraTarget Target; } /// If enabled, a default target will be available. It will be used /// if a child rig needs a target and doesn't specify one itself. [FoldoutWithEnabledButton] public DefaultTargetSettings DefaultTarget; /// /// The blend which is used if you don't explicitly define a blend between two Virtual Camera children. /// [Tooltip("The blend which is used if you don't explicitly define a blend between two Virtual Camera children")] [FormerlySerializedAs("m_DefaultBlend")] public CinemachineBlendDefinition DefaultBlend = new (CinemachineBlendDefinition.Styles.EaseInOut, 0.5f); /// /// This is the asset which contains custom settings for specific child blends. /// [Tooltip("This is the asset which contains custom settings for specific child blends")] [FormerlySerializedAs("m_CustomBlends")] [EmbeddedBlenderSettingsProperty] public CinemachineBlenderSettings CustomBlends = null; List m_ChildCameras; readonly BlendManager m_BlendManager = new (); CameraState m_State = CameraState.Default; ICinemachineCamera m_TransitioningFrom; /// Reset the component to default values. protected virtual void Reset() { Priority = default; OutputChannel = OutputChannels.Default; DefaultTarget = default; InvalidateCameraCache(); } /// /// Standard MonoBehaviour OnEnable. Derived classes must call base class implementation. /// protected override void OnEnable() { base.OnEnable(); m_BlendManager.OnEnable(); m_BlendManager.LookupBlendDelegate = LookupBlend; InvalidateCameraCache(); } /// /// Standard MonoBehaviour OnDisable. Derived classes must call base class implementation. /// protected override void OnDisable() { m_BlendManager.OnDisable(); base.OnDisable(); } /// public override string Description => m_BlendManager.Description; /// public override CameraState State => m_State; /// public virtual bool IsLiveChild(ICinemachineCamera cam, bool dominantChildOnly = false) => m_BlendManager.IsLive(cam); /// The list of child cameras. These are just the immediate children in the hierarchy. public List ChildCameras { get { UpdateCameraCache(); return m_ChildCameras; } } /// public override bool PreviousStateIsValid { get => base.PreviousStateIsValid; set { base.PreviousStateIsValid = value; // Only propagate to the children when we're invalidating the state if (value == false) for (int i = 0; m_ChildCameras != null && i < m_ChildCameras.Count; ++i) m_ChildCameras[i].PreviousStateIsValid = value; } } /// Is there a blend in progress? public bool IsBlending => m_BlendManager.IsBlending; /// /// Get the current blend in progress. Returns null if none. /// It is also possible to set the current blend, but this is not a recommended usage /// unless it is to set the active blend to null, which will force completion of the blend. /// public CinemachineBlend ActiveBlend { get => PreviousStateIsValid ? m_BlendManager.ActiveBlend : null; set => m_BlendManager.ActiveBlend = value; } /// /// Get the current active camera. Will return null if no camera is active. /// public ICinemachineCamera LiveChild => PreviousStateIsValid ? m_BlendManager.ActiveVirtualCamera : null; /// Get the current LookAt target. Returns parent's LookAt if parent /// is non-null and no specific LookAt defined for this camera public override Transform LookAt { get { if (!DefaultTarget.Enabled) return null; return ResolveLookAt(DefaultTarget.Target.CustomLookAtTarget ? DefaultTarget.Target.LookAtTarget : DefaultTarget.Target.TrackingTarget); } set { DefaultTarget.Enabled = true; DefaultTarget.Target.CustomLookAtTarget = true; DefaultTarget.Target.LookAtTarget = value; } } /// Get the current Follow target. Returns parent's Follow if parent /// is non-null and no specific Follow defined for this camera public override Transform Follow { get { if (!DefaultTarget.Enabled) return null; return ResolveFollow(DefaultTarget.Target.TrackingTarget); } set { DefaultTarget.Enabled = true; DefaultTarget.Target.TrackingTarget = value; } } /// 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. This implementation /// updates all the children, chooses the best one, and implements any required blending. /// Default world Up, set by the CinemachineBrain /// Delta time for time-based effects (ignore if less than or equal to 0) public override void InternalUpdateCameraState(Vector3 worldUp, float deltaTime) { UpdateCameraCache(); if (!PreviousStateIsValid) ResetLiveChild(); // Choose the best camera - auto-activate it if it's inactive var best = ChooseCurrentCamera(worldUp, deltaTime); if (best != null && !best.gameObject.activeInHierarchy) { best.gameObject.SetActive(true); best.UpdateCameraState(worldUp, deltaTime); } SetLiveChild(best, worldUp, deltaTime); // Special case to handle being called from OnTransitionFromCamera() - GML todo: fix this if (m_TransitioningFrom != null && !IsBlending && LiveChild != null) { LiveChild.OnCameraActivated(new ICinemachineCamera.ActivationEventParams { Origin = this, OutgoingCamera = m_TransitioningFrom, IncomingCamera = LiveChild, IsCut = false, WorldUp = worldUp, DeltaTime = deltaTime }); } FinalizeCameraState(deltaTime); m_TransitioningFrom = null; PreviousStateIsValid = true; } /// Find a blend curve for blending from one child camera to another. /// The camera we're blending from. /// The camera we're blending to. /// The blend to use for this camera transition. protected virtual CinemachineBlendDefinition LookupBlend(ICinemachineCamera outgoing, ICinemachineCamera incoming) { return CinemachineBlenderSettings.LookupBlend(outgoing, incoming, DefaultBlend, CustomBlends, this); } /// /// Choose the appropriate current camera from among the ChildCameras, based on current state. /// If the returned camera is different from the current camera, an appropriate transition will be made. /// /// Default world Up, set by the CinemachineBrain /// Delta time for time-based effects (ignore if less than or equal to 0) /// The current child camera that should be active. Must be present in ChildCameras. protected abstract CinemachineVirtualCameraBase ChooseCurrentCamera(Vector3 worldUp, float deltaTime); /// 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) { UpdateCameraCache(); for (int i = 0; i < m_ChildCameras.Count; ++i) m_ChildCameras[i].OnTargetObjectWarped(target, positionDelta); base.OnTargetObjectWarped(target, positionDelta); } /// /// Force the virtual camera to assume a given position and orientation /// /// World-space position to take /// World-space orientation to take public override void ForceCameraPosition(Vector3 pos, Quaternion rot) { UpdateCameraCache(); for (int i = 0; i < m_ChildCameras.Count; ++i) m_ChildCameras[i].ForceCameraPosition(pos, rot); base.ForceCameraPosition(pos, rot); } /// Notification that this virtual camera is going live. /// 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); m_TransitioningFrom = fromCam; InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime); InternalUpdateCameraState(worldUp, deltaTime); } /// Force a rebuild of the child camera cache. /// Call this if CinemachineCamera children are added or removed dynamically public void InvalidateCameraCache() { m_ChildCameras = null; PreviousStateIsValid = false; } /// Rebuild the camera cache if it's been invalidated /// True if a cache rebuild was performed, false if cache is up to date. protected virtual bool UpdateCameraCache() { if (m_ChildCameras != null) return false; PreviousStateIsValid = false; m_ChildCameras = new(); GetComponentsInChildren(true, m_ChildCameras); for (int i = m_ChildCameras.Count-1; i >= 0; --i) if (m_ChildCameras[i].transform.parent != transform) m_ChildCameras.RemoveAt(i); return true; } /// Makes sure the internal child cache is up to date protected virtual void OnTransformChildrenChanged() => InvalidateCameraCache(); /// /// Set the current active camera. All necessary blends will be created, and events generated. /// /// Current active camera /// Current world up /// Current deltaTime applicable for this frame protected void SetLiveChild( ICinemachineCamera activeCamera, Vector3 worldUp, float deltaTime) { m_BlendManager.UpdateRootFrame(this, activeCamera, worldUp, deltaTime); m_BlendManager.ComputeCurrentBlend(); m_BlendManager.ProcessActiveCamera(this, worldUp, deltaTime); } /// Cancel current active camera and all blends protected void ResetLiveChild() => m_BlendManager.ResetRootFrame(); /// At the end of InternalUpdateCameraState, call this to finalize the state /// Current deltaTime for this frame protected void FinalizeCameraState(float deltaTime) { m_State = m_BlendManager.CameraState; InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Finalize, ref m_State, deltaTime); } } }