using UnityEngine; using System.Collections.Generic; using UnityEngine.Assertions; namespace Cinemachine { internal static class Documentation { /// This must be used like /// [HelpURL(Documentation.BaseURL + "api/some-page.html")] /// or /// [HelpURL(Documentation.BaseURL + "manual/some-page.html")] /// It cannot support String.Format nor string interpolation public const string BaseURL = "https://docs.unity3d.com/Packages/com.unity.cinemachine@2.9/"; } /// A singleton that manages complete lists of CinemachineBrain and, /// Cinemachine Virtual Cameras, and the priority queue. Provides /// services to keeping track of whether Cinemachine Virtual Cameras have /// been updated each frame. public sealed class CinemachineCore { /// Data version string. Used to upgrade from legacy projects public static readonly int kStreamingVersion = 20170927; /// /// Stages in the Cinemachine Component pipeline, used for /// UI organization>. This enum defines the pipeline order. /// public enum Stage { /// Second stage: position the camera in space Body, /// Third stage: orient the camera to point at the target Aim, /// Final pipeline stage: apply noise (this is done separately, in the /// Correction channel of the CameraState) Noise, /// Post-correction stage. This is invoked on all virtual camera /// types, after the pipeline is complete Finalize }; private static CinemachineCore sInstance = null; /// Get the singleton instance public static CinemachineCore Instance { get { if (sInstance == null) sInstance = new CinemachineCore(); return sInstance; } } /// /// If true, show hidden Cinemachine objects, to make manual script mapping possible. /// public static bool sShowHiddenObjects = false; /// Delegate for overriding Unity's default input system. Returns the value /// of the named axis. public delegate float AxisInputDelegate(string axisName); /// Delegate for overriding Unity's default input system. /// If you set this, then your delegate will be called instead of /// System.Input.GetAxis(axisName) whenever in-game user input is needed. #if ENABLE_LEGACY_INPUT_MANAGER public static AxisInputDelegate GetInputAxis = UnityEngine.Input.GetAxis; #else public static AxisInputDelegate GetInputAxis = delegate { return 0; }; #endif /// /// If non-negative, cinemachine will update with this uniform delta time. /// Usage is for timelines in manual update mode. /// public static float UniformDeltaTimeOverride = -1; /// /// Replacement for Time.deltaTime, taking UniformDeltaTimeOverride into account. /// public static float DeltaTime => UniformDeltaTimeOverride >= 0 ? UniformDeltaTimeOverride : Time.deltaTime; /// /// If non-negative, cinemachine willuse this value whenever it wants current game time. /// Usage is for master timelines in manual update mode, for deterministic behaviour. /// public static float CurrentTimeOverride = -1; /// /// Replacement for Time.time, taking CurrentTimeTimeOverride into account. /// public static float CurrentTime => CurrentTimeOverride >= 0 ? CurrentTimeOverride : Time.time; /// /// Delegate for overriding a blend that is about to be applied to a transition. /// A handler can either return the default blend, or a new blend specific to /// current conditions. /// /// The outgoing virtual camera /// Yhe incoming virtual camera /// The blend that would normally be applied /// The context in which the blend is taking place. /// Can be a CinemachineBrain, or CinemachineStateDrivenCamera, or other manager /// object that can initiate a blend /// The blend definition to use for this transition. public delegate CinemachineBlendDefinition GetBlendOverrideDelegate( ICinemachineCamera fromVcam, ICinemachineCamera toVcam, CinemachineBlendDefinition defaultBlend, MonoBehaviour owner); /// /// Delegate for overriding a blend that is about to be applied to a transition. /// A handler can either return the default blend, or a new blend specific to /// current conditions. /// public static GetBlendOverrideDelegate GetBlendOverride; /// This event will fire after a brain updates its Camera public static CinemachineBrain.BrainEvent CameraUpdatedEvent = new CinemachineBrain.BrainEvent(); /// This event will fire after a brain updates its Camera public static CinemachineBrain.BrainEvent CameraCutEvent = new CinemachineBrain.BrainEvent(); /// List of all active CinemachineBrains. private List mActiveBrains = new List(); /// Access the array of active CinemachineBrains in the scene public int BrainCount { get { return mActiveBrains.Count; } } /// Enables frame delta compensation for not updated frames. False is useful for deterministic test results. internal static bool FrameDeltaCompensationEnabled = true; /// Access the array of active CinemachineBrains in the scene /// without generating garbage /// Index of the brain to access, range 0-BrainCount /// The brain at the specified index public CinemachineBrain GetActiveBrain(int index) { return mActiveBrains[index]; } /// Called when a CinemachineBrain is enabled. internal void AddActiveBrain(CinemachineBrain brain) { // First remove it, just in case it's being added twice RemoveActiveBrain(brain); mActiveBrains.Insert(0, brain); } /// Called when a CinemachineBrain is disabled. internal void RemoveActiveBrain(CinemachineBrain brain) { mActiveBrains.Remove(brain); } /// List of all active ICinemachineCameras. private List mActiveCameras = new List(); private bool m_ActiveCamerasAreSorted; private int m_ActivationSequence; /// /// List of all active Cinemachine Virtual Cameras for all brains. /// This list is kept sorted by priority. /// public int VirtualCameraCount { get { return mActiveCameras.Count; } } /// 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 CinemachineVirtualCameraBase GetVirtualCamera(int index) { if (!m_ActiveCamerasAreSorted && mActiveCameras.Count > 1) { mActiveCameras.Sort((x, y) => x.Priority == y.Priority ? y.m_ActivationId.CompareTo(x.m_ActivationId) : y.Priority.CompareTo(x.Priority)); m_ActiveCamerasAreSorted = true; } return mActiveCameras[index]; } /// Called when a Cinemachine Virtual Camera is enabled. internal void AddActiveCamera(CinemachineVirtualCameraBase vcam) { Assert.IsFalse(mActiveCameras.Contains(vcam)); vcam.m_ActivationId = m_ActivationSequence++; mActiveCameras.Add(vcam); m_ActiveCamerasAreSorted = false; } /// Called when a Cinemachine Virtual Camera is disabled. internal void RemoveActiveCamera(CinemachineVirtualCameraBase vcam) { if (mActiveCameras.Contains(vcam)) mActiveCameras.Remove(vcam); } /// Called when a Cinemachine Virtual Camera is destroyed. internal void CameraDestroyed(CinemachineVirtualCameraBase vcam) { if (mActiveCameras.Contains(vcam)) mActiveCameras.Remove(vcam); if (mUpdateStatus != null && mUpdateStatus.ContainsKey(vcam)) mUpdateStatus.Remove(vcam); } // Registry of all vcams that are present, active or not private List> mAllCameras = new List>(); /// Called when a vcam is enabled. internal void CameraEnabled(CinemachineVirtualCameraBase vcam) { int parentLevel = 0; for (ICinemachineCamera p = vcam.ParentCamera; p != null; p = p.ParentCamera) ++parentLevel; while (mAllCameras.Count <= parentLevel) mAllCameras.Add(new List()); mAllCameras[parentLevel].Add(vcam); } /// Called when a vcam is disabled. internal void CameraDisabled(CinemachineVirtualCameraBase vcam) { for (int i = 0; i < mAllCameras.Count; ++i) mAllCameras[i].Remove(vcam); if (mRoundRobinVcamLastFrame == vcam) mRoundRobinVcamLastFrame = null; } CinemachineVirtualCameraBase mRoundRobinVcamLastFrame = null; static float s_LastUpdateTime; static int s_FixedFrameCount; // Current fixed frame count /// Update all the active vcams in the scene, in the correct dependency order. internal void UpdateAllActiveVirtualCameras(int layerMask, Vector3 worldUp, float deltaTime) { // Setup for roundRobin standby updating var filter = m_CurrentUpdateFilter; bool canUpdateStandby = (filter != UpdateFilter.SmartFixed); // never in smart fixed CinemachineVirtualCameraBase currentRoundRobin = mRoundRobinVcamLastFrame; // 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 for (int i = mAllCameras.Count-1; i >= 0; --i) { var sublist = mAllCameras[i]; for (int j = sublist.Count - 1; j >= 0; --j) { var vcam = sublist[j]; if (canUpdateStandby && vcam == mRoundRobinVcamLastFrame) currentRoundRobin = null; // update the next roundrobin candidate if (vcam == null) { sublist.RemoveAt(j); continue; // deleted } if (vcam.m_StandbyUpdate == CinemachineVirtualCameraBase.StandbyUpdateMode.Always || IsLive(vcam)) { // Skip this vcam if it's not on the layer mask if (((1 << vcam.gameObject.layer) & layerMask) != 0) UpdateVirtualCamera(vcam, worldUp, deltaTime); } else if (currentRoundRobin == null && mRoundRobinVcamLastFrame != vcam && canUpdateStandby && vcam.m_StandbyUpdate != CinemachineVirtualCameraBase.StandbyUpdateMode.Never && vcam.isActiveAndEnabled) { // Do the round-robin update m_CurrentUpdateFilter &= ~UpdateFilter.Smart; // force it UpdateVirtualCamera(vcam, worldUp, deltaTime); m_CurrentUpdateFilter = filter; currentRoundRobin = vcam; } } } // Did we manage to update a roundrobin? if (canUpdateStandby) { if (currentRoundRobin == mRoundRobinVcamLastFrame) currentRoundRobin = null; // take the first candidate mRoundRobinVcamLastFrame = currentRoundRobin; } } /// /// Update a single Cinemachine Virtual Camera 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 void UpdateVirtualCamera( CinemachineVirtualCameraBase vcam, Vector3 worldUp, float deltaTime) { if (vcam == null) return; bool isSmartUpdate = (m_CurrentUpdateFilter & UpdateFilter.Smart) == UpdateFilter.Smart; UpdateTracker.UpdateClock updateClock = (UpdateTracker.UpdateClock)(m_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) { Transform 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 (mUpdateStatus == null) mUpdateStatus = new Dictionary(); if (!mUpdateStatus.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 }; mUpdateStatus.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 (FrameDeltaCompensationEnabled && 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 + ", " + CurrentUpdateFilter + ", deltaTime = " + deltaTime); vcam.InternalUpdateCameraState(worldUp, deltaTime); status.lastUpdateFrame = Time.frameCount; status.lastUpdateFixedFrame = s_FixedFrameCount; status.lastUpdateMode = updateClock; status.lastUpdateDeltaTime = deltaTime; } class UpdateStatus { public int lastUpdateFrame; public int lastUpdateFixedFrame; public UpdateTracker.UpdateClock lastUpdateMode; public float lastUpdateDeltaTime; } Dictionary mUpdateStatus; [RuntimeInitializeOnLoadMethod] static void InitializeModule() { CinemachineCore.Instance.mUpdateStatus = new Dictionary(); } /// 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 UpdateFilter m_CurrentUpdateFilter; private static Transform GetUpdateTarget(CinemachineVirtualCameraBase vcam) { if (vcam == null || vcam.gameObject == null) return null; Transform 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 UpdateTracker.UpdateClock GetVcamUpdateStatus(CinemachineVirtualCameraBase vcam) { UpdateStatus status; if (mUpdateStatus == null || !mUpdateStatus.TryGetValue(vcam, out status)) return UpdateTracker.UpdateClock.Late; return status.lastUpdateMode; } /// /// Is this virtual camera currently actively controlling any Camera? /// /// The virtual camea in question /// True if the vcam is currently driving a Brain public bool IsLive(ICinemachineCamera vcam) { if (vcam != null) { for (int i = 0; i < BrainCount; ++i) { CinemachineBrain b = GetActiveBrain(i); if (b != null && b.IsLive(vcam)) return true; } } return false; } /// /// Checks if the vcam is live as part of an outgoing blend in any active CinemachineBrain. /// 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 vcam) { if (vcam != null) { for (int i = 0; i < BrainCount; ++i) { CinemachineBrain b = GetActiveBrain(i); if (b != null && b.IsLiveInBlend(vcam)) return true; } } return false; } /// /// Signal that the virtual has been activated. /// If the camera is live, then all CinemachineBrains that are showing it will /// send an activation event. /// /// The virtual camera being activated /// The previously-active virtual camera (may be null) public void GenerateCameraActivationEvent(ICinemachineCamera vcam, ICinemachineCamera vcamFrom) { if (vcam != null) { for (int i = 0; i < BrainCount; ++i) { CinemachineBrain b = GetActiveBrain(i); if (b != null && b.IsLive(vcam)) b.m_CameraActivatedEvent.Invoke(vcam, vcamFrom); } } } /// /// Signal that the virtual camera's content is discontinuous WRT the previous frame. /// If the camera is live, then all CinemachineBrains that are showing it will send a cut event. /// /// The virtual camera being cut to public void GenerateCameraCutEvent(ICinemachineCamera vcam) { if (vcam != null) { for (int i = 0; i < BrainCount; ++i) { CinemachineBrain b = GetActiveBrain(i); if (b != null && b.IsLive(vcam)) { if (b.m_CameraCutEvent != null) b.m_CameraCutEvent.Invoke(b); if (CameraCutEvent != null) CameraCutEvent.Invoke(b); } } } } /// /// Try to find a CinemachineBrain to associate with a /// Cinemachine Virtual Camera. The first CinemachineBrain /// in which this Cinemachine Virtual Camera is live will be used. /// If none, then the first active CinemachineBrain with the correct /// layer filter will be used. /// Brains with OutputCamera == null will not be returned. /// Final result may be null. /// /// Virtual camera whose potential brain we need. /// First CinemachineBrain found that might be /// appropriate for this vcam, or null public CinemachineBrain FindPotentialTargetBrain(CinemachineVirtualCameraBase vcam) { if (vcam != null) { int numBrains = BrainCount; for (int i = 0; i < numBrains; ++i) { CinemachineBrain b = GetActiveBrain(i); if (b != null && b.OutputCamera != null && b.IsLive(vcam)) return b; } int layer = 1 << vcam.gameObject.layer; for (int i = 0; i < numBrains; ++i) { CinemachineBrain b = GetActiveBrain(i); if (b != null && b.OutputCamera != null && (b.OutputCamera.cullingMask & layer) != 0) return b; } } return null; } /// Call this to notify all virtual camewras that may be tracking a target /// that the target's position has suddenly warped to somewhere else, so that /// the virtual cameras can update their internal state to make the camera /// warp seamlessy along with the target. /// /// All virtual cameras are iterated so this call will work no matter how many /// are tracking the target, and whether they are active or inactive. /// /// The object that was warped /// The amount the target's position changed public void OnTargetObjectWarped(Transform target, Vector3 positionDelta) { int numVcams = VirtualCameraCount; for (int i = 0; i < numVcams; ++i) GetVirtualCamera(i).OnTargetObjectWarped(target, positionDelta); } } }