b486678290
Library -Artifacts
457 lines
18 KiB
C#
457 lines
18 KiB
C#
#if CINEMACHINE_EXPERIMENTAL_VCAM
|
|
using UnityEngine;
|
|
using System;
|
|
using System.Linq;
|
|
|
|
namespace Cinemachine
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// NOTE: THIS CLASS IS EXPERIMENTAL, AND NOT FOR PUBLIC USE
|
|
///
|
|
/// Lighter-weight version of the CinemachineVirtualCamera.
|
|
///
|
|
/// </summary>
|
|
[DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
|
|
[DisallowMultipleComponent]
|
|
[ExecuteAlways]
|
|
[AddComponentMenu("Cinemachine/CinemachineNewVirtualCamera")]
|
|
public class CinemachineNewVirtualCamera : CinemachineVirtualCameraBase
|
|
{
|
|
/// <summary>Object for the camera children to look at (the aim target)</summary>
|
|
[Tooltip("Object for the camera children to look at (the aim target).")]
|
|
[NoSaveDuringPlay]
|
|
[VcamTargetProperty]
|
|
public Transform m_LookAt = null;
|
|
|
|
/// <summary>Object for the camera children wants to move with (the body target)</summary>
|
|
[Tooltip("Object for the camera children wants to move with (the body target).")]
|
|
[NoSaveDuringPlay]
|
|
[VcamTargetProperty]
|
|
public Transform m_Follow = null;
|
|
|
|
/// <summary>Specifies the LensSettings of this Virtual Camera.
|
|
/// These settings will be transferred to the Unity camera when the vcam is live.</summary>
|
|
[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 m_Lens = LensSettings.Default;
|
|
|
|
/// <summary> Collection of parameters that influence how this virtual camera transitions from
|
|
/// other virtual cameras </summary>
|
|
public TransitionParams m_Transitions;
|
|
|
|
/// <summary>API for the editor, to make the dragging of position handles behave better.</summary>
|
|
public bool UserIsDragging;
|
|
|
|
/// <summary>Updates the child rig cache</summary>
|
|
protected override void OnEnable()
|
|
{
|
|
base.OnEnable();
|
|
InvalidateComponentCache();
|
|
}
|
|
|
|
void Reset()
|
|
{
|
|
DestroyComponents();
|
|
}
|
|
|
|
/// <summary>Validates the settings avter inspector edit</summary>
|
|
protected override void OnValidate()
|
|
{
|
|
base.OnValidate();
|
|
m_Lens.Validate();
|
|
}
|
|
|
|
/// <summary>The camera state, which will be a blend of the child rig states</summary>
|
|
override public CameraState State { get { return m_State; } }
|
|
|
|
/// <summary>The camera state, which will be a blend of the child rig states</summary>
|
|
protected CameraState m_State = CameraState.Default;
|
|
|
|
/// <summary>Get the current LookAt target. Returns parent's LookAt if parent
|
|
/// is non-null and no specific LookAt defined for this camera</summary>
|
|
override public Transform LookAt
|
|
{
|
|
get { return ResolveLookAt(m_LookAt); }
|
|
set { m_LookAt = value; }
|
|
}
|
|
|
|
/// <summary>Get the current Follow target. Returns parent's Follow if parent
|
|
/// is non-null and no specific Follow defined for this camera</summary>
|
|
override public Transform Follow
|
|
{
|
|
get { return ResolveFollow(m_Follow); }
|
|
set { m_Follow = value; }
|
|
}
|
|
|
|
/// <summary>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 seamlessy.</summary>
|
|
/// <param name="target">The object that was warped</param>
|
|
/// <param name="positionDelta">The amount the target's position changed</param>
|
|
public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
|
|
{
|
|
if (target == Follow)
|
|
{
|
|
transform.position += positionDelta;
|
|
m_State.RawPosition += positionDelta;
|
|
}
|
|
UpdateComponentCache();
|
|
for (int i = 0; i < m_Components.Length; ++i)
|
|
{
|
|
if (m_Components[i] != null)
|
|
m_Components[i].OnTargetObjectWarped(target, positionDelta);
|
|
}
|
|
base.OnTargetObjectWarped(target, positionDelta);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force the virtual camera to assume a given position and orientation
|
|
/// </summary>
|
|
/// <param name="pos">Worldspace pposition to take</param>
|
|
/// <param name="rot">Worldspace orientation to take</param>
|
|
public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
|
|
{
|
|
PreviousStateIsValid = false;
|
|
transform.position = pos;
|
|
transform.rotation = rot;
|
|
m_State.RawPosition = pos;
|
|
m_State.RawOrientation = rot;
|
|
|
|
UpdateComponentCache();
|
|
for (int i = 0; i < m_Components.Length; ++i)
|
|
if (m_Components[i] != null)
|
|
m_Components[i].ForceCameraPosition(pos, rot);
|
|
|
|
base.ForceCameraPosition(pos, rot);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Query components and extensions for the maximum damping time.
|
|
/// </summary>
|
|
/// <returns>Highest damping setting in this vcam</returns>
|
|
public override float GetMaxDampTime()
|
|
{
|
|
float maxDamp = base.GetMaxDampTime();
|
|
UpdateComponentCache();
|
|
for (int i = 0; i < m_Components.Length; ++i)
|
|
if (m_Components[i] != null)
|
|
maxDamp = Mathf.Max(maxDamp, m_Components[i].GetMaxDampTime());
|
|
return maxDamp;
|
|
}
|
|
|
|
/// <summary>If we are transitioning from another FreeLook, grab the axis values from it.</summary>
|
|
/// <param name="fromCam">The camera being deactivated. May be null.</param>
|
|
/// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
|
|
/// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param>
|
|
public override void OnTransitionFromCamera(
|
|
ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime)
|
|
{
|
|
base.OnTransitionFromCamera(fromCam, worldUp, deltaTime);
|
|
InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime);
|
|
bool forceUpdate = false;
|
|
if (m_Transitions.m_InheritPosition && fromCam != null
|
|
&& !CinemachineCore.Instance.IsLiveInBlend(this))
|
|
{
|
|
ForceCameraPosition(fromCam.State.FinalPosition, fromCam.State.FinalOrientation);
|
|
}
|
|
UpdateComponentCache();
|
|
for (int i = 0; i < m_Components.Length; ++i)
|
|
{
|
|
if (m_Components[i] != null
|
|
&& m_Components[i].OnTransitionFromCamera(
|
|
fromCam, worldUp, deltaTime, ref m_Transitions))
|
|
forceUpdate = true;
|
|
}
|
|
if (forceUpdate)
|
|
{
|
|
InternalUpdateCameraState(worldUp, deltaTime);
|
|
InternalUpdateCameraState(worldUp, deltaTime);
|
|
}
|
|
else
|
|
UpdateCameraState(worldUp, deltaTime);
|
|
if (m_Transitions.m_OnCameraLive != null)
|
|
m_Transitions.m_OnCameraLive.Invoke(this, fromCam);
|
|
}
|
|
|
|
/// <summary>Internal use only. Called by CinemachineCore at designated update time
|
|
/// so the vcam can position itself and track its targets. All 3 child rigs are updated,
|
|
/// and a blend calculated, depending on the value of the Y axis.</summary>
|
|
/// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
|
|
/// <param name="deltaTime">Delta time for time-based effects (ignore if less than 0)</param>
|
|
override public void InternalUpdateCameraState(Vector3 worldUp, float deltaTime)
|
|
{
|
|
UpdateTargetCache();
|
|
|
|
FollowTargetAttachment = 1;
|
|
LookAtTargetAttachment = 1;
|
|
|
|
// Initialize the camera state, in case the game object got moved in the editor
|
|
m_State = PullStateFromVirtualCamera(worldUp, ref m_Lens);
|
|
|
|
// Do our stuff
|
|
SetReferenceLookAtTargetInState(ref m_State);
|
|
InvokeComponentPipeline(ref m_State, worldUp, deltaTime);
|
|
ApplyPositionBlendMethod(ref m_State, m_Transitions.m_BlendHint);
|
|
|
|
// Push the raw position back to the game object's transform, so it
|
|
// moves along with the camera.
|
|
if (!UserIsDragging)
|
|
{
|
|
if (Follow != null)
|
|
transform.position = State.RawPosition;
|
|
if (LookAt != null)
|
|
transform.rotation = State.RawOrientation;
|
|
}
|
|
// Signal that it's all done
|
|
InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Finalize, ref m_State, deltaTime);
|
|
PreviousStateIsValid = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true, when the vcam has extensions or components that require input.
|
|
/// </summary>
|
|
internal override bool RequiresUserInput()
|
|
{
|
|
return base.RequiresUserInput() ||
|
|
m_Components != null && m_Components.Any(t => t != null && t.RequiresUserInput);
|
|
}
|
|
|
|
private Transform mCachedLookAtTarget;
|
|
private CinemachineVirtualCameraBase mCachedLookAtTargetVcam;
|
|
|
|
/// <summary>Set the state's refeenceLookAt target to our lookAt, with some smarts
|
|
/// in case our LookAt points to a vcam</summary>
|
|
protected void SetReferenceLookAtTargetInState(ref CameraState state)
|
|
{
|
|
Transform lookAtTarget = LookAt;
|
|
if (lookAtTarget != mCachedLookAtTarget)
|
|
{
|
|
mCachedLookAtTarget = lookAtTarget;
|
|
mCachedLookAtTargetVcam = null;
|
|
if (lookAtTarget != null)
|
|
mCachedLookAtTargetVcam = lookAtTarget.GetComponent<CinemachineVirtualCameraBase>();
|
|
}
|
|
if (lookAtTarget != null)
|
|
{
|
|
if (mCachedLookAtTargetVcam != null)
|
|
state.ReferenceLookAt = mCachedLookAtTargetVcam.State.FinalPosition;
|
|
else
|
|
state.ReferenceLookAt = TargetPositionCache.GetTargetPosition(lookAtTarget);
|
|
}
|
|
}
|
|
|
|
protected CameraState InvokeComponentPipeline(
|
|
ref CameraState state, Vector3 worldUp, float deltaTime)
|
|
{
|
|
UpdateComponentCache();
|
|
|
|
// Extensions first
|
|
InvokePrePipelineMutateCameraStateCallback(this, ref state, deltaTime);
|
|
|
|
// Apply the component pipeline
|
|
for (CinemachineCore.Stage stage = CinemachineCore.Stage.Body;
|
|
stage <= CinemachineCore.Stage.Finalize; ++stage)
|
|
{
|
|
var c = m_Components[(int)stage];
|
|
if (c != null)
|
|
c.PrePipelineMutateCameraState(ref state, deltaTime);
|
|
}
|
|
CinemachineComponentBase postAimBody = null;
|
|
for (CinemachineCore.Stage stage = CinemachineCore.Stage.Body;
|
|
stage <= CinemachineCore.Stage.Finalize; ++stage)
|
|
{
|
|
var c = m_Components[(int)stage];
|
|
if (c != null)
|
|
{
|
|
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 (c == null)
|
|
state.BlendHint |= CameraState.BlendHintValue.IgnoreLookAtTarget; // no 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;
|
|
}
|
|
|
|
// Component Cache - serialized only for copy/paste
|
|
[SerializeField, HideInInspector, NoSaveDuringPlay]
|
|
CinemachineComponentBase[] m_Components;
|
|
|
|
/// For inspector
|
|
internal CinemachineComponentBase[] ComponentCache
|
|
{
|
|
get
|
|
{
|
|
UpdateComponentCache();
|
|
return m_Components;
|
|
}
|
|
}
|
|
|
|
/// <summary>Call this when CinemachineCompponentBase compponents are added
|
|
/// or removed. If you don't call this, you may get null reference errors.</summary>
|
|
public void InvalidateComponentCache()
|
|
{
|
|
m_Components = null;
|
|
}
|
|
|
|
/// <summary>Bring the component cache up to date if needed</summary>
|
|
protected void UpdateComponentCache()
|
|
{
|
|
#if UNITY_EDITOR
|
|
// Special case: if we have serialized in with some other game object's
|
|
// components, then we have just been pasted so we should clone them
|
|
for (int i = 0; m_Components != null && i < m_Components.Length; ++i)
|
|
{
|
|
if (m_Components[i] != null && m_Components[i].gameObject != gameObject)
|
|
{
|
|
var copyFrom = m_Components;
|
|
DestroyComponents();
|
|
CopyComponents(copyFrom);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
if (m_Components != null && m_Components.Length == (int)CinemachineCore.Stage.Finalize + 1)
|
|
return; // up to date
|
|
|
|
m_Components = new CinemachineComponentBase[(int)CinemachineCore.Stage.Finalize + 1];
|
|
var existing = GetComponents<CinemachineComponentBase>();
|
|
for (int i = 0; existing != null && i < existing.Length; ++i)
|
|
m_Components[(int)existing[i].Stage] = existing[i];
|
|
|
|
for (int i = 0; i < m_Components.Length; ++i)
|
|
{
|
|
if (m_Components[i] != null)
|
|
{
|
|
if (CinemachineCore.sShowHiddenObjects)
|
|
m_Components[i].hideFlags &= ~HideFlags.HideInInspector;
|
|
else
|
|
m_Components[i].hideFlags |= HideFlags.HideInInspector;
|
|
}
|
|
}
|
|
OnComponentCacheUpdated();
|
|
}
|
|
|
|
/// <summary>Notification that the component cache has just been update,
|
|
/// in case a subclass needs to do something extra</summary>
|
|
protected virtual void OnComponentCacheUpdated() {}
|
|
|
|
/// <summary>Destroy all the CinmachineComponentBase components</summary>
|
|
protected void DestroyComponents()
|
|
{
|
|
var existing = GetComponents<CinemachineComponentBase>();
|
|
for (int i = 0; i < existing.Length; ++i)
|
|
{
|
|
#if UNITY_EDITOR
|
|
UnityEditor.Undo.DestroyObjectImmediate(existing[i]);
|
|
#else
|
|
UnityEngine.Object.Destroy(existing[i]);
|
|
#endif
|
|
}
|
|
InvalidateComponentCache();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
// This gets called when user pastes component values
|
|
void CopyComponents(CinemachineComponentBase[] copyFrom)
|
|
{
|
|
foreach (CinemachineComponentBase c in copyFrom)
|
|
{
|
|
if (c != null)
|
|
{
|
|
Type type = c.GetType();
|
|
var copy = UnityEditor.Undo.AddComponent(gameObject, type);
|
|
UnityEditor.Undo.RecordObject(copy, "copying pipeline");
|
|
|
|
System.Reflection.BindingFlags bindingAttr
|
|
= System.Reflection.BindingFlags.Public
|
|
| System.Reflection.BindingFlags.NonPublic
|
|
| System.Reflection.BindingFlags.Instance;
|
|
|
|
System.Reflection.FieldInfo[] fields = type.GetFields(bindingAttr);
|
|
for (int i = 0; i < fields.Length; ++i)
|
|
if (!fields[i].IsStatic)
|
|
fields[i].SetValue(copy, fields[i].GetValue(c));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// Legacy support for an old API. GML todo: deprecate these methods
|
|
|
|
/// <summary>Get the component set for a specific stage.</summary>
|
|
/// <param name="stage">The stage for which we want the component</param>
|
|
/// <returns>The Cinemachine component for that stage, or null if not defined</returns>
|
|
public CinemachineComponentBase GetCinemachineComponent(CinemachineCore.Stage stage)
|
|
{
|
|
var cache = ComponentCache;
|
|
var i = (int)stage;
|
|
return i >= 0 && i < cache.Length ? cache[i] : null;
|
|
}
|
|
|
|
/// <summary>Get an existing component of a specific type from the cinemachine pipeline.</summary>
|
|
public T GetCinemachineComponent<T>() where T : CinemachineComponentBase
|
|
{
|
|
var components = ComponentCache;
|
|
foreach (var c in components)
|
|
if (c is T)
|
|
return c as T;
|
|
return null;
|
|
}
|
|
|
|
/// <summary>Add a component to the cinemachine pipeline.</summary>
|
|
public T AddCinemachineComponent<T>() where T : CinemachineComponentBase
|
|
{
|
|
var components = ComponentCache;
|
|
T c = gameObject.AddComponent<T>();
|
|
var oldC = components[(int)c.Stage];
|
|
if (oldC != null)
|
|
{
|
|
oldC.enabled = false;
|
|
RuntimeUtility.DestroyObject(oldC);
|
|
}
|
|
InvalidateComponentCache();
|
|
return c;
|
|
}
|
|
|
|
/// <summary>Remove a component from the cinemachine pipeline.</summary>
|
|
public void DestroyCinemachineComponent<T>() where T : CinemachineComponentBase
|
|
{
|
|
var components = ComponentCache;
|
|
foreach (var c in components)
|
|
{
|
|
if (c is T)
|
|
{
|
|
c.enabled = false;
|
|
RuntimeUtility.DestroyObject(c);
|
|
InvalidateComponentCache();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This prevents the sensor size from dirtying the scene in the event of aspect ratio change
|
|
internal override void OnBeforeSerialize()
|
|
{
|
|
if (!m_Lens.IsPhysicalCamera)
|
|
m_Lens.SensorSize = Vector2.one;
|
|
}
|
|
}
|
|
}
|
|
#endif
|