CinemachineClearShot.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. #if !UNITY_2019_3_OR_NEWER
  2. #define CINEMACHINE_PHYSICS
  3. #define CINEMACHINE_PHYSICS_2D
  4. #endif
  5. using System.Collections.Generic;
  6. using UnityEngine;
  7. using Cinemachine.Utility;
  8. namespace Cinemachine
  9. {
  10. #if CINEMACHINE_PHYSICS
  11. /// <summary>
  12. /// Cinemachine ClearShot is a "manager camera" that owns and manages a set of
  13. /// Virtual Camera gameObject children. When Live, the ClearShot will check the
  14. /// children, and choose the one with the best quality shot and make it Live.
  15. ///
  16. /// This can be a very powerful tool. If the child cameras have CinemachineCollider
  17. /// extensions, they will analyze the scene for target obstructions, optimal target
  18. /// distance, and other items, and report their assessment of shot quality back to
  19. /// the ClearShot parent, who will then choose the best one. You can use this to set
  20. /// up complex multi-camera coverage of a scene, and be assured that a clear shot of
  21. /// the target will always be available.
  22. ///
  23. /// If multiple child cameras have the same shot quality, the one with the highest
  24. /// priority will be chosen.
  25. ///
  26. /// You can also define custom blends between the ClearShot children.
  27. /// </summary>
  28. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  29. [DisallowMultipleComponent]
  30. #if UNITY_2018_3_OR_NEWER
  31. [ExecuteAlways]
  32. #else
  33. [ExecuteInEditMode]
  34. #endif
  35. [ExcludeFromPreset]
  36. [AddComponentMenu("Cinemachine/CinemachineClearShot")]
  37. [HelpURL(Documentation.BaseURL + "manual/CinemachineClearShot.html")]
  38. public class CinemachineClearShot : CinemachineVirtualCameraBase
  39. {
  40. /// <summary>Default object for the camera children to look at (the aim target), if not specified in a child camera. May be empty.</summary>
  41. [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.")]
  42. [NoSaveDuringPlay]
  43. [VcamTargetProperty]
  44. public Transform m_LookAt = null;
  45. /// <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>
  46. [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.")]
  47. [NoSaveDuringPlay]
  48. [VcamTargetProperty]
  49. public Transform m_Follow = null;
  50. /// <summary>When enabled, the current camera and blend will be indicated in the game window, for debugging</summary>
  51. [Tooltip("When enabled, the current child camera and blend will be indicated in the game window, for debugging")]
  52. [NoSaveDuringPlay]
  53. public bool m_ShowDebugText = false;
  54. /// <summary>Internal API for the editor. Do not use this filed.</summary>
  55. [SerializeField, HideInInspector, NoSaveDuringPlay]
  56. internal CinemachineVirtualCameraBase[] m_ChildCameras = null;
  57. /// <summary>Wait this many seconds before activating a new child camera</summary>
  58. [Tooltip("Wait this many seconds before activating a new child camera")]
  59. public float m_ActivateAfter;
  60. /// <summary>An active camera must be active for at least this many seconds</summary>
  61. [Tooltip("An active camera must be active for at least this many seconds")]
  62. public float m_MinDuration;
  63. /// <summary>If checked, camera choice will be randomized if multiple cameras are equally desirable. Otherwise, child list order will be used</summary>
  64. [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.")]
  65. public bool m_RandomizeChoice = false;
  66. /// <summary>The blend which is used if you don't explicitly define a blend between two Virtual Cameras</summary>
  67. [CinemachineBlendDefinitionProperty]
  68. [Tooltip("The blend which is used if you don't explicitly define a blend between two Virtual Cameras")]
  69. public CinemachineBlendDefinition m_DefaultBlend
  70. = new CinemachineBlendDefinition(CinemachineBlendDefinition.Style.Cut, 0);
  71. /// <summary>This is the asset which contains custom settings for specific blends</summary>
  72. [HideInInspector]
  73. public CinemachineBlenderSettings m_CustomBlends = null;
  74. /// <summary>Gets a brief debug description of this virtual camera, for use when displayiong debug info</summary>
  75. public override string Description
  76. {
  77. get
  78. {
  79. // Show the active camera and blend
  80. if (mActiveBlend != null)
  81. return mActiveBlend.Description;
  82. ICinemachineCamera vcam = LiveChild;
  83. if (vcam == null)
  84. return "(none)";
  85. var sb = CinemachineDebug.SBFromPool();
  86. sb.Append("["); sb.Append(vcam.Name); sb.Append("]");
  87. string text = sb.ToString();
  88. CinemachineDebug.ReturnToPool(sb);
  89. return text;
  90. }
  91. }
  92. /// <summary>Get the current "best" child virtual camera, that would be chosen
  93. /// if the ClearShot camera were active.</summary>
  94. public ICinemachineCamera LiveChild { set; get; }
  95. /// <summary>The CameraState of the currently live child</summary>
  96. public override CameraState State { get { return m_State; } }
  97. /// <summary>Check whether the vcam a live child of this camera.</summary>
  98. /// <param name="vcam">The Virtual Camera to check</param>
  99. /// <param name="dominantChildOnly">If truw, will only return true if this vcam is the dominat live child</param>
  100. /// <returns>True if the vcam is currently actively influencing the state of this vcam</returns>
  101. public override bool IsLiveChild(ICinemachineCamera vcam, bool dominantChildOnly = false)
  102. {
  103. return vcam == LiveChild || (mActiveBlend != null && mActiveBlend.Uses(vcam));
  104. }
  105. /// <summary>Get the current LookAt target. Returns parent's LookAt if parent
  106. /// is non-null and no specific LookAt defined for this camera</summary>
  107. override public Transform LookAt
  108. {
  109. get { return ResolveLookAt(m_LookAt); }
  110. set { m_LookAt = value; }
  111. }
  112. /// <summary>Get the current Follow target. Returns parent's Follow if parent
  113. /// is non-null and no specific Follow defined for this camera</summary>
  114. override public Transform Follow
  115. {
  116. get { return ResolveFollow(m_Follow); }
  117. set { m_Follow = value; }
  118. }
  119. /// <summary>This is called to notify the vcam that a target got warped,
  120. /// so that the vcam can update its internal state to make the camera
  121. /// also warp seamlessy.</summary>
  122. /// <param name="target">The object that was warped</param>
  123. /// <param name="positionDelta">The amount the target's position changed</param>
  124. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  125. {
  126. UpdateListOfChildren();
  127. foreach (var vcam in m_ChildCameras)
  128. vcam.OnTargetObjectWarped(target, positionDelta);
  129. base.OnTargetObjectWarped(target, positionDelta);
  130. }
  131. /// <summary>
  132. /// Force the virtual camera to assume a given position and orientation
  133. /// </summary>
  134. /// <param name="pos">Worldspace pposition to take</param>
  135. /// <param name="rot">Worldspace orientation to take</param>
  136. public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
  137. {
  138. UpdateListOfChildren();
  139. foreach (var vcam in m_ChildCameras)
  140. vcam.ForceCameraPosition(pos, rot);
  141. base.ForceCameraPosition(pos, rot);
  142. }
  143. /// <summary>Internal use only. Called by CinemachineCore at designated update time
  144. /// so the vcam can position itself and track its targets. This implementation
  145. /// updates all the children, chooses the best one, and implements any required blending.</summary>
  146. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  147. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than 0)</param>
  148. public override void InternalUpdateCameraState(Vector3 worldUp, float deltaTime)
  149. {
  150. // Choose the best camera
  151. UpdateListOfChildren();
  152. ICinemachineCamera previousCam = LiveChild;
  153. LiveChild = ChooseCurrentCamera(worldUp);
  154. // Are we transitioning cameras?
  155. if (previousCam != LiveChild && LiveChild != null)
  156. {
  157. // Notify incoming camera of transition
  158. LiveChild.OnTransitionFromCamera(previousCam, worldUp, deltaTime);
  159. // Generate Camera Activation event in the brain if live
  160. CinemachineCore.Instance.GenerateCameraActivationEvent(LiveChild, previousCam);
  161. // Are we transitioning cameras?
  162. if (previousCam != null)
  163. {
  164. // Create a blend (will be null if a cut)
  165. mActiveBlend = CreateBlend(
  166. previousCam, LiveChild,
  167. LookupBlend(previousCam, LiveChild), mActiveBlend);
  168. // If cutting, generate a camera cut event if live
  169. if (mActiveBlend == null || !mActiveBlend.Uses(previousCam))
  170. CinemachineCore.Instance.GenerateCameraCutEvent(LiveChild);
  171. }
  172. }
  173. // Advance the current blend (if any)
  174. if (mActiveBlend != null)
  175. {
  176. mActiveBlend.TimeInBlend += (deltaTime >= 0)
  177. ? deltaTime : mActiveBlend.Duration;
  178. if (mActiveBlend.IsComplete)
  179. mActiveBlend = null;
  180. }
  181. if (mActiveBlend != null)
  182. {
  183. mActiveBlend.UpdateCameraState(worldUp, deltaTime);
  184. m_State = mActiveBlend.State;
  185. }
  186. else if (LiveChild != null)
  187. {
  188. if (TransitioningFrom != null)
  189. LiveChild.OnTransitionFromCamera(TransitioningFrom, worldUp, deltaTime);
  190. m_State = LiveChild.State;
  191. }
  192. TransitioningFrom = null;
  193. InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Finalize, ref m_State, deltaTime);
  194. PreviousStateIsValid = true;
  195. }
  196. /// <summary>Makes sure the internal child cache is up to date</summary>
  197. protected override void OnEnable()
  198. {
  199. base.OnEnable();
  200. InvalidateListOfChildren();
  201. mActiveBlend = null;
  202. CinemachineDebug.OnGUIHandlers -= OnGuiHandler;
  203. CinemachineDebug.OnGUIHandlers += OnGuiHandler;
  204. }
  205. /// <summary>
  206. /// Uninstall the GUI handler
  207. /// </summary>
  208. protected override void OnDisable()
  209. {
  210. base.OnDisable();
  211. CinemachineDebug.OnGUIHandlers -= OnGuiHandler;
  212. }
  213. /// <summary>Makes sure the internal child cache is up to date</summary>
  214. public void OnTransformChildrenChanged()
  215. {
  216. InvalidateListOfChildren();
  217. UpdateListOfChildren();
  218. }
  219. /// Will only be called if Unity Editor - never in build
  220. private void OnGuiHandler()
  221. {
  222. if (!m_ShowDebugText)
  223. CinemachineDebug.ReleaseScreenPos(this);
  224. else
  225. {
  226. var sb = CinemachineDebug.SBFromPool();
  227. sb.Append(Name); sb.Append(": "); sb.Append(Description);
  228. string text = sb.ToString();
  229. Rect r = CinemachineDebug.GetScreenPos(this, text, GUI.skin.box);
  230. GUI.Label(r, text, GUI.skin.box);
  231. CinemachineDebug.ReturnToPool(sb);
  232. }
  233. }
  234. /// <summary>Is there a blend in progress?</summary>
  235. public bool IsBlending { get { return mActiveBlend != null; } }
  236. CameraState m_State = CameraState.Default;
  237. /// <summary>The list of child cameras. These are just the immediate children in the hierarchy.</summary>
  238. public CinemachineVirtualCameraBase[] ChildCameras
  239. {
  240. get { UpdateListOfChildren(); return m_ChildCameras; }
  241. }
  242. float mActivationTime = 0;
  243. float mPendingActivationTime = 0;
  244. ICinemachineCamera mPendingCamera;
  245. private CinemachineBlend mActiveBlend = null;
  246. void InvalidateListOfChildren()
  247. {
  248. m_ChildCameras = null;
  249. m_RandomizedChilden = null;
  250. LiveChild = null;
  251. }
  252. /// <summary>If RandomizeChoice is enabled, call this to re-randomize the children next frame.
  253. /// This is useful if you want to freshen up the shot.</summary>
  254. public void ResetRandomization()
  255. {
  256. m_RandomizedChilden = null;
  257. mRandomizeNow = true;
  258. }
  259. void UpdateListOfChildren()
  260. {
  261. if (m_ChildCameras != null)
  262. return;
  263. List<CinemachineVirtualCameraBase> list = new List<CinemachineVirtualCameraBase>();
  264. CinemachineVirtualCameraBase[] kids = GetComponentsInChildren<CinemachineVirtualCameraBase>(true);
  265. foreach (CinemachineVirtualCameraBase k in kids)
  266. if (k.transform.parent == transform)
  267. list.Add(k);
  268. m_ChildCameras = list.ToArray();
  269. // Zap the cached current instructions
  270. mActivationTime = mPendingActivationTime = 0;
  271. mPendingCamera = null;
  272. LiveChild = null;
  273. mActiveBlend = null;
  274. }
  275. private bool mRandomizeNow = false;
  276. private CinemachineVirtualCameraBase[] m_RandomizedChilden = null;
  277. private ICinemachineCamera ChooseCurrentCamera(Vector3 worldUp)
  278. {
  279. if (m_ChildCameras == null || m_ChildCameras.Length == 0)
  280. {
  281. mActivationTime = 0;
  282. return null;
  283. }
  284. CinemachineVirtualCameraBase[] childCameras = m_ChildCameras;
  285. if (!m_RandomizeChoice)
  286. m_RandomizedChilden = null;
  287. else if (m_ChildCameras.Length > 1)
  288. {
  289. if (m_RandomizedChilden == null)
  290. m_RandomizedChilden = Randomize(m_ChildCameras);
  291. childCameras = m_RandomizedChilden;
  292. }
  293. if (LiveChild != null && !LiveChild.VirtualCameraGameObject.activeSelf)
  294. LiveChild = null;
  295. ICinemachineCamera best = LiveChild;
  296. for (int i = 0; i < childCameras.Length; ++i)
  297. {
  298. CinemachineVirtualCameraBase vcam = childCameras[i];
  299. if (vcam != null && vcam.gameObject.activeInHierarchy)
  300. {
  301. // Choose the first in the list that is better than the current
  302. if (best == null
  303. || vcam.State.ShotQuality > best.State.ShotQuality
  304. || (vcam.State.ShotQuality == best.State.ShotQuality && vcam.Priority > best.Priority)
  305. || (m_RandomizeChoice && mRandomizeNow && (ICinemachineCamera)vcam != LiveChild
  306. && vcam.State.ShotQuality == best.State.ShotQuality
  307. && vcam.Priority == best.Priority))
  308. {
  309. best = vcam;
  310. }
  311. }
  312. }
  313. mRandomizeNow = false;
  314. float now = CinemachineCore.CurrentTime;
  315. if (mActivationTime != 0)
  316. {
  317. // Is it active now?
  318. if (LiveChild == best)
  319. {
  320. // Yes, cancel any pending
  321. mPendingActivationTime = 0;
  322. mPendingCamera = null;
  323. return best;
  324. }
  325. // Is it pending?
  326. if (PreviousStateIsValid)
  327. {
  328. if (mPendingActivationTime != 0 && mPendingCamera == best)
  329. {
  330. // Has it been pending long enough, and are we allowed to switch away
  331. // from the active action?
  332. if ((now - mPendingActivationTime) > m_ActivateAfter
  333. && (now - mActivationTime) > m_MinDuration)
  334. {
  335. // Yes, activate it now
  336. m_RandomizedChilden = null; // reshuffle the children
  337. mActivationTime = now;
  338. mPendingActivationTime = 0;
  339. mPendingCamera = null;
  340. return best;
  341. }
  342. return LiveChild;
  343. }
  344. }
  345. }
  346. // Neither active nor pending.
  347. mPendingActivationTime = 0; // cancel the pending, if any
  348. mPendingCamera = null;
  349. // Can we activate it now?
  350. if (PreviousStateIsValid && mActivationTime > 0)
  351. {
  352. if (m_ActivateAfter > 0
  353. || (now - mActivationTime) < m_MinDuration)
  354. {
  355. // Too early - make it pending
  356. mPendingCamera = best;
  357. mPendingActivationTime = now;
  358. return LiveChild;
  359. }
  360. }
  361. // Activate now
  362. m_RandomizedChilden = null; // reshuffle the children
  363. mActivationTime = now;
  364. return best;
  365. }
  366. struct Pair { public int a; public float b; }
  367. CinemachineVirtualCameraBase[] Randomize(CinemachineVirtualCameraBase[] src)
  368. {
  369. List<Pair> pairs = new List<Pair>();
  370. for (int i = 0; i < src.Length; ++i)
  371. {
  372. Pair p = new Pair(); p.a = i; p.b = Random.Range(0, 1000f);
  373. pairs.Add(p);
  374. }
  375. pairs.Sort((p1, p2) => (int)p1.b - (int)p2.b);
  376. CinemachineVirtualCameraBase[] dst = new CinemachineVirtualCameraBase[src.Length];
  377. Pair[] result = pairs.ToArray();
  378. for (int i = 0; i < src.Length; ++i)
  379. dst[i] = src[result[i].a];
  380. return dst;
  381. }
  382. private CinemachineBlendDefinition LookupBlend(
  383. ICinemachineCamera fromKey, ICinemachineCamera toKey)
  384. {
  385. // Get the blend curve that's most appropriate for these cameras
  386. CinemachineBlendDefinition blend = m_DefaultBlend;
  387. if (m_CustomBlends != null)
  388. {
  389. string fromCameraName = (fromKey != null) ? fromKey.Name : string.Empty;
  390. string toCameraName = (toKey != null) ? toKey.Name : string.Empty;
  391. blend = m_CustomBlends.GetBlendForVirtualCameras(
  392. fromCameraName, toCameraName, blend);
  393. }
  394. if (CinemachineCore.GetBlendOverride != null)
  395. blend = CinemachineCore.GetBlendOverride(fromKey, toKey, blend, this);
  396. return blend;
  397. }
  398. /// <summary>Notification that this virtual camera is going live.
  399. /// This implementation resets the child randomization.</summary>
  400. /// <param name="fromCam">The camera being deactivated. May be null.</param>
  401. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  402. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param>
  403. public override void OnTransitionFromCamera(
  404. ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime)
  405. {
  406. base.OnTransitionFromCamera(fromCam, worldUp, deltaTime);
  407. InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime);
  408. TransitioningFrom = fromCam;
  409. if (m_RandomizeChoice && mActiveBlend == null)
  410. {
  411. m_RandomizedChilden = null;
  412. LiveChild = null;
  413. }
  414. InternalUpdateCameraState(worldUp, deltaTime);
  415. }
  416. ICinemachineCamera TransitioningFrom { get; set; }
  417. }
  418. #endif
  419. }