466 lines
20 KiB
C#
466 lines
20 KiB
C#
|
#if !UNITY_2019_3_OR_NEWER
|
||
|
#define CINEMACHINE_PHYSICS
|
||
|
#define CINEMACHINE_PHYSICS_2D
|
||
|
#endif
|
||
|
|
||
|
using System.Collections.Generic;
|
||
|
using UnityEngine;
|
||
|
using Cinemachine.Utility;
|
||
|
|
||
|
namespace Cinemachine
|
||
|
{
|
||
|
#if CINEMACHINE_PHYSICS
|
||
|
/// <summary>
|
||
|
/// Cinemachine ClearShot is a "manager camera" that owns and manages a set of
|
||
|
/// Virtual Camera gameObject children. When Live, the ClearShot will check the
|
||
|
/// children, and choose the one with the best quality shot and make it Live.
|
||
|
///
|
||
|
/// This can be a very powerful tool. If the child cameras have CinemachineCollider
|
||
|
/// extensions, they will analyze the scene for target obstructions, optimal target
|
||
|
/// distance, and other items, and report their assessment of shot quality back to
|
||
|
/// the ClearShot parent, who will then choose the best one. You can use this to set
|
||
|
/// up complex multi-camera coverage of a scene, and be assured that a clear shot of
|
||
|
/// the target will always be available.
|
||
|
///
|
||
|
/// If multiple child cameras have the same shot quality, the one with the highest
|
||
|
/// priority will be chosen.
|
||
|
///
|
||
|
/// You can also define custom blends between the ClearShot children.
|
||
|
/// </summary>
|
||
|
[DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
|
||
|
[DisallowMultipleComponent]
|
||
|
[ExecuteAlways]
|
||
|
[ExcludeFromPreset]
|
||
|
[AddComponentMenu("Cinemachine/CinemachineClearShot")]
|
||
|
[HelpURL(Documentation.BaseURL + "manual/CinemachineClearShot.html")]
|
||
|
public class CinemachineClearShot : CinemachineVirtualCameraBase
|
||
|
{
|
||
|
/// <summary>Default object for the camera children to look at (the aim target), if not specified in a child camera. May be empty.</summary>
|
||
|
[Tooltip("Default object for the camera children to look at (the aim target), if not specified in a child camera. May be empty if all children specify targets of their own.")]
|
||
|
[NoSaveDuringPlay]
|
||
|
[VcamTargetProperty]
|
||
|
public Transform m_LookAt = null;
|
||
|
|
||
|
/// <summary>Default object for the camera children wants to move with (the body target), if not specified in a child camera. May be empty.</summary>
|
||
|
[Tooltip("Default object for the camera children wants to move with (the body target), if not specified in a child camera. May be empty if all children specify targets of their own.")]
|
||
|
[NoSaveDuringPlay]
|
||
|
[VcamTargetProperty]
|
||
|
public Transform m_Follow = null;
|
||
|
|
||
|
/// <summary>When enabled, the current camera and blend will be indicated in the game window, for debugging</summary>
|
||
|
[Tooltip("When enabled, the current child camera and blend will be indicated in the game window, for debugging")]
|
||
|
[NoSaveDuringPlay]
|
||
|
public bool m_ShowDebugText = false;
|
||
|
|
||
|
/// <summary>Internal API for the editor. Do not use this filed.</summary>
|
||
|
[SerializeField, HideInInspector, NoSaveDuringPlay]
|
||
|
internal CinemachineVirtualCameraBase[] m_ChildCameras = null;
|
||
|
|
||
|
/// <summary>Wait this many seconds before activating a new child camera</summary>
|
||
|
[Tooltip("Wait this many seconds before activating a new child camera")]
|
||
|
public float m_ActivateAfter;
|
||
|
|
||
|
/// <summary>An active camera must be active for at least this many seconds</summary>
|
||
|
[Tooltip("An active camera must be active for at least this many seconds")]
|
||
|
public float m_MinDuration;
|
||
|
|
||
|
/// <summary>If checked, camera choice will be randomized if multiple cameras are equally desirable. Otherwise, child list order will be used</summary>
|
||
|
[Tooltip("If checked, camera choice will be randomized if multiple cameras are equally desirable. Otherwise, child list order and child camera priority will be used.")]
|
||
|
public bool m_RandomizeChoice = false;
|
||
|
|
||
|
/// <summary>The blend which is used if you don't explicitly define a blend between two Virtual Cameras</summary>
|
||
|
[CinemachineBlendDefinitionProperty]
|
||
|
[Tooltip("The blend which is used if you don't explicitly define a blend between two Virtual Cameras")]
|
||
|
public CinemachineBlendDefinition m_DefaultBlend
|
||
|
= new CinemachineBlendDefinition(CinemachineBlendDefinition.Style.Cut, 0);
|
||
|
|
||
|
/// <summary>This is the asset which contains custom settings for specific blends</summary>
|
||
|
[HideInInspector]
|
||
|
public CinemachineBlenderSettings m_CustomBlends = null;
|
||
|
|
||
|
/// <summary>Gets a brief debug description of this virtual camera, for use when displayiong debug info</summary>
|
||
|
public override string Description
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
// Show the active camera and blend
|
||
|
if (mActiveBlend != null)
|
||
|
return mActiveBlend.Description;
|
||
|
|
||
|
ICinemachineCamera vcam = LiveChild;
|
||
|
if (vcam == null)
|
||
|
return "(none)";
|
||
|
var sb = CinemachineDebug.SBFromPool();
|
||
|
sb.Append("["); sb.Append(vcam.Name); sb.Append("]");
|
||
|
string text = sb.ToString();
|
||
|
CinemachineDebug.ReturnToPool(sb);
|
||
|
return text;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Get the current "best" child virtual camera, that would be chosen
|
||
|
/// if the ClearShot camera were active.</summary>
|
||
|
public ICinemachineCamera LiveChild { get; set; }
|
||
|
|
||
|
/// <summary>The CameraState of the currently live child</summary>
|
||
|
public override CameraState State { get { return m_State; } }
|
||
|
|
||
|
/// <summary>Check whether the vcam a live child of this camera.</summary>
|
||
|
/// <param name="vcam">The Virtual Camera to check</param>
|
||
|
/// <param name="dominantChildOnly">If truw, will only return true if this vcam is the dominat live child</param>
|
||
|
/// <returns>True if the vcam is currently actively influencing the state of this vcam</returns>
|
||
|
public override bool IsLiveChild(ICinemachineCamera vcam, bool dominantChildOnly = false)
|
||
|
{
|
||
|
return vcam == LiveChild || (mActiveBlend != null && mActiveBlend.Uses(vcam));
|
||
|
}
|
||
|
|
||
|
/// <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)
|
||
|
{
|
||
|
UpdateListOfChildren();
|
||
|
foreach (var vcam in m_ChildCameras)
|
||
|
vcam.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)
|
||
|
{
|
||
|
UpdateListOfChildren();
|
||
|
foreach (var vcam in m_ChildCameras)
|
||
|
vcam.ForceCameraPosition(pos, rot);
|
||
|
base.ForceCameraPosition(pos, rot);
|
||
|
}
|
||
|
|
||
|
/// <summary>Internal use only. 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.</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>
|
||
|
public override void InternalUpdateCameraState(Vector3 worldUp, float deltaTime)
|
||
|
{
|
||
|
// Choose the best camera
|
||
|
UpdateListOfChildren();
|
||
|
ICinemachineCamera previousCam = LiveChild;
|
||
|
LiveChild = ChooseCurrentCamera(worldUp);
|
||
|
|
||
|
// Are we transitioning cameras?
|
||
|
if (previousCam != LiveChild && LiveChild != null)
|
||
|
{
|
||
|
// Notify incoming camera of transition
|
||
|
LiveChild.OnTransitionFromCamera(previousCam, worldUp, deltaTime);
|
||
|
|
||
|
// Generate Camera Activation event in the brain if live
|
||
|
CinemachineCore.Instance.GenerateCameraActivationEvent(LiveChild, previousCam);
|
||
|
|
||
|
// Are we transitioning cameras?
|
||
|
if (previousCam != null)
|
||
|
{
|
||
|
// Create a blend (will be null if a cut)
|
||
|
mActiveBlend = CreateBlend(
|
||
|
previousCam, LiveChild,
|
||
|
LookupBlend(previousCam, LiveChild), mActiveBlend);
|
||
|
|
||
|
// If cutting, generate a camera cut event if live
|
||
|
if (mActiveBlend == null || !mActiveBlend.Uses(previousCam))
|
||
|
CinemachineCore.Instance.GenerateCameraCutEvent(LiveChild);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Advance the current blend (if any)
|
||
|
if (mActiveBlend != null)
|
||
|
{
|
||
|
mActiveBlend.TimeInBlend += (deltaTime >= 0)
|
||
|
? deltaTime : mActiveBlend.Duration;
|
||
|
if (mActiveBlend.IsComplete)
|
||
|
mActiveBlend = null;
|
||
|
}
|
||
|
|
||
|
if (mActiveBlend != null)
|
||
|
{
|
||
|
mActiveBlend.UpdateCameraState(worldUp, deltaTime);
|
||
|
m_State = mActiveBlend.State;
|
||
|
}
|
||
|
else if (LiveChild != null)
|
||
|
{
|
||
|
if (m_TransitioningFrom != null)
|
||
|
LiveChild.OnTransitionFromCamera(m_TransitioningFrom, worldUp, deltaTime);
|
||
|
m_State = LiveChild.State;
|
||
|
}
|
||
|
m_TransitioningFrom = null;
|
||
|
InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Finalize, ref m_State, deltaTime);
|
||
|
PreviousStateIsValid = true;
|
||
|
}
|
||
|
|
||
|
/// <summary>Makes sure the internal child cache is up to date</summary>
|
||
|
protected override void OnEnable()
|
||
|
{
|
||
|
base.OnEnable();
|
||
|
InvalidateListOfChildren();
|
||
|
mActiveBlend = null;
|
||
|
CinemachineDebug.OnGUIHandlers -= OnGuiHandler;
|
||
|
CinemachineDebug.OnGUIHandlers += OnGuiHandler;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Uninstall the GUI handler
|
||
|
/// </summary>
|
||
|
protected override void OnDisable()
|
||
|
{
|
||
|
base.OnDisable();
|
||
|
CinemachineDebug.OnGUIHandlers -= OnGuiHandler;
|
||
|
}
|
||
|
|
||
|
/// <summary>Makes sure the internal child cache is up to date</summary>
|
||
|
public void OnTransformChildrenChanged()
|
||
|
{
|
||
|
InvalidateListOfChildren();
|
||
|
UpdateListOfChildren();
|
||
|
}
|
||
|
|
||
|
/// Will only be called if Unity Editor - never in build
|
||
|
private void OnGuiHandler()
|
||
|
{
|
||
|
if (!m_ShowDebugText)
|
||
|
CinemachineDebug.ReleaseScreenPos(this);
|
||
|
else
|
||
|
{
|
||
|
var sb = CinemachineDebug.SBFromPool();
|
||
|
sb.Append(Name); sb.Append(": "); sb.Append(Description);
|
||
|
string text = sb.ToString();
|
||
|
Rect r = CinemachineDebug.GetScreenPos(this, text, GUI.skin.box);
|
||
|
GUI.Label(r, text, GUI.skin.box);
|
||
|
CinemachineDebug.ReturnToPool(sb);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Is there a blend in progress?</summary>
|
||
|
public bool IsBlending { get { return mActiveBlend != null; } }
|
||
|
|
||
|
CameraState m_State = CameraState.Default;
|
||
|
|
||
|
/// <summary>The list of child cameras. These are just the immediate children in the hierarchy.</summary>
|
||
|
public CinemachineVirtualCameraBase[] ChildCameras
|
||
|
{
|
||
|
get { UpdateListOfChildren(); return m_ChildCameras; }
|
||
|
}
|
||
|
|
||
|
float mActivationTime = 0;
|
||
|
float mPendingActivationTime = 0;
|
||
|
ICinemachineCamera mPendingCamera;
|
||
|
private CinemachineBlend mActiveBlend = null;
|
||
|
|
||
|
void InvalidateListOfChildren()
|
||
|
{
|
||
|
m_ChildCameras = null;
|
||
|
m_RandomizedChilden = null;
|
||
|
LiveChild = null;
|
||
|
}
|
||
|
|
||
|
/// <summary>If RandomizeChoice is enabled, call this to re-randomize the children next frame.
|
||
|
/// This is useful if you want to freshen up the shot.</summary>
|
||
|
public void ResetRandomization()
|
||
|
{
|
||
|
m_RandomizedChilden = null;
|
||
|
mRandomizeNow = true;
|
||
|
}
|
||
|
|
||
|
void UpdateListOfChildren()
|
||
|
{
|
||
|
if (m_ChildCameras != null)
|
||
|
return;
|
||
|
|
||
|
List<CinemachineVirtualCameraBase> list = new List<CinemachineVirtualCameraBase>();
|
||
|
CinemachineVirtualCameraBase[] kids = GetComponentsInChildren<CinemachineVirtualCameraBase>(true);
|
||
|
foreach (CinemachineVirtualCameraBase k in kids)
|
||
|
if (k.transform.parent == transform)
|
||
|
list.Add(k);
|
||
|
m_ChildCameras = list.ToArray();
|
||
|
|
||
|
// Zap the cached current instructions
|
||
|
mActivationTime = mPendingActivationTime = 0;
|
||
|
mPendingCamera = null;
|
||
|
LiveChild = null;
|
||
|
mActiveBlend = null;
|
||
|
}
|
||
|
|
||
|
private bool mRandomizeNow = false;
|
||
|
private CinemachineVirtualCameraBase[] m_RandomizedChilden = null;
|
||
|
|
||
|
private ICinemachineCamera ChooseCurrentCamera(Vector3 worldUp)
|
||
|
{
|
||
|
if (m_ChildCameras == null || m_ChildCameras.Length == 0)
|
||
|
{
|
||
|
mActivationTime = 0;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
CinemachineVirtualCameraBase[] childCameras = m_ChildCameras;
|
||
|
if (!m_RandomizeChoice)
|
||
|
m_RandomizedChilden = null;
|
||
|
else if (m_ChildCameras.Length > 1)
|
||
|
{
|
||
|
if (m_RandomizedChilden == null)
|
||
|
m_RandomizedChilden = Randomize(m_ChildCameras);
|
||
|
childCameras = m_RandomizedChilden;
|
||
|
}
|
||
|
|
||
|
if (LiveChild != null && !LiveChild.VirtualCameraGameObject.activeSelf)
|
||
|
LiveChild = null;
|
||
|
ICinemachineCamera best = LiveChild;
|
||
|
for (int i = 0; i < childCameras.Length; ++i)
|
||
|
{
|
||
|
CinemachineVirtualCameraBase vcam = childCameras[i];
|
||
|
if (vcam != null && vcam.gameObject.activeInHierarchy)
|
||
|
{
|
||
|
// Choose the first in the list that is better than the current
|
||
|
if (best == null
|
||
|
|| vcam.State.ShotQuality > best.State.ShotQuality
|
||
|
|| (vcam.State.ShotQuality == best.State.ShotQuality && vcam.Priority > best.Priority)
|
||
|
|| (m_RandomizeChoice && mRandomizeNow && (ICinemachineCamera)vcam != LiveChild
|
||
|
&& vcam.State.ShotQuality == best.State.ShotQuality
|
||
|
&& vcam.Priority == best.Priority))
|
||
|
{
|
||
|
best = vcam;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
mRandomizeNow = false;
|
||
|
|
||
|
float now = CinemachineCore.CurrentTime;
|
||
|
if (mActivationTime != 0)
|
||
|
{
|
||
|
// Is it active now?
|
||
|
if (LiveChild == best)
|
||
|
{
|
||
|
// Yes, cancel any pending
|
||
|
mPendingActivationTime = 0;
|
||
|
mPendingCamera = null;
|
||
|
return best;
|
||
|
}
|
||
|
|
||
|
// Is it pending?
|
||
|
if (PreviousStateIsValid)
|
||
|
{
|
||
|
if (mPendingActivationTime != 0 && mPendingCamera == best)
|
||
|
{
|
||
|
// Has it been pending long enough, and are we allowed to switch away
|
||
|
// from the active action?
|
||
|
if ((now - mPendingActivationTime) > m_ActivateAfter
|
||
|
&& (now - mActivationTime) > m_MinDuration)
|
||
|
{
|
||
|
// Yes, activate it now
|
||
|
m_RandomizedChilden = null; // reshuffle the children
|
||
|
mActivationTime = now;
|
||
|
mPendingActivationTime = 0;
|
||
|
mPendingCamera = null;
|
||
|
return best;
|
||
|
}
|
||
|
return LiveChild;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Neither active nor pending.
|
||
|
mPendingActivationTime = 0; // cancel the pending, if any
|
||
|
mPendingCamera = null;
|
||
|
|
||
|
// Can we activate it now?
|
||
|
if (PreviousStateIsValid && mActivationTime > 0)
|
||
|
{
|
||
|
if (m_ActivateAfter > 0
|
||
|
|| (now - mActivationTime) < m_MinDuration)
|
||
|
{
|
||
|
// Too early - make it pending
|
||
|
mPendingCamera = best;
|
||
|
mPendingActivationTime = now;
|
||
|
return LiveChild;
|
||
|
}
|
||
|
}
|
||
|
// Activate now
|
||
|
m_RandomizedChilden = null; // reshuffle the children
|
||
|
mActivationTime = now;
|
||
|
return best;
|
||
|
}
|
||
|
|
||
|
struct Pair { public int a; public float b; }
|
||
|
CinemachineVirtualCameraBase[] Randomize(CinemachineVirtualCameraBase[] src)
|
||
|
{
|
||
|
List<Pair> pairs = new List<Pair>();
|
||
|
for (int i = 0; i < src.Length; ++i)
|
||
|
{
|
||
|
Pair p = new Pair(); p.a = i; p.b = Random.Range(0, 1000f);
|
||
|
pairs.Add(p);
|
||
|
}
|
||
|
pairs.Sort((p1, p2) => (int)p1.b - (int)p2.b);
|
||
|
CinemachineVirtualCameraBase[] dst = new CinemachineVirtualCameraBase[src.Length];
|
||
|
Pair[] result = pairs.ToArray();
|
||
|
for (int i = 0; i < src.Length; ++i)
|
||
|
dst[i] = src[result[i].a];
|
||
|
return dst;
|
||
|
}
|
||
|
|
||
|
private CinemachineBlendDefinition LookupBlend(
|
||
|
ICinemachineCamera fromKey, ICinemachineCamera toKey)
|
||
|
{
|
||
|
// Get the blend curve that's most appropriate for these cameras
|
||
|
CinemachineBlendDefinition blend = m_DefaultBlend;
|
||
|
if (m_CustomBlends != null)
|
||
|
{
|
||
|
string fromCameraName = (fromKey != null) ? fromKey.Name : string.Empty;
|
||
|
string toCameraName = (toKey != null) ? toKey.Name : string.Empty;
|
||
|
blend = m_CustomBlends.GetBlendForVirtualCameras(
|
||
|
fromCameraName, toCameraName, blend);
|
||
|
}
|
||
|
if (CinemachineCore.GetBlendOverride != null)
|
||
|
blend = CinemachineCore.GetBlendOverride(fromKey, toKey, blend, this);
|
||
|
return blend;
|
||
|
}
|
||
|
|
||
|
/// <summary>Notification that this virtual camera is going live.
|
||
|
/// This implementation resets the child randomization.</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);
|
||
|
m_TransitioningFrom = fromCam;
|
||
|
if (m_RandomizeChoice && mActiveBlend == null)
|
||
|
{
|
||
|
m_RandomizedChilden = null;
|
||
|
LiveChild = null;
|
||
|
}
|
||
|
InternalUpdateCameraState(worldUp, deltaTime);
|
||
|
}
|
||
|
|
||
|
ICinemachineCamera m_TransitioningFrom;
|
||
|
}
|
||
|
#endif
|
||
|
}
|