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. internal static class CameraUpdateManager { static readonly VirtualCameraRegistry s_CameraRegistry = new (); static int s_RoundRobinIndex = 0; static int s_RoundRobinSubIndex = 0; static object s_LastFixedUpdateContext; static float s_LastUpdateTime = 0; static int s_FixedFrameCount = 0; // Current fixed frame count class UpdateStatus { public int lastUpdateFrame; public int lastUpdateFixedFrame; public UpdateTracker.UpdateClock lastUpdateMode; } static Dictionary s_UpdateStatus; [RuntimeInitializeOnLoadMethod] static void InitializeModule() => s_UpdateStatus = new (); /// Internal use only public 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 } public 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. public static void AddActiveCamera(CinemachineVirtualCameraBase vcam) => s_CameraRegistry.AddActiveCamera(vcam); /// Called when a CinemachineCamera is disabled. public static void RemoveActiveCamera(CinemachineVirtualCameraBase vcam) => s_CameraRegistry.RemoveActiveCamera(vcam); /// Called when a CinemachineCamera is destroyed. public 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. public static void CameraEnabled(CinemachineVirtualCameraBase vcam) => s_CameraRegistry.CameraEnabled(vcam); /// Called when a vcam is disabled. public static void CameraDisabled(CinemachineVirtualCameraBase vcam) => s_CameraRegistry.CameraDisabled(vcam); public static void ForgetContext(object context) { if (s_LastFixedUpdateContext == context) s_LastFixedUpdateContext = null; } /// Update all the active vcams in the scene, in the correct dependency order. public static void UpdateAllActiveVirtualCameras(uint channelMask, Vector3 worldUp, float deltaTime, object context) { // Update the fixed frame count - do it only once per fixed frmae if ((s_CurrentUpdateFilter & ~UpdateFilter.Smart) == UpdateFilter.Fixed && (s_LastFixedUpdateContext == null || s_LastFixedUpdateContext == context)) { ++s_FixedFrameCount; s_LastFixedUpdateContext = context; } // Advance the round-robin index once per rendered frame var allCameras = s_CameraRegistry.AllCamerasSortedByNestingLevel; float now = CinemachineCore.CurrentTime; if (now != s_LastUpdateTime) { s_LastUpdateTime = now; if (allCameras.Count > 0) { if (s_RoundRobinIndex >= allCameras.Count) s_RoundRobinIndex = 0; if (++s_RoundRobinSubIndex >= allCameras[s_RoundRobinIndex].Count) { s_RoundRobinSubIndex = 0; if (++s_RoundRobinIndex >= allCameras.Count) s_RoundRobinIndex = 0; } } } // Update the leaf-most cameras first 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 (vcam == null) { sublist.RemoveAt(j); continue; // deleted } // Skip this vcam if it's not on the channel mask if (((uint)vcam.OutputChannel & channelMask) == 0) continue; if (CinemachineCore.IsLive(vcam) || vcam.StandbyUpdate == CinemachineVirtualCameraBase.StandbyUpdateMode.Always) { UpdateVirtualCamera(vcam, worldUp, deltaTime); } // Do round-robin update else if (vcam.StandbyUpdate == CinemachineVirtualCameraBase.StandbyUpdateMode.RoundRobin && s_RoundRobinIndex == i && s_RoundRobinSubIndex == j && vcam.isActiveAndEnabled) { UpdateVirtualCamera(vcam, worldUp, deltaTime); } } } } /// /// 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. /// public 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? s_UpdateStatus ??= new(); if (!s_UpdateStatus.TryGetValue(vcam, out UpdateStatus status)) { status = new UpdateStatus { 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) 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; } 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 (maybe its position is being animated) return vcam.transform; } /// Internal use only - inspector public static UpdateTracker.UpdateClock GetVcamUpdateStatus(CinemachineVirtualCameraBase vcam) { if (s_UpdateStatus == null || !s_UpdateStatus.TryGetValue(vcam, out UpdateStatus status)) return UpdateTracker.UpdateClock.Late; return status.lastUpdateMode; } } }