using UnityEngine;
using System;
namespace Unity.Cinemachine
{
///
/// This behaviour is intended to be attached to an empty GameObject,
/// and it represents a Cinemachine Camera within the Unity scene.
///
/// The CinemachineCamera will animate its Transform according to the rules contained
/// in its CinemachineComponent pipeline (Aim, Body, and Noise). When the CM
/// camera is Live, the Unity camera will assume the position and orientation
/// of the CinemachineCamera.
///
/// A CinemachineCamera is not a camera. Instead, it can be thought of as a camera controller,
/// not unlike a cameraman. It can drive the Unity Camera and control its position,
/// rotation, lens settings, and PostProcessing effects. Each CM Camera owns
/// its own Cinemachine Component Pipeline, through which you can provide the instructions
/// for procedurally tracking specific game objects. An empty procedural pipeline
/// will result in a passive CinemachineCamera, which can be controlled in the same way as
/// an ordinary GameObject.
///
/// A CinemachineCamera is very lightweight, and does no rendering of its own. It merely
/// tracks interesting GameObjects, and positions itself accordingly. A typical game
/// can have dozens of CinemachineCameras, each set up to follow a particular character
/// or capture a particular event.
///
/// A CinemachineCamera can be in any of three states:
///
/// * **Live**: The CinemachineCamera is actively controlling the Unity Camera. The
/// CinemachineCamera is tracking its targets and being updated every frame.
/// * **Standby**: The CinemachineCamera is tracking its targets and being updated
/// every frame, but no Unity Camera is actively being controlled by it. This is
/// the state of a CinemachineCamera that is enabled in the scene but perhaps at a
/// lower priority than the Live CinemachineCamera.
/// * **Disabled**: The CinemachineCamera is present but disabled in the scene. It is
/// not actively tracking its targets and so consumes no processing power. However,
/// the CinemachineCamera can be made live from the Timeline.
///
/// The Unity Camera can be driven by any CinemachineCamera in the scene. The game
/// logic can choose the CinemachineCamera to make live by manipulating the CM
/// camerass enabled flags and/or its priority, based on game logic.
///
/// In order to be driven by a CinemachineCamera, the Unity Camera must have a CinemachineBrain
/// behaviour, which will select the most eligible CinemachineCamera based on its priority
/// or on other criteria, and will manage blending.
///
///
[DisallowMultipleComponent]
[ExecuteAlways]
[AddComponentMenu("Cinemachine/Cinemachine Camera")]
[HelpURL(Documentation.BaseURL + "manual/CinemachineCamera.html")]
public sealed class CinemachineCamera : CinemachineVirtualCameraBase
{
/// The Tracking and LookAt targets for this camera.
[NoSaveDuringPlay]
[Tooltip("Specifies the Tracking and LookAt targets for this camera.")]
public CameraTarget Target;
/// Specifies the LensSettings of this camera.
/// These settings will be transferred to the Unity camera when the CM Camera is live.
[Tooltip("Specifies the lens properties of this Virtual Camera. This generally mirrors the "
+ "Unity Camera's lens settings, and will be used to drive the Unity camera when the vcam is active.")]
public LensSettings Lens = LensSettings.Default;
/// Hint for transitioning to and from this CinemachineCamera. Hints can be combined, although
/// not all combinations make sense. In the case of conflicting hints, Cinemachine will
/// make an arbitrary choice.
[Tooltip("Hint for transitioning to and from this CinemachineCamera. Hints can be combined, although "
+ "not all combinations make sense. In the case of conflicting hints, Cinemachine will "
+ "make an arbitrary choice.")]
public CinemachineCore.BlendHints BlendHint;
CameraState m_State = CameraState.Default;
CinemachineComponentBase[] m_Pipeline;
void Reset()
{
Priority = new();
OutputChannel = OutputChannels.Default;
Target = default;
Lens = LensSettings.Default;
}
/// Validates the settings avter inspector edit
void OnValidate()
{
Lens.Validate();
}
/// The current camera state, which will applied to the Unity Camera
public override CameraState State { get => m_State; }
/// 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 { return ResolveLookAt(Target.CustomLookAtTarget ? Target.LookAtTarget : Target.TrackingTarget); }
set { Target.CustomLookAtTarget = true; 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 { return ResolveFollow(Target.TrackingTarget); }
set { Target.TrackingTarget = value; }
}
/// This is called to notify the CinemachineCamera that a target got warped,
/// so that the CinemachineCamera 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)
{
if (target == Follow)
{
transform.position += positionDelta;
m_State.RawPosition += positionDelta;
}
UpdatePipelineCache();
for (int i = 0; i < m_Pipeline.Length; ++i)
{
if (m_Pipeline[i] != null)
m_Pipeline[i].OnTargetObjectWarped(target, positionDelta);
}
base.OnTargetObjectWarped(target, positionDelta);
}
///
/// Force the CinemachineCamera 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)
{
PreviousStateIsValid = false;
transform.ConservativeSetPositionAndRotation(pos, rot);
m_State.RawPosition = pos;
m_State.RawOrientation = rot;
UpdatePipelineCache();
for (int i = 0; i < m_Pipeline.Length; ++i)
if (m_Pipeline[i] != null)
m_Pipeline[i].ForceCameraPosition(pos, rot);
base.ForceCameraPosition(pos, rot);
}
///
/// Query components and extensions for the maximum damping time.
///
/// Highest damping setting in this CinemachineCamera
public override float GetMaxDampTime()
{
float maxDamp = base.GetMaxDampTime();
UpdatePipelineCache();
for (int i = 0; i < m_Pipeline.Length; ++i)
if (m_Pipeline[i] != null)
maxDamp = Mathf.Max(maxDamp, m_Pipeline[i].GetMaxDampTime());
return maxDamp;
}
/// Handle transition from another CinemachineCamera. InheritPosition is implemented here.
/// 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);
InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime);
bool forceUpdate = false;
// Can't inherit position if already live, because there will be a pop
if ((State.BlendHint & CameraState.BlendHints.InheritPosition) != 0
&& fromCam != null && !CinemachineCore.IsLiveInBlend(this))
{
var state = fromCam.State;
ForceCameraPosition(state.GetFinalPosition(), state.GetFinalOrientation());
}
UpdatePipelineCache();
for (int i = 0; i < m_Pipeline.Length; ++i)
if (m_Pipeline[i] != null && m_Pipeline[i].OnTransitionFromCamera(fromCam, worldUp, deltaTime))
forceUpdate = true;
if (!forceUpdate)
UpdateCameraState(worldUp, deltaTime);
else
{
// GML todo: why twice? Isn't once enough? Check this.
InternalUpdateCameraState(worldUp, deltaTime);
InternalUpdateCameraState(worldUp, deltaTime);
}
}
/// Internal use only. Called by CinemachineCore at designated update time
/// so the vcam can position itself and track its targets.
/// Default world Up, set by the CinemachineBrain
/// Delta time for time-based effects (ignore if less than 0)
public override void InternalUpdateCameraState(Vector3 worldUp, float deltaTime)
{
UpdateTargetCache();
FollowTargetAttachment = 1;
LookAtTargetAttachment = 1;
if (deltaTime < 0)
PreviousStateIsValid = false;
// Initialize the camera state, in case the game object got moved in the editor
m_State = PullStateFromVirtualCamera(worldUp, ref Lens);
// Do our stuff
var lookAt = LookAt;
if (lookAt != null)
m_State.ReferenceLookAt = (LookAtTargetAsVcam != null)
? LookAtTargetAsVcam.State.GetFinalPosition() : TargetPositionCache.GetTargetPosition(lookAt);
m_State.BlendHint = (CameraState.BlendHints)BlendHint;
InvokeComponentPipeline(ref m_State, deltaTime);
// Push the raw position back to the game object's transform, so it
// moves along with the camera.
var pos = transform.position;
var rot = transform.rotation;
if (Follow != null)
pos = m_State.RawPosition;
if (LookAt != null)
rot = m_State.RawOrientation;
transform.ConservativeSetPositionAndRotation(pos, rot);
// Signal that it's all done
PreviousStateIsValid = true;
}
CameraState InvokeComponentPipeline(ref CameraState state, float deltaTime)
{
// Extensions first
InvokePrePipelineMutateCameraStateCallback(this, ref state, deltaTime);
// Apply the component pipeline
UpdatePipelineCache();
for (int i = 0; i < m_Pipeline.Length; ++i)
{
var c = m_Pipeline[i];
if (c != null && c.IsValid)
c.PrePipelineMutateCameraState(ref state, deltaTime);
}
CinemachineComponentBase postAimBody = null;
for (int i = 0; i < m_Pipeline.Length; ++i)
{
var stage = (CinemachineCore.Stage)i;
var c = m_Pipeline[i];
if (c != null && c.IsValid)
{
if (stage == CinemachineCore.Stage.Body && c.BodyAppliesAfterAim)
{
postAimBody = c;
continue; // do the body stage of the pipeline after Aim
}
c.MutateCameraState(ref state, deltaTime);
}
InvokePostPipelineStageCallback(this, stage, ref state, deltaTime);
if (stage == CinemachineCore.Stage.Aim)
{
// If we have saved a Body for after Aim, do it now
if (postAimBody != null)
{
postAimBody.MutateCameraState(ref state, deltaTime);
InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Body, ref state, deltaTime);
}
}
}
return state;
}
/// A CinemachineComponentBase has just been added or removed. Pipeline cache will be rebuilt
internal void InvalidatePipelineCache() => m_Pipeline = null;
/// For unit tests
internal bool PipelineCacheInvalidated => m_Pipeline == null;
/// For unit tests
internal Type PeekPipelineCacheType(CinemachineCore.Stage stage)
=> m_Pipeline[(int)stage] == null ? null : m_Pipeline[(int)stage].GetType();
void UpdatePipelineCache()
{
const int pipelineLength = (int)CinemachineCore.Stage.Finalize + 1;
if (m_Pipeline == null || m_Pipeline.Length != pipelineLength)
{
m_Pipeline = new CinemachineComponentBase[pipelineLength];
var components = GetComponents();
for (int i = 0; i < components.Length; ++i)
{
if (m_Pipeline[(int)components[i].Stage] == null)
m_Pipeline[(int)components[i].Stage] = components[i];
//#if UNITY_EDITOR
// else
// Debug.LogWarning("Multiple " + components[i].Stage + " components on " + name);
//#endif
}
}
}
/// Get the component set for a specific stage in the pipeline.
/// The stage for which we want the component
/// The Cinemachine component for that stage, or null if not present.
public override CinemachineComponentBase GetCinemachineComponent(CinemachineCore.Stage stage)
{
UpdatePipelineCache();
var i = (int)stage;
return i >= 0 && i < m_Pipeline.Length ? m_Pipeline[i] : null;
}
}
}