using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Cinemachine
{
///
/// This is a virtual camera "manager" that owns and manages a collection
/// of child Cm Cameras.
///
public abstract class CinemachineCameraManagerBase : CinemachineVirtualCameraBase, ICinemachineMixer
{
/// If enabled, a default target will be available. It will be used
/// if a child rig needs a target and doesn't specify one itself.
[Serializable]
public struct DefaultTargetSettings
{
/// If enabled, a default target will be available. It will be used
/// if a child rig needs a target and doesn't specify one itself.
[Tooltip("If enabled, a default target will be available. It will be used "
+ "if a child rig needs a target and doesn't specify one itself.")]
public bool Enabled;
/// Default target for the camera children, which may be used if the child rig
/// does not specify a target of its own.
[NoSaveDuringPlay]
[Tooltip("Default target for the camera children, which may be used if the child rig "
+ "does not specify a target of its own.")]
public CameraTarget Target;
}
/// If enabled, a default target will be available. It will be used
/// if a child rig needs a target and doesn't specify one itself.
[FoldoutWithEnabledButton]
public DefaultTargetSettings DefaultTarget;
///
/// The blend which is used if you don't explicitly define a blend between two Virtual Camera children.
///
[Tooltip("The blend which is used if you don't explicitly define a blend between two Virtual Camera children")]
[FormerlySerializedAs("m_DefaultBlend")]
public CinemachineBlendDefinition DefaultBlend = new (CinemachineBlendDefinition.Styles.EaseInOut, 0.5f);
///
/// This is the asset which contains custom settings for specific child blends.
///
[Tooltip("This is the asset which contains custom settings for specific child blends")]
[FormerlySerializedAs("m_CustomBlends")]
[EmbeddedBlenderSettingsProperty]
public CinemachineBlenderSettings CustomBlends = null;
List m_ChildCameras;
readonly BlendManager m_BlendManager = new ();
CameraState m_State = CameraState.Default;
ICinemachineCamera m_TransitioningFrom;
/// Reset the component to default values.
protected virtual void Reset()
{
Priority = default;
OutputChannel = OutputChannels.Default;
DefaultTarget = default;
InvalidateCameraCache();
}
///
/// Standard MonoBehaviour OnEnable. Derived classes must call base class implementation.
///
protected override void OnEnable()
{
base.OnEnable();
m_BlendManager.OnEnable();
m_BlendManager.LookupBlendDelegate = LookupBlend;
InvalidateCameraCache();
}
///
/// Standard MonoBehaviour OnDisable. Derived classes must call base class implementation.
///
protected override void OnDisable()
{
m_BlendManager.OnDisable();
base.OnDisable();
}
///
public override string Description => m_BlendManager.Description;
///
public override CameraState State => m_State;
///
public virtual bool IsLiveChild(ICinemachineCamera cam, bool dominantChildOnly = false)
=> m_BlendManager.IsLive(cam);
/// The list of child cameras. These are just the immediate children in the hierarchy.
public List ChildCameras
{
get
{
UpdateCameraCache();
return m_ChildCameras;
}
}
///
public override bool PreviousStateIsValid
{
get => base.PreviousStateIsValid;
set
{
base.PreviousStateIsValid = value;
// Only propagate to the children when we're invalidating the state
if (value == false)
for (int i = 0; m_ChildCameras != null && i < m_ChildCameras.Count; ++i)
m_ChildCameras[i].PreviousStateIsValid = value;
}
}
/// Is there a blend in progress?
public bool IsBlending => m_BlendManager.IsBlending;
///
/// 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
/// unless it is to set the active blend to null, which will force completion of the blend.
///
public CinemachineBlend ActiveBlend
{
get => PreviousStateIsValid ? m_BlendManager.ActiveBlend : null;
set => m_BlendManager.ActiveBlend = value;
}
///
/// Get the current active camera. Will return null if no camera is active.
///
public ICinemachineCamera LiveChild => PreviousStateIsValid ? m_BlendManager.ActiveVirtualCamera : null;
/// Get the current LookAt target. Returns parent's LookAt if parent
/// is non-null and no specific LookAt defined for this camera
public override Transform LookAt
{
get
{
if (!DefaultTarget.Enabled)
return null;
return ResolveLookAt(DefaultTarget.Target.CustomLookAtTarget
? DefaultTarget.Target.LookAtTarget : DefaultTarget.Target.TrackingTarget);
}
set
{
DefaultTarget.Enabled = true;
DefaultTarget.Target.CustomLookAtTarget = true;
DefaultTarget.Target.LookAtTarget = value;
}
}
/// Get the current Follow target. Returns parent's Follow if parent
/// is non-null and no specific Follow defined for this camera
public override Transform Follow
{
get
{
if (!DefaultTarget.Enabled)
return null;
return ResolveFollow(DefaultTarget.Target.TrackingTarget);
}
set
{
DefaultTarget.Enabled = true;
DefaultTarget.Target.TrackingTarget = value;
}
}
/// Internal use only. Do not call this method.
/// Called by CinemachineCore at designated update time
/// so the vcam can position itself and track its targets. This implementation
/// updates all the children, chooses the best one, and implements any required blending.
/// Default world Up, set by the CinemachineBrain
/// Delta time for time-based effects (ignore if less than or equal to 0)
public override void InternalUpdateCameraState(Vector3 worldUp, float deltaTime)
{
UpdateCameraCache();
if (!PreviousStateIsValid)
ResetLiveChild();
// Choose the best camera - auto-activate it if it's inactive
var best = ChooseCurrentCamera(worldUp, deltaTime);
if (best != null && !best.gameObject.activeInHierarchy)
{
best.gameObject.SetActive(true);
best.UpdateCameraState(worldUp, deltaTime);
}
SetLiveChild(best, worldUp, deltaTime);
// Special case to handle being called from OnTransitionFromCamera() - GML todo: fix this
if (m_TransitioningFrom != null && !IsBlending && LiveChild != null)
{
LiveChild.OnCameraActivated(new ICinemachineCamera.ActivationEventParams
{
Origin = this,
OutgoingCamera = m_TransitioningFrom,
IncomingCamera = LiveChild,
IsCut = false,
WorldUp = worldUp,
DeltaTime = deltaTime
});
}
FinalizeCameraState(deltaTime);
m_TransitioningFrom = null;
PreviousStateIsValid = true;
}
/// Find a blend curve for blending from one child camera to another.
/// The camera we're blending from.
/// The camera we're blending to.
/// The blend to use for this camera transition.
protected virtual CinemachineBlendDefinition LookupBlend(ICinemachineCamera outgoing, ICinemachineCamera incoming)
{
return CinemachineBlenderSettings.LookupBlend(outgoing, incoming, DefaultBlend, CustomBlends, this);
}
///
/// Choose the appropriate current camera from among the ChildCameras, based on current state.
/// If the returned camera is different from the current camera, an appropriate transition will be made.
///
/// Default world Up, set by the CinemachineBrain
/// Delta time for time-based effects (ignore if less than or equal to 0)
/// The current child camera that should be active. Must be present in ChildCameras.
protected abstract CinemachineVirtualCameraBase ChooseCurrentCamera(Vector3 worldUp, float deltaTime);
/// This is called to notify the vcam that a target got warped,
/// so that the vcam can update its internal state to make the camera
/// also warp seamlessly.
/// The object that was warped
/// The amount the target's position changed
public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
{
UpdateCameraCache();
for (int i = 0; i < m_ChildCameras.Count; ++i)
m_ChildCameras[i].OnTargetObjectWarped(target, positionDelta);
base.OnTargetObjectWarped(target, positionDelta);
}
///
/// Force the virtual camera to assume a given position and orientation
///
/// World-space position to take
/// World-space orientation to take
public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
{
UpdateCameraCache();
for (int i = 0; i < m_ChildCameras.Count; ++i)
m_ChildCameras[i].ForceCameraPosition(pos, rot);
base.ForceCameraPosition(pos, rot);
}
/// Notification that this virtual camera is going live.
/// The camera being deactivated. May be null.
/// Default world Up, set by the CinemachineBrain
/// Delta time for time-based effects (ignore if less than or equal to 0)
public override void OnTransitionFromCamera(
ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime)
{
base.OnTransitionFromCamera(fromCam, worldUp, deltaTime);
m_TransitioningFrom = fromCam;
InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime);
InternalUpdateCameraState(worldUp, deltaTime);
}
/// Force a rebuild of the child camera cache.
/// Call this if CinemachineCamera children are added or removed dynamically
public void InvalidateCameraCache()
{
m_ChildCameras = null;
PreviousStateIsValid = false;
}
/// Rebuild the camera cache if it's been invalidated
/// True if a cache rebuild was performed, false if cache is up to date.
protected virtual bool UpdateCameraCache()
{
if (m_ChildCameras != null)
return false;
PreviousStateIsValid = false;
m_ChildCameras = new();
GetComponentsInChildren(true, m_ChildCameras);
for (int i = m_ChildCameras.Count-1; i >= 0; --i)
if (m_ChildCameras[i].transform.parent != transform)
m_ChildCameras.RemoveAt(i);
return true;
}
/// Makes sure the internal child cache is up to date
protected virtual void OnTransformChildrenChanged() => InvalidateCameraCache();
///
/// Set the current active camera. All necessary blends will be created, and events generated.
///
/// Current active camera
/// Current world up
/// Current deltaTime applicable for this frame
protected void SetLiveChild(
ICinemachineCamera activeCamera, Vector3 worldUp, float deltaTime)
{
m_BlendManager.UpdateRootFrame(this, activeCamera, worldUp, deltaTime);
m_BlendManager.ComputeCurrentBlend();
m_BlendManager.ProcessActiveCamera(this, worldUp, deltaTime);
}
/// Cancel current active camera and all blends
protected void ResetLiveChild() => m_BlendManager.ResetRootFrame();
/// At the end of InternalUpdateCameraState, call this to finalize the state
/// Current deltaTime for this frame
protected void FinalizeCameraState(float deltaTime)
{
m_State = m_BlendManager.CameraState;
InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Finalize, ref m_State, deltaTime);
}
}
}