using UnityEngine;
using System.Collections.Generic;
namespace Cinemachine
{
/// 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;
/// Human-readable Cinemachine Version
public static readonly string kVersionString = "2.1";
///
/// 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 stage: apply noise (this is done separately, in the
/// Correction channel of the CameraState)
Noise
};
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.
public static AxisInputDelegate GetInputAxis = UnityEngine.Input.GetAxis;
/// 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; } }
/// Access the array of active CinemachineBrains in the scene
/// without gebnerating 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();
///
/// 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 array of active ICinemachineCamera in the scene
/// without gebnerating garbage
/// Index of the camera to access, range 0-VirtualCameraCount
/// The virtual camera at the specified index
public ICinemachineCamera GetVirtualCamera(int index)
{
return mActiveCameras[index];
}
/// Called when a Cinemachine Virtual Camera is enabled.
internal void AddActiveCamera(ICinemachineCamera vcam)
{
// Bring it to the top of the list
RemoveActiveCamera(vcam);
// Keep list sorted by priority
int insertIndex;
for (insertIndex = 0; insertIndex < mActiveCameras.Count; ++insertIndex)
if (vcam.Priority >= mActiveCameras[insertIndex].Priority)
break;
mActiveCameras.Insert(insertIndex, vcam);
}
/// Called when a Cinemachine Virtual Camera is disabled.
internal void RemoveActiveCamera(ICinemachineCamera vcam)
{
mActiveCameras.Remove(vcam);
}
// Registry of all vcams that are parented (i.e. slaves of) to other vcams
private List> mChildCameras = new List>();
/// Called when a child vcam is enabled.
internal void AddChildCamera(ICinemachineCamera vcam)
{
RemoveChildCamera(vcam);
int parentLevel = 0;
for (ICinemachineCamera p = vcam; p != null; p = p.ParentCamera)
++parentLevel;
while (mChildCameras.Count < parentLevel)
mChildCameras.Add(new List());
mChildCameras[parentLevel-1].Add(vcam);
}
/// Called when a child vcam is disabled.
internal void RemoveChildCamera(ICinemachineCamera vcam)
{
for (int i = 0; i < mChildCameras.Count; ++i)
mChildCameras[i].Remove(vcam);
}
/// Update all the active vcams in the scene, in the correct dependency order.
internal void UpdateAllActiveVirtualCameras(Vector3 worldUp, float deltaTime)
{
//UnityEngine.Profiling.Profiler.BeginSample("CinemachineCore.UpdateAllActiveVirtualCameras");
int numCameras;
// Update the leaf-most cameras first
//UnityEngine.Profiling.Profiler.BeginSample("CinemachineCore.UpdateAllActiveVirtualCameras.leaf-most");
for (int i = mChildCameras.Count-1; i >= 0; --i)
{
numCameras = mChildCameras[i].Count;
for (int j = 0; j < numCameras; ++j)
UpdateVirtualCamera(mChildCameras[i][j], worldUp, deltaTime);
}
//UnityEngine.Profiling.Profiler.EndSample();
// Then all the top-level cameras
numCameras = VirtualCameraCount;
for (int i = 0; i < numCameras; ++i)
UpdateVirtualCamera(GetVirtualCamera(i), worldUp, deltaTime);
//UnityEngine.Profiling.Profiler.EndSample();
}
///
/// 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 bool UpdateVirtualCamera(ICinemachineCamera vcam, Vector3 worldUp, float deltaTime)
{
//UnityEngine.Profiling.Profiler.BeginSample("CinemachineCore.UpdateVirtualCamera");
int now = Time.frameCount;
UpdateFilter filter = CurrentUpdateFilter;
bool isSmartUpdate = filter != UpdateFilter.ForcedFixed
&& filter != UpdateFilter.ForcedLate;
bool isSmartLateUpdate = filter == UpdateFilter.Late;
if (!isSmartUpdate)
{
if (filter == UpdateFilter.ForcedFixed)
filter = UpdateFilter.Fixed;
if (filter == UpdateFilter.ForcedLate)
filter = UpdateFilter.Late;
}
if (mUpdateStatus == null)
mUpdateStatus = new Dictionary();
if (vcam.VirtualCameraGameObject == null)
{
if (mUpdateStatus.ContainsKey(vcam))
mUpdateStatus.Remove(vcam);
//UnityEngine.Profiling.Profiler.EndSample();
return false; // camera was deleted
}
UpdateStatus status;
if (!mUpdateStatus.TryGetValue(vcam, out status))
{
status = new UpdateStatus(now);
mUpdateStatus.Add(vcam, status);
}
int subframes = isSmartLateUpdate ? 1 : CinemachineBrain.GetSubframeCount();
if (status.lastUpdateFrame != now)
status.lastUpdateSubframe = 0;
// 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
bool updateNow = !isSmartUpdate;
if (isSmartUpdate)
{
Matrix4x4 targetPos;
if (!GetTargetPosition(vcam, out targetPos))
updateNow = isSmartLateUpdate; // no target
else
updateNow = status.ChoosePreferredUpdate(now, targetPos, filter)
== filter;
}
if (updateNow)
{
status.preferredUpdate = filter;
while (status.lastUpdateSubframe < subframes)
{
//Debug.Log(vcam.Name + ": frame " + Time.frameCount + "." + status.lastUpdateSubframe + ", " + CurrentUpdateFilter + ", deltaTime = " + deltaTime);
vcam.UpdateCameraState(worldUp, deltaTime);
++status.lastUpdateSubframe;
}
status.lastUpdateFrame = now;
}
mUpdateStatus[vcam] = status;
//UnityEngine.Profiling.Profiler.EndSample();
return true;
}
struct UpdateStatus
{
const int kWindowSize = 30;
public int lastUpdateFrame;
public int lastUpdateSubframe;
public int windowStart;
public int numWindowLateUpdateMoves;
public int numWindowFixedUpdateMoves;
public int numWindows;
public UpdateFilter preferredUpdate;
public Matrix4x4 targetPos;
public UpdateStatus(int currentFrame)
{
lastUpdateFrame = -1;
lastUpdateSubframe = 0;
windowStart = currentFrame;
numWindowLateUpdateMoves = 0;
numWindowFixedUpdateMoves = 0;
numWindows = 0;
preferredUpdate = UpdateFilter.Late;
targetPos = Matrix4x4.zero;
}
// Important: updateFilter may ONLY be Late or Fixed
public UpdateFilter ChoosePreferredUpdate(
int currentFrame, Matrix4x4 pos, UpdateFilter updateFilter)
{
if (targetPos != pos)
{
if (updateFilter == UpdateFilter.Late)
++numWindowLateUpdateMoves;
else if (lastUpdateSubframe == 0)
++numWindowFixedUpdateMoves;
targetPos = pos;
}
//Debug.Log("Fixed=" + numWindowFixedUpdateMoves + ", Late=" + numWindowLateUpdateMoves);
UpdateFilter choice = preferredUpdate;
bool inconsistent = numWindowLateUpdateMoves > 0 && numWindowFixedUpdateMoves > 0;
if (inconsistent || numWindowLateUpdateMoves >= numWindowFixedUpdateMoves)
choice = UpdateFilter.Late;
else
choice = UpdateFilter.Fixed;
if (numWindows == 0)
preferredUpdate = choice;
if (windowStart + kWindowSize <= currentFrame)
{
preferredUpdate = choice;
++numWindows;
windowStart = currentFrame;
numWindowLateUpdateMoves = numWindowFixedUpdateMoves = 0;
}
return preferredUpdate;
}
}
Dictionary mUpdateStatus;
/// Internal use only
public enum UpdateFilter { Fixed, ForcedFixed, Late, ForcedLate };
internal UpdateFilter CurrentUpdateFilter { get; set; }
private static bool GetTargetPosition(ICinemachineCamera vcam, out Matrix4x4 targetPos)
{
ICinemachineCamera vcamTarget = vcam.LiveChildOrSelf;
if (vcamTarget == null || vcamTarget.VirtualCameraGameObject == null)
{
targetPos = Matrix4x4.identity;
return false;
}
targetPos = vcamTarget.VirtualCameraGameObject.transform.localToWorldMatrix;
if (vcamTarget.LookAt != null)
{
targetPos = vcamTarget.LookAt.localToWorldMatrix;
return true;
}
if (vcamTarget.Follow != null)
{
targetPos = vcamTarget.Follow.localToWorldMatrix;
return true;
}
// If no target, use the vcam itself
targetPos = vcam.VirtualCameraGameObject.transform.localToWorldMatrix;
return true;
}
/// Internal use only
public UpdateFilter GetVcamUpdateStatus(ICinemachineCamera vcam)
{
UpdateStatus status;
if (mUpdateStatus == null || !mUpdateStatus.TryGetValue(vcam, out status))
return UpdateFilter.Late;
return status.preferredUpdate;
}
///
/// Is this virtual camera currently actively controlling any Camera?
///
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;
}
///
/// Signal that the virtual has been activated.
/// If the camera is live, then all CinemachineBrains that are showing it will
/// send an activation event.
///
public void GenerateCameraActivationEvent(ICinemachineCamera vcam)
{
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);
}
}
}
///
/// 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.
///
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))
b.m_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 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(ICinemachineCamera vcam)
{
int numBrains = BrainCount;
if (vcam != null && numBrains > 1)
{
for (int i = 0; i < numBrains; ++i)
{
CinemachineBrain b = GetActiveBrain(i);
if (b != null && b.OutputCamera != null && b.IsLive(vcam))
return b;
}
}
for (int i = 0; i < numBrains; ++i)
{
CinemachineBrain b = GetActiveBrain(i);
if (b != null && b.OutputCamera != null)
return b;
}
return null;
}
}
}