using System.Collections.Generic; using UnityEngine; namespace Unity.Cinemachine { /// /// This class manages a current active camera and an stack of camera override frames. /// It takes care of looking up BlendDefinitions when blends need to be created, /// and it generates ActivationEvents for camweras when necessary. /// class BlendManager : CameraBlendStack { // Current blend State - result of all frames. Blend camB is "current" camera always CinemachineBlend m_CurrentLiveCameras = new (); // Blend state last frame, used for computing deltas CinemachineBlend m_PreviousLiveCameras = new (); // This is to control GC allocs when generating camera deactivated events List m_CameraCache = new(); /// Get the current active virtual camera. public ICinemachineCamera ActiveVirtualCamera => DeepCamBFromBlend(m_CurrentLiveCameras); static ICinemachineCamera DeepCamBFromBlend(CinemachineBlend blend) { var cam = blend?.CamB; while (cam is NestedBlendSource bs) cam = bs.Blend.CamB; if (cam != null && cam.IsValid) return cam; return null; } /// /// 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. /// public CinemachineBlend ActiveBlend { get { if (m_CurrentLiveCameras.CamA == null || m_CurrentLiveCameras.IsComplete) return null; return m_CurrentLiveCameras; } set => SetRootBlend(value); } /// /// Is there a blend in progress? /// public bool IsBlending => ActiveBlend != null; /// Describe the current blend state public string Description { get { if (ActiveVirtualCamera == null) return "[(none)]"; var sb = CinemachineDebug.SBFromPool(); sb.Append("["); sb.Append(IsBlending ? ActiveBlend.Description : ActiveVirtualCamera.Name); sb.Append("]"); var text = sb.ToString(); CinemachineDebug.ReturnToPool(sb); return text; } } /// /// Checks if the vcam is live as part of an outgoing blend. /// Does not check whether the vcam is also the current active vcam. /// /// The virtual camera to check /// True if the virtual camera is part of a live outgoing blend, false otherwise public bool IsLiveInBlend(ICinemachineCamera cam) { // Ignore m_CurrentLiveCameras.CamB if (cam != null) { if (cam == m_CurrentLiveCameras.CamA) return true; if (m_CurrentLiveCameras.CamA is NestedBlendSource b && b.Blend.Uses(cam)) return true; } return false; } /// /// True if the ICinemachineCamera is the current active camera, /// or part of a current blend, either directly or indirectly because its parents are live. /// /// The camera to test whether it is live /// True if the camera is live (directly or indirectly) /// or part of a blend in progress. public bool IsLive(ICinemachineCamera cam) => m_CurrentLiveCameras.Uses(cam); /// Get the current state, representing the active camera and blend state. public CameraState CameraState => m_CurrentLiveCameras.State; /// /// Compute the current blend, taking into account /// the in-game camera and all the active overrides. Caller may optionally /// exclude n topmost overrides. /// public void ComputeCurrentBlend() { m_PreviousLiveCameras.CopyFrom(m_CurrentLiveCameras); ProcessOverrideFrames(ref m_CurrentLiveCameras, 0); } /// /// Special support for fixed update: cameras that have been enabled /// since the last physics frame can be updated now. /// Current world up /// Current delta time for this update frame /// public void RefreshCurrentCameraState(Vector3 up, float deltaTime) => m_CurrentLiveCameras.UpdateCameraState(up, deltaTime); /// /// Once the current blend has been computed, process it and generate camera activation events. /// /// Current world up /// Current delta time for this update frame /// Active virtual camera this frame public ICinemachineCamera ProcessActiveCamera(ICinemachineMixer mixer, Vector3 up, float deltaTime) { // Send deactivation events m_CameraCache.Clear(); CollectLiveCameras(m_PreviousLiveCameras, ref m_CameraCache); for (int i = 0; i < m_CameraCache.Count; ++i) if (!IsLive(m_CameraCache[i])) CinemachineCore.CameraDeactivatedEvent.Invoke(mixer, m_CameraCache[i]); // Process newly activated cameras var incomingCamera = ActiveVirtualCamera; if (incomingCamera != null && incomingCamera.IsValid) { // Has the current camera changed this frame? var outgoingCamera = DeepCamBFromBlend(m_PreviousLiveCameras); if (outgoingCamera != null && !outgoingCamera.IsValid) outgoingCamera = null; // object was deleted if (incomingCamera == outgoingCamera) { // Send a blend completeed event if appropriate if (m_PreviousLiveCameras.CamA != null && m_CurrentLiveCameras.CamA == null) CinemachineCore.BlendFinishedEvent.Invoke(mixer, incomingCamera); } else { // Generate ActivationEvents var eventOutgoing = outgoingCamera; if (IsBlending) { eventOutgoing = new NestedBlendSource(ActiveBlend); eventOutgoing.UpdateCameraState(up, deltaTime); } var evt = new ICinemachineCamera.ActivationEventParams { Origin = mixer, OutgoingCamera = eventOutgoing, IncomingCamera = incomingCamera, IsCut = !IsBlending,// does not work with snapshots: || !IsLive(outgoingCamera), WorldUp = up, DeltaTime = deltaTime }; incomingCamera.OnCameraActivated(evt); mixer.OnCameraActivated(evt); CinemachineCore.CameraActivatedEvent.Invoke(evt); // Re-update in case it's inactive incomingCamera.UpdateCameraState(up, deltaTime); } } return incomingCamera; // local method - find all the live cameras in a blend static void CollectLiveCameras(CinemachineBlend blend, ref List cams) { if (blend.CamA is NestedBlendSource a && a.Blend != null) CollectLiveCameras(a.Blend, ref cams); else if (blend.CamA != null) cams.Add(blend.CamA); if (blend.CamB is NestedBlendSource b && b.Blend != null) CollectLiveCameras(b.Blend, ref cams); else if (blend.CamB != null) cams.Add(blend.CamB); } } } }