using UnityEngine; using System.Collections.Generic; namespace Unity.Cinemachine { /// Owns Camera Registry. /// Provides services to update cinemachine cameras and keep track of /// whether and how they have been updated each frame. static class CameraUpdateManager { static readonly VirtualCameraRegistry s_CameraRegistry = new (); static CinemachineVirtualCameraBase s_RoundRobinVcamLastFrame = null; static float s_LastUpdateTime; static int s_FixedFrameCount; // Current fixed frame count class UpdateStatus { public int lastUpdateFrame; public int lastUpdateFixedFrame; public UpdateTracker.UpdateClock lastUpdateMode; public float lastUpdateDeltaTime; } static Dictionary s_UpdateStatus; [RuntimeInitializeOnLoadMethod] static void InitializeModule() => s_UpdateStatus = new (); /// Internal use only internal enum UpdateFilter { Fixed = UpdateTracker.UpdateClock.Fixed, Late = UpdateTracker.UpdateClock.Late, Smart = 8, // meant to be or'ed with the others SmartFixed = Smart | Fixed, SmartLate = Smart | Late } internal static UpdateFilter s_CurrentUpdateFilter; /// /// List of all active CinemachineCameras for all brains. /// This list is kept sorted by priority. /// public static int VirtualCameraCount => s_CameraRegistry.ActiveCameraCount; /// Access the priority-sorted array of active ICinemachineCamera in the scene /// without generating garbage /// Index of the camera to access, range 0-VirtualCameraCount /// The virtual camera at the specified index public static CinemachineVirtualCameraBase GetVirtualCamera(int index) => s_CameraRegistry.GetActiveCamera(index); /// Called when a CinemachineCamera is enabled. internal static void AddActiveCamera(CinemachineVirtualCameraBase vcam) => s_CameraRegistry.AddActiveCamera(vcam); /// Called when a CinemachineCamera is disabled. internal static void RemoveActiveCamera(CinemachineVirtualCameraBase vcam) => s_CameraRegistry.RemoveActiveCamera(vcam); /// Called when a CinemachineCamera is destroyed. internal static void CameraDestroyed(CinemachineVirtualCameraBase vcam) { s_CameraRegistry.CameraDestroyed(vcam); if (s_UpdateStatus != null && s_UpdateStatus.ContainsKey(vcam)) s_UpdateStatus.Remove(vcam); } /// Called when a vcam is enabled. internal static void CameraEnabled(CinemachineVirtualCameraBase vcam) => s_CameraRegistry.CameraEnabled(vcam); /// Called when a vcam is disabled. internal static void CameraDisabled(CinemachineVirtualCameraBase vcam) { s_CameraRegistry.CameraDisabled(vcam); if (s_RoundRobinVcamLastFrame == vcam) s_RoundRobinVcamLastFrame = null; } /// Update all the active vcams in the scene, in the correct dependency order. internal static void UpdateAllActiveVirtualCameras(uint channelMask, Vector3 worldUp, float deltaTime) { // Setup for roundRobin standby updating var filter = s_CurrentUpdateFilter; bool canUpdateStandby = (filter != UpdateFilter.SmartFixed); // never in smart fixed var currentRoundRobin = s_RoundRobinVcamLastFrame; // Update the fixed frame count float now = CinemachineCore.CurrentTime; if (now != s_LastUpdateTime) { s_LastUpdateTime = now; if ((filter & ~UpdateFilter.Smart) == UpdateFilter.Fixed) ++s_FixedFrameCount; } // Update the leaf-most cameras first var allCameras = s_CameraRegistry.AllCamerasSortedByNestingLevel; for (int i = allCameras.Count-1; i >= 0; --i) { var sublist = allCameras[i]; for (int j = sublist.Count - 1; j >= 0; --j) { var vcam = sublist[j]; if (canUpdateStandby && vcam == s_RoundRobinVcamLastFrame) currentRoundRobin = null; // update the next roundrobin candidate if (vcam == null) { sublist.RemoveAt(j); continue; // deleted } if (vcam.StandbyUpdate == CinemachineVirtualCameraBase.StandbyUpdateMode.Always || CinemachineCore.IsLive(vcam)) { // Skip this vcam if it's not on the channel mask if (((uint)vcam.OutputChannel & channelMask) != 0) UpdateVirtualCamera(vcam, worldUp, deltaTime); } else if (currentRoundRobin == null && s_RoundRobinVcamLastFrame != vcam && canUpdateStandby && vcam.StandbyUpdate != CinemachineVirtualCameraBase.StandbyUpdateMode.Never && vcam.isActiveAndEnabled) { // Do the round-robin update s_CurrentUpdateFilter &= ~UpdateFilter.Smart; // force it UpdateVirtualCamera(vcam, worldUp, deltaTime); s_CurrentUpdateFilter = filter; currentRoundRobin = vcam; } } } // Did we manage to update a roundrobin? if (canUpdateStandby) { if (currentRoundRobin == s_RoundRobinVcamLastFrame) currentRoundRobin = null; // take the first candidate s_RoundRobinVcamLastFrame = currentRoundRobin; } } /// /// Update a single CinemachineCamera if and only if it /// hasn't already been updated this frame. Always update vcams via this method. /// Calling this more than once per frame for the same camera will have no effect. /// internal static void UpdateVirtualCamera( CinemachineVirtualCameraBase vcam, Vector3 worldUp, float deltaTime) { if (vcam == null) return; bool isSmartUpdate = (s_CurrentUpdateFilter & UpdateFilter.Smart) == UpdateFilter.Smart; var updateClock = (UpdateTracker.UpdateClock)(s_CurrentUpdateFilter & ~UpdateFilter.Smart); // If we're in smart update mode and the target moved, then we must examine // how the target has been moving recently in order to figure out whether to // update now if (isSmartUpdate) { var updateTarget = GetUpdateTarget(vcam); if (updateTarget == null) return; // vcam deleted if (UpdateTracker.GetPreferredUpdate(updateTarget) != updateClock) return; // wrong clock } // Have we already been updated this frame? if (s_UpdateStatus == null) s_UpdateStatus = new(); if (!s_UpdateStatus.TryGetValue(vcam, out UpdateStatus status)) { status = new UpdateStatus { lastUpdateDeltaTime = -2, lastUpdateMode = UpdateTracker.UpdateClock.Late, lastUpdateFrame = Time.frameCount + 2, // so that frameDelta ends up negative lastUpdateFixedFrame = s_FixedFrameCount + 2 }; s_UpdateStatus.Add(vcam, status); } int frameDelta = (updateClock == UpdateTracker.UpdateClock.Late) ? Time.frameCount - status.lastUpdateFrame : s_FixedFrameCount - status.lastUpdateFixedFrame; if (deltaTime >= 0) { if (frameDelta == 0 && status.lastUpdateMode == updateClock && status.lastUpdateDeltaTime == deltaTime) return; // already updated if (!CinemachineCore.UnitTestMode && frameDelta > 0) deltaTime *= frameDelta; // try to catch up if multiple frames } //Debug.Log((vcam.ParentCamera == null ? "" : vcam.ParentCamera.Name + ".") + vcam.Name + ": frame " + Time.frameCount + "/" + status.lastUpdateFixedFrame + ", " + s_CurrentUpdateFilter + ", deltaTime = " + deltaTime); vcam.InternalUpdateCameraState(worldUp, deltaTime); status.lastUpdateFrame = Time.frameCount; status.lastUpdateFixedFrame = s_FixedFrameCount; status.lastUpdateMode = updateClock; status.lastUpdateDeltaTime = deltaTime; } static Transform GetUpdateTarget(CinemachineVirtualCameraBase vcam) { if (vcam == null || vcam.gameObject == null) return null; var target = vcam.LookAt; if (target != null) return target; target = vcam.Follow; if (target != null) return target; // If no target, use the vcam itself return vcam.transform; } /// Internal use only - inspector internal static UpdateTracker.UpdateClock GetVcamUpdateStatus(CinemachineVirtualCameraBase vcam) { if (s_UpdateStatus == null || !s_UpdateStatus.TryGetValue(vcam, out UpdateStatus status)) return UpdateTracker.UpdateClock.Late; return status.lastUpdateMode; } } }