#if !CINEMACHINE_NO_CM2_SUPPORT
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Cinemachine
{
///
/// This is a deprecated component. Use CinemachineCamera instead.
///
[Obsolete("CinemachineVirtualCamera is deprecated. Use CinemachineCamera instead.")]
[DisallowMultipleComponent]
[ExecuteAlways]
[ExcludeFromPreset]
[AddComponentMenu("")] // Don't display in add component menu
public class CinemachineVirtualCamera : CinemachineVirtualCameraBase, AxisState.IRequiresInput
{
/// The object that the camera wants to look at (the Aim target).
/// The Aim component of the CinemachineComponent pipeline
/// will refer to this target and orient the vcam in accordance with rules and
/// settings that are provided to it.
/// If this is null, then the vcam's Transform orientation will be used.
[Tooltip("The object that the camera wants to look at (the Aim target). "
+ "If this is null, then the vcam's Transform orientation will define the camera's orientation.")]
[NoSaveDuringPlay]
[VcamTargetProperty]
public Transform m_LookAt = null;
/// The object that the camera wants to move with (the Body target).
/// The Body component of the CinemachineComponent pipeline
/// will refer to this target and position the vcam in accordance with rules and
/// settings that are provided to it.
/// If this is null, then the vcam's Transform position will be used.
[Tooltip("The object that the camera wants to move with (the Body target). "
+ "If this is null, then the vcam's Transform position will define the camera's position.")]
[NoSaveDuringPlay]
[VcamTargetProperty]
public Transform m_Follow = null;
/// Specifies the LensSettings of this Virtual Camera.
/// These settings will be transferred to the Unity camera when the vcam 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.")]
[FormerlySerializedAs("m_LensAttributes")]
public LegacyLensSettings m_Lens = LegacyLensSettings.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;
/// This event fires when a transition occurs.
[Tooltip("This event fires when a transition occurs")]
public CinemachineLegacyCameraEvents.OnCameraLiveEvent m_OnCameraLiveEvent = new();
/// Inspector control - Use for hiding sections of the Inspector UI.
[HideInInspector, SerializeField, NoSaveDuringPlay]
internal string[] m_ExcludedPropertiesInInspector = new string[] { "m_Script" };
/// Inspector control - Use for enabling sections of the Inspector UI.
[HideInInspector, SerializeField, NoSaveDuringPlay]
internal CinemachineCore.Stage[] m_LockStageInInspector;
// Legacy support ===============================================================
[Serializable]
struct LegacyTransitionParams
{
[FormerlySerializedAs("m_PositionBlending")]
public int m_BlendHint;
public bool m_InheritPosition;
public CinemachineLegacyCameraEvents.OnCameraLiveEvent m_OnCameraLive;
}
[FormerlySerializedAs("m_Transitions")]
[SerializeField, HideInInspector] LegacyTransitionParams m_LegacyTransitions;
internal protected override void PerformLegacyUpgrade(int streamedVersion)
{
base.PerformLegacyUpgrade(streamedVersion);
if (streamedVersion < 20221011)
{
if (m_LegacyTransitions.m_BlendHint != 0)
{
if (m_LegacyTransitions.m_BlendHint == 3)
BlendHint = CinemachineCore.BlendHints.ScreenSpaceAimWhenTargetsDiffer;
else
BlendHint = (CinemachineCore.BlendHints)m_LegacyTransitions.m_BlendHint;
m_LegacyTransitions.m_BlendHint = 0;
}
if (m_LegacyTransitions.m_InheritPosition)
{
BlendHint |= CinemachineCore.BlendHints.InheritPosition;
m_LegacyTransitions.m_InheritPosition = false;
}
if (m_LegacyTransitions.m_OnCameraLive != null)
{
m_OnCameraLiveEvent = m_LegacyTransitions.m_OnCameraLive;
m_LegacyTransitions.m_OnCameraLive = null;
}
}
}
// ===============================================================
/// Helper for upgrading from CM2
internal protected override bool IsDprecated => true;
/// This is the name of the hidden GameObject that will be created as a child object
/// of the virtual camera. This hidden game object acts as a container for the polymorphic
/// CinemachineComponent pipeline. The Inspector UI for the Virtual Camera
/// provides access to this pipleline, as do the CinemachineComponent-family of
/// public methods in this class.
/// The lifecycle of the pipeline GameObject is managed automatically.
const string PipelineName = "cm";
/// The CameraState object holds all of the information
/// necessary to position the Unity camera. It is the output of this class.
override public CameraState State { get { return m_State; } }
/// Get the LookAt target for the Aim component in the Cinemachine pipeline.
/// If this vcam is a part of a meta-camera collection, then the owner's target
/// will be used if the local target is null.
override public Transform LookAt
{
get { return ResolveLookAt(m_LookAt); }
set { m_LookAt = value; }
}
/// Get the Follow target for the Body component in the Cinemachine pipeline.
/// If this vcam is a part of a meta-camera collection, then the owner's target
/// will be used if the local target is null.
override public Transform Follow
{
get { return ResolveFollow(m_Follow); }
set { m_Follow = value; }
}
///
/// Query components and extensions for the maximum damping time.
///
/// Highest damping setting in this vcam
public override float GetMaxDampTime()
{
float maxDamp = base.GetMaxDampTime();
UpdateComponentPipeline();
if (m_ComponentPipeline != null)
for (int i = 0; i < m_ComponentPipeline.Length; ++i)
maxDamp = Mathf.Max(maxDamp, m_ComponentPipeline[i].GetMaxDampTime());
return maxDamp;
}
/// Internal use only. Do not call this method.
/// Called by CinemachineCore at the appropriate Update time
/// so the vcam can position itself and track its targets. This class will
/// invoke its pipeline and generate a CameraState for this frame.
/// Effective world up
/// Effective deltaTime
override public void InternalUpdateCameraState(Vector3 worldUp, float deltaTime)
{
UpdateTargetCache();
if (deltaTime < 0)
PreviousStateIsValid = false;
// Update the state by invoking the component pipeline
m_State = CalculateNewState(worldUp, deltaTime);
m_State.BlendHint = (CameraState.BlendHints)BlendHint;
// Push the raw position back to the game object's transform, so it
// moves along with the camera.
if (Follow != null)
transform.position = State.RawPosition;
if (LookAt != null)
transform.rotation = State.RawOrientation;
PreviousStateIsValid = true;
}
/// Make sure that the pipeline cache is up-to-date.
override protected void OnEnable()
{
base.OnEnable();
m_LensSettings = m_Lens.ToLensSettings();
m_State = PullStateFromVirtualCamera(Vector3.up, ref m_LensSettings);
InvalidateComponentPipeline();
}
/// Calls the DestroyPipelineDelegate for destroying the hidden
/// child object, to support undo.
protected override void OnDestroy()
{
// Make the pipeline visible instead of destroying - this is to keep Undo happy
foreach (Transform child in transform)
if (child.GetComponent() != null)
child.gameObject.hideFlags
&= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector);
base.OnDestroy();
}
/// Enforce bounds for fields, when changed in inspector.
protected void OnValidate()
{
m_Lens.Validate();
}
void OnTransformChildrenChanged()
{
InvalidateComponentPipeline();
}
void Reset()
{
DestroyPipeline();
UpdateComponentPipeline();
Priority = new ();
OutputChannel = OutputChannels.Default;
}
///
/// Override component pipeline creation.
/// This needs to be done by the editor to support Undo.
/// The override must do exactly the same thing as the CreatePipeline method in this class.
///
public static CreatePipelineDelegate CreatePipelineOverride;
///
/// Override component pipeline creation.
/// This needs to be done by the editor to support Undo.
/// The override must do exactly the same thing as the CreatePipeline method in
/// the CinemachineVirtualCamera class.
///
public delegate Transform CreatePipelineDelegate(
CinemachineVirtualCamera vcam, string name, CinemachineComponentBase[] copyFrom);
///
/// Override component pipeline destruction.
/// This needs to be done by the editor to support Undo.
///
public static DestroyPipelineDelegate DestroyPipelineOverride;
///
/// Override component pipeline destruction.
/// This needs to be done by the editor to support Undo.
///
public delegate void DestroyPipelineDelegate(GameObject pipeline);
/// Destroy any existing pipeline container.
internal void DestroyPipeline()
{
List oldPipeline = new List();
foreach (Transform child in transform)
if (child.GetComponent() != null)
oldPipeline.Add(child);
foreach (Transform child in oldPipeline)
{
if (DestroyPipelineOverride != null)
DestroyPipelineOverride(child.gameObject);
else
{
var oldStuff = child.GetComponents();
foreach (var c in oldStuff)
Destroy(c);
if (!RuntimeUtility.IsPrefab(gameObject))
Destroy(child.gameObject);
}
}
m_ComponentOwner = null;
InvalidateComponentPipeline();
PreviousStateIsValid = false;
}
/// Create a default pipeline container.
/// Note: copyFrom only supported in Editor, not build
internal Transform CreatePipeline(CinemachineVirtualCamera copyFrom)
{
CinemachineComponentBase[] components = null;
if (copyFrom != null)
{
copyFrom.InvalidateComponentPipeline(); // make sure it's up to date
components = copyFrom.GetComponentPipeline();
}
Transform newPipeline = null;
if (CreatePipelineOverride != null)
newPipeline = CreatePipelineOverride(this, PipelineName, components);
else if (!RuntimeUtility.IsPrefab(gameObject))
{
GameObject go = new GameObject(PipelineName);
go.transform.parent = transform;
go.AddComponent();
newPipeline = go.transform;
}
PreviousStateIsValid = false;
return newPipeline;
}
///
/// Editor API: Call this when changing the pipeline from the editor.
/// Will force a rebuild of the pipeline cache.
///
public void InvalidateComponentPipeline() { m_ComponentPipeline = null; }
/// Get the hidden CinemachinePipeline child object.
/// The hidden CinemachinePipeline child object
public Transform GetComponentOwner() { UpdateComponentPipeline(); return m_ComponentOwner; }
/// Get the component pipeline owned by the hidden child pipline container.
/// For most purposes, it is preferable to use the GetCinemachineComponent method.
/// The component pipeline
public CinemachineComponentBase[] GetComponentPipeline() { UpdateComponentPipeline(); return m_ComponentPipeline; }
/// Get the component set for a specific stage.
/// The stage for which we want the component
/// The Cinemachine component for that stage, or null if not defined
public override CinemachineComponentBase GetCinemachineComponent(CinemachineCore.Stage stage)
{
CinemachineComponentBase[] components = GetComponentPipeline();
if (components != null)
foreach (var c in components)
if (c.Stage == stage)
return c;
return null;
}
/// Get an existing component of a specific type from the cinemachine pipeline.
/// The type of component to get
/// The component if it's present, or null
public T GetCinemachineComponent() where T : CinemachineComponentBase
{
CinemachineComponentBase[] components = GetComponentPipeline();
if (components != null)
foreach (var c in components)
if (c is T)
return c as T;
return null;
}
/// Add a component to the cinemachine pipeline.
/// Existing components at the new component's stage are removed
/// The type of component to add
/// The new component
public T AddCinemachineComponent() where T : CinemachineComponentBase
{
// Get the existing components
Transform owner = GetComponentOwner();
if (owner == null)
return null; // maybe it's a prefab
CinemachineComponentBase[] components = owner.GetComponents();
T component = owner.gameObject.AddComponent();
if (component != null && components != null)
{
// Remove the existing components at that stage
CinemachineCore.Stage stage = component.Stage;
for (int i = components.Length - 1; i >= 0; --i)
{
if (components[i].Stage == stage)
{
components[i].enabled = false;
RuntimeUtility.DestroyObject(components[i]);
}
}
}
InvalidateComponentPipeline();
return component;
}
/// Remove a component from the cinemachine pipeline if it's present.
/// The type of component to remove
public void DestroyCinemachineComponent() where T : CinemachineComponentBase
{
CinemachineComponentBase[] components = GetComponentPipeline();
if (components != null)
{
foreach (var c in components)
{
if (c is T)
{
c.enabled = false;
RuntimeUtility.DestroyObject(c);
InvalidateComponentPipeline();
}
}
}
}
CameraState m_State = CameraState.Default; // Current state this frame
CinemachineComponentBase[] m_ComponentPipeline = null;
// Serialized only to implement copy/paste of CM subcomponents.
// Note however that this strategy has its limitations: the CM pipeline Components
// won't be pasted onto a prefab asset outside the scene unless the prefab
// is opened in Prefab edit mode.
[SerializeField][HideInInspector] private Transform m_ComponentOwner = null;
void UpdateComponentPipeline()
{
#if UNITY_EDITOR
// Did we just get copy/pasted?
if (m_ComponentOwner != null && m_ComponentOwner.parent != transform)
{
CinemachineVirtualCamera copyFrom = (m_ComponentOwner.parent != null)
? m_ComponentOwner.parent.gameObject.GetComponent() : null;
DestroyPipeline();
CreatePipeline(copyFrom);
m_ComponentOwner = null;
}
// Make sure the pipeline stays hidden, even through prefab
if (m_ComponentOwner != null)
SetFlagsForHiddenChild(m_ComponentOwner.gameObject);
#endif
// Early out if we're up-to-date
if (m_ComponentOwner != null && m_ComponentPipeline != null)
return;
m_ComponentOwner = null;
List list = new List();
foreach (Transform child in transform)
{
if (child.GetComponent() != null)
{
CinemachineComponentBase[] components = child.GetComponents();
foreach (CinemachineComponentBase c in components)
if (c != null && c.enabled)
list.Add(c);
m_ComponentOwner = child;
break;
}
}
// Make sure we have a pipeline owner
if (m_ComponentOwner == null)
m_ComponentOwner = CreatePipeline(null);
if (m_ComponentOwner != null && m_ComponentOwner.gameObject != null)
{
// Sort the pipeline
list.Sort((c1, c2) => (int)c1.Stage - (int)c2.Stage);
m_ComponentPipeline = list.ToArray();
}
}
static internal void SetFlagsForHiddenChild(GameObject child)
{
if (child != null)
{
#if true
child.hideFlags &= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector);
#else
if (CinemachineCore.sShowHiddenObjects)
child.hideFlags &= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector);
else
child.hideFlags |= (HideFlags.HideInHierarchy | HideFlags.HideInInspector);
#endif
}
}
private LensSettings m_LensSettings;
private Transform mCachedLookAtTarget;
private CinemachineVirtualCameraBase mCachedLookAtTargetVcam;
private CameraState CalculateNewState(Vector3 worldUp, float deltaTime)
{
FollowTargetAttachment = 1;
LookAtTargetAttachment = 1;
// Initialize the camera state, in case the game object got moved in the editor
m_LensSettings = m_Lens.ToLensSettings();
CameraState state = PullStateFromVirtualCamera(worldUp, ref m_LensSettings);
Transform lookAtTarget = LookAt;
if (lookAtTarget != mCachedLookAtTarget)
{
mCachedLookAtTarget = lookAtTarget;
mCachedLookAtTargetVcam = null;
if (lookAtTarget != null)
mCachedLookAtTargetVcam = lookAtTarget.GetComponent();
}
if (lookAtTarget != null)
{
if (mCachedLookAtTargetVcam != null)
state.ReferenceLookAt = mCachedLookAtTargetVcam.State.GetFinalPosition();
else
state.ReferenceLookAt = TargetPositionCache.GetTargetPosition(lookAtTarget);
}
// Update the state by invoking the component pipeline
UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC
// Extensions first
InvokePrePipelineMutateCameraStateCallback(this, ref state, deltaTime);
// Then components
if (m_ComponentPipeline == null)
{
for (var stage = CinemachineCore.Stage.Body; stage <= CinemachineCore.Stage.Finalize; ++stage)
InvokePostPipelineStageCallback(this, stage, ref state, deltaTime);
}
else
{
for (int i = 0; i < m_ComponentPipeline.Length; ++i)
if (m_ComponentPipeline[i] != null)
m_ComponentPipeline[i].PrePipelineMutateCameraState(ref state, deltaTime);
int componentIndex = 0;
CinemachineComponentBase postAimBody = null;
for (var stage = CinemachineCore.Stage.Body; stage <= CinemachineCore.Stage.Finalize; ++stage)
{
var c = componentIndex < m_ComponentPipeline.Length
? m_ComponentPipeline[componentIndex] : null;
if (c != null && stage == c.Stage)
{
++componentIndex;
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;
}
/// 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)
{
if (target == Follow)
{
transform.position += positionDelta;
m_State.RawPosition += positionDelta;
}
UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC
if (m_ComponentPipeline != null)
{
for (int i = 0; i < m_ComponentPipeline.Length; ++i)
m_ComponentPipeline[i].OnTargetObjectWarped(target, positionDelta);
}
base.OnTargetObjectWarped(target, positionDelta);
}
///
/// Force the virtual camera to assume a given position and orientation
///
/// Worldspace position to take
/// Worldspace orientation to take
public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
{
PreviousStateIsValid = true;
transform.SetPositionAndRotation(pos, rot);
m_State.RawPosition = pos;
m_State.RawOrientation = rot;
UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC
if (m_ComponentPipeline != null)
for (int i = 0; i < m_ComponentPipeline.Length; ++i)
m_ComponentPipeline[i].ForceCameraPosition(pos, rot);
base.ForceCameraPosition(pos, rot);
}
// This is a hack for FreeLook rigs - to be removed
internal void SetStateRawPosition(Vector3 pos) { m_State.RawPosition = pos; }
/// If we are transitioning from another vcam, grab the position from it.
/// 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;
if (fromCam != null
&& (State.BlendHint & CameraState.BlendHints.InheritPosition) != 0
&& !CinemachineCore.IsLiveInBlend(this))
{
ForceCameraPosition(fromCam.State.GetFinalPosition(), fromCam.State.GetFinalOrientation());
}
UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC
if (m_ComponentPipeline != null)
{
for (int i = 0; i < m_ComponentPipeline.Length; ++i)
if (m_ComponentPipeline[i].OnTransitionFromCamera(fromCam, worldUp, deltaTime))
forceUpdate = true;
}
if (forceUpdate)
{
InternalUpdateCameraState(worldUp, deltaTime);
InternalUpdateCameraState(worldUp, deltaTime);
}
else
UpdateCameraState(worldUp, deltaTime);
m_OnCameraLiveEvent?.Invoke(this, fromCam);
}
/// Tells whether this vcam requires input.
/// Returns true, when the vcam has an extension or components that require input.
bool AxisState.IRequiresInput.RequiresInput()
{
if (Extensions != null)
for (int i = 0; i < Extensions.Count; ++i)
if (Extensions[i] is AxisState.IRequiresInput)
return true;
UpdateComponentPipeline(); // avoid GetComponentPipeline() here because of GC
if (m_ComponentPipeline != null)
for (int i = 0; i < m_ComponentPipeline.Length; ++i)
if (m_ComponentPipeline[i] is AxisState.IRequiresInput)
return true;
return false;
}
}
}
#endif