CinemachineFreeLook.cs 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916
  1. using UnityEngine;
  2. using Cinemachine.Utility;
  3. using UnityEngine.Serialization;
  4. using System;
  5. using System.Collections.Generic;
  6. namespace Cinemachine
  7. {
  8. /// <summary>
  9. /// A Cinemachine Camera geared towards a 3rd person camera experience.
  10. /// The camera orbits around its subject with three separate camera rigs defining
  11. /// rings around the target. Each rig has its own radius, height offset, composer,
  12. /// and lens settings.
  13. /// Depending on the camera's position along the spline connecting these three rigs,
  14. /// these settings are interpolated to give the final camera position and state.
  15. /// </summary>
  16. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  17. [DisallowMultipleComponent]
  18. [ExecuteAlways]
  19. [ExcludeFromPreset]
  20. [AddComponentMenu("Cinemachine/CinemachineFreeLook")]
  21. [HelpURL(Documentation.BaseURL + "manual/CinemachineFreeLook.html")]
  22. public class CinemachineFreeLook : CinemachineVirtualCameraBase
  23. {
  24. /// <summary>Object for the camera children to look at (the aim target)</summary>
  25. [Tooltip("Object for the camera children to look at (the aim target).")]
  26. [NoSaveDuringPlay]
  27. [VcamTargetProperty]
  28. public Transform m_LookAt = null;
  29. /// <summary>Object for the camera children wants to move with (the body target)</summary>
  30. [Tooltip("Object for the camera children wants to move with (the body target).")]
  31. [NoSaveDuringPlay]
  32. [VcamTargetProperty]
  33. public Transform m_Follow = null;
  34. /// <summary>If enabled, this lens setting will apply to all three child rigs,
  35. /// otherwise the child rig lens settings will be used</summary>
  36. [Tooltip("If enabled, this lens setting will apply to all three child rigs, "
  37. + "otherwise the child rig lens settings will be used")]
  38. [FormerlySerializedAs("m_UseCommonLensSetting")]
  39. public bool m_CommonLens = true;
  40. /// <summary>Specifies the lens properties of this Virtual Camera.
  41. /// This generally mirrors the Unity Camera's lens settings, and will be used to drive
  42. /// the Unity camera when the vcam is active</summary>
  43. [FormerlySerializedAs("m_LensAttributes")]
  44. [Tooltip("Specifies the lens properties of this Virtual Camera. This generally "
  45. + "mirrors the Unity Camera's lens settings, and will be used to drive the "
  46. + "Unity camera when the vcam is active")]
  47. public LensSettings m_Lens = LensSettings.Default;
  48. /// <summary> Collection of parameters that influence how this virtual camera transitions from
  49. /// other virtual cameras </summary>
  50. public TransitionParams m_Transitions;
  51. /// <summary>Legacy support</summary>
  52. [SerializeField] [HideInInspector]
  53. [FormerlySerializedAs("m_BlendHint")]
  54. [FormerlySerializedAs("m_PositionBlending")] private BlendHint m_LegacyBlendHint;
  55. /// <summary>The Vertical axis. Value is 0..1. Chooses how to blend the child rigs</summary>
  56. [Header("Axis Control")]
  57. [Tooltip("The Vertical axis. Value is 0..1. Chooses how to blend the child rigs")]
  58. [AxisStateProperty]
  59. public AxisState m_YAxis = new AxisState(0, 1, false, true, 2f, 0.2f, 0.1f, "Mouse Y", false);
  60. /// <summary>Controls how automatic recentering of the Y axis is accomplished</summary>
  61. [Tooltip("Controls how automatic recentering of the Y axis is accomplished")]
  62. public AxisState.Recentering m_YAxisRecentering = new AxisState.Recentering(false, 1, 2);
  63. /// <summary>The Horizontal axis. Value is -180...180. This is passed on to
  64. /// the rigs' OrbitalTransposer component</summary>
  65. [Tooltip("The Horizontal axis. Value is -180...180. "
  66. + "This is passed on to the rigs' OrbitalTransposer component")]
  67. [AxisStateProperty]
  68. public AxisState m_XAxis = new AxisState(-180, 180, true, false, 300f, 0.1f, 0.1f, "Mouse X", true);
  69. /// <summary>The definition of Forward. Camera will follow behind</summary>
  70. [OrbitalTransposerHeadingProperty]
  71. [Tooltip("The definition of Forward. Camera will follow behind.")]
  72. public CinemachineOrbitalTransposer.Heading m_Heading
  73. = new CinemachineOrbitalTransposer.Heading(
  74. CinemachineOrbitalTransposer.Heading.HeadingDefinition.TargetForward, 4, 0);
  75. /// <summary>Controls how automatic recentering of the X axis is accomplished</summary>
  76. [Tooltip("Controls how automatic recentering of the X axis is accomplished")]
  77. public AxisState.Recentering m_RecenterToTargetHeading = new AxisState.Recentering(false, 1, 2);
  78. /// <summary>The coordinate space to use when interpreting the offset from the target</summary>
  79. [Header("Orbits")]
  80. [Tooltip("The coordinate space to use when interpreting the offset from the target. "
  81. + "This is also used to set the camera's Up vector, which will be maintained "
  82. + "when aiming the camera.")]
  83. public CinemachineOrbitalTransposer.BindingMode m_BindingMode
  84. = CinemachineOrbitalTransposer.BindingMode.SimpleFollowWithWorldUp;
  85. /// <summary></summary>
  86. [Tooltip("Controls how taut is the line that connects the rigs' orbits, which "
  87. + "determines final placement on the Y axis")]
  88. [Range(0f, 1f)]
  89. [FormerlySerializedAs("m_SplineTension")]
  90. public float m_SplineCurvature = 0.2f;
  91. /// <summary>Defines the height and radius of the Rig orbit</summary>
  92. [Serializable]
  93. public struct Orbit
  94. {
  95. /// <summary>Height relative to target</summary>
  96. public float m_Height;
  97. /// <summary>Radius of orbit</summary>
  98. public float m_Radius;
  99. /// <summary>Constructor with specific values</summary>
  100. /// <param name="h">Orbit height</param>
  101. /// <param name="r">Orbit radius</param>
  102. public Orbit(float h, float r) { m_Height = h; m_Radius = r; }
  103. }
  104. /// <summary>The radius and height of the three orbiting rigs</summary>
  105. [Tooltip("The radius and height of the three orbiting rigs.")]
  106. public Orbit[] m_Orbits = new Orbit[3]
  107. {
  108. // These are the default orbits
  109. new Orbit(4.5f, 1.75f),
  110. new Orbit(2.5f, 3f),
  111. new Orbit(0.4f, 1.3f)
  112. };
  113. // Legacy support
  114. [SerializeField] [HideInInspector] [FormerlySerializedAs("m_HeadingBias")]
  115. private float m_LegacyHeadingBias = float.MaxValue;
  116. bool mUseLegacyRigDefinitions = false;
  117. /// <summary>Enforce bounds for fields, when changed in inspector.</summary>
  118. protected override void OnValidate()
  119. {
  120. base.OnValidate();
  121. // Upgrade after a legacy deserialize
  122. if (m_LegacyHeadingBias != float.MaxValue)
  123. {
  124. m_Heading.m_Bias= m_LegacyHeadingBias;
  125. m_LegacyHeadingBias = float.MaxValue;
  126. int heading = (int)m_Heading.m_Definition;
  127. if (m_RecenterToTargetHeading.LegacyUpgrade(ref heading, ref m_Heading.m_VelocityFilterStrength))
  128. m_Heading.m_Definition = (CinemachineOrbitalTransposer.Heading.HeadingDefinition)heading;
  129. mUseLegacyRigDefinitions = true;
  130. }
  131. if (m_LegacyBlendHint != BlendHint.None)
  132. {
  133. m_Transitions.m_BlendHint = m_LegacyBlendHint;
  134. m_LegacyBlendHint = BlendHint.None;
  135. }
  136. m_YAxis.Validate();
  137. m_XAxis.Validate();
  138. m_RecenterToTargetHeading.Validate();
  139. m_YAxisRecentering.Validate();
  140. m_Lens.Validate();
  141. InvalidateRigCache();
  142. #if UNITY_EDITOR
  143. for (int i = 0; m_Rigs != null && i < m_Rigs.Length; ++i)
  144. if (m_Rigs[i] != null)
  145. CinemachineVirtualCamera.SetFlagsForHiddenChild(m_Rigs[i].gameObject);
  146. #endif
  147. }
  148. /// <summary>Get a child rig</summary>
  149. /// <param name="i">Rig index. Can be 0, 1, or 2</param>
  150. /// <returns>The rig, or null if index is bad.</returns>
  151. public CinemachineVirtualCamera GetRig(int i)
  152. {
  153. return (UpdateRigCache() && i >= 0 && i < 3) ? m_Rigs[i] : null;
  154. }
  155. internal bool RigsAreCreated { get => m_Rigs != null && m_Rigs.Length == 3; }
  156. /// <summary>Names of the 3 child rigs</summary>
  157. public static string[] RigNames { get { return new string[] { "TopRig", "MiddleRig", "BottomRig" }; } }
  158. bool mIsDestroyed = false;
  159. /// <summary>Updates the child rig cache</summary>
  160. protected override void OnEnable()
  161. {
  162. mIsDestroyed = false;
  163. base.OnEnable();
  164. InvalidateRigCache();
  165. UpdateInputAxisProvider();
  166. }
  167. /// <summary>
  168. /// API for the inspector. Internal use only
  169. /// </summary>
  170. public void UpdateInputAxisProvider()
  171. {
  172. m_XAxis.SetInputAxisProvider(0, null);
  173. m_YAxis.SetInputAxisProvider(1, null);
  174. var provider = GetInputAxisProvider();
  175. if (provider != null)
  176. {
  177. m_XAxis.SetInputAxisProvider(0, provider);
  178. m_YAxis.SetInputAxisProvider(1, provider);
  179. }
  180. }
  181. /// <summary>Makes sure that the child rigs get destroyed in an undo-firndly manner.
  182. /// Invalidates the rig cache.</summary>
  183. protected override void OnDestroy()
  184. {
  185. // Make the rigs visible instead of destroying - this is to keep Undo happy
  186. if (m_Rigs != null)
  187. foreach (var rig in m_Rigs)
  188. if (rig != null && rig.gameObject != null)
  189. rig.gameObject.hideFlags
  190. &= ~(HideFlags.HideInHierarchy | HideFlags.HideInInspector);
  191. mIsDestroyed = true;
  192. base.OnDestroy();
  193. }
  194. /// <summary>Invalidates the rig cache</summary>
  195. void OnTransformChildrenChanged()
  196. {
  197. InvalidateRigCache();
  198. }
  199. void Reset()
  200. {
  201. DestroyRigs();
  202. UpdateRigCache();
  203. }
  204. /// <summary>Set this to force the next update to ignore deltaTime and reset itself</summary>
  205. public override bool PreviousStateIsValid
  206. {
  207. get { return base.PreviousStateIsValid; }
  208. set
  209. {
  210. if (value == false)
  211. for (int i = 0; m_Rigs != null && i < m_Rigs.Length; ++i)
  212. if (m_Rigs[i] != null)
  213. m_Rigs[i].PreviousStateIsValid = value;
  214. base.PreviousStateIsValid = value;
  215. }
  216. }
  217. /// <summary>The camera state, which will be a blend of the child rig states</summary>
  218. override public CameraState State { get { return m_State; } }
  219. /// <summary>Get the current LookAt target. Returns parent's LookAt if parent
  220. /// is non-null and no specific LookAt defined for this camera</summary>
  221. override public Transform LookAt
  222. {
  223. get { return ResolveLookAt(m_LookAt); }
  224. set { m_LookAt = value; }
  225. }
  226. /// <summary>Get the current Follow target. Returns parent's Follow if parent
  227. /// is non-null and no specific Follow defined for this camera</summary>
  228. override public Transform Follow
  229. {
  230. get { return ResolveFollow(m_Follow); }
  231. set { m_Follow = value; }
  232. }
  233. /// <summary>Check whether the vcam a live child of this camera.
  234. /// Returns true if the child is currently contributing actively to the camera state.</summary>
  235. /// <param name="vcam">The Virtual Camera to check</param>
  236. /// <param name="dominantChildOnly">If truw, will only return true if this vcam is the dominat live child</param>
  237. /// <returns>True if the vcam is currently actively influencing the state of this vcam</returns>
  238. public override bool IsLiveChild(ICinemachineCamera vcam, bool dominantChildOnly = false)
  239. {
  240. // Do not update the rig cache here or there will be infinite loop at creation time
  241. if (!RigsAreCreated)
  242. return false;
  243. var y = GetYAxisValue();
  244. if (dominantChildOnly)
  245. {
  246. if (vcam == (ICinemachineCamera)m_Rigs[0])
  247. return y > 0.666f;
  248. if (vcam == (ICinemachineCamera)m_Rigs[2])
  249. return y < 0.333;
  250. if (vcam == (ICinemachineCamera)m_Rigs[1])
  251. return y >= 0.333f && y <= 0.666f;
  252. return false;
  253. }
  254. if (vcam == (ICinemachineCamera)m_Rigs[1])
  255. return true;
  256. if (y < 0.5f)
  257. return vcam == (ICinemachineCamera)m_Rigs[2];
  258. return vcam == (ICinemachineCamera)m_Rigs[0];
  259. }
  260. /// <summary>This is called to notify the vcam that a target got warped,
  261. /// so that the vcam can update its internal state to make the camera
  262. /// also warp seamlessy.</summary>
  263. /// <param name="target">The object that was warped</param>
  264. /// <param name="positionDelta">The amount the target's position changed</param>
  265. public override void OnTargetObjectWarped(Transform target, Vector3 positionDelta)
  266. {
  267. UpdateRigCache();
  268. if (RigsAreCreated)
  269. foreach (var vcam in m_Rigs)
  270. vcam.OnTargetObjectWarped(target, positionDelta);
  271. base.OnTargetObjectWarped(target, positionDelta);
  272. }
  273. /// <summary>
  274. /// Force the virtual camera to assume a given position and orientation.
  275. /// Procedural placement then takes over
  276. /// </summary>
  277. /// <param name="pos">Worldspace pposition to take</param>
  278. /// <param name="rot">Worldspace orientation to take</param>
  279. public override void ForceCameraPosition(Vector3 pos, Quaternion rot)
  280. {
  281. var up = m_State.ReferenceUp;
  282. m_YAxis.Value = GetYAxisClosestValue(pos, up);
  283. PreviousStateIsValid = true;
  284. transform.ConservativeSetPositionAndRotation(pos, rot);
  285. m_State.RawPosition = pos;
  286. m_State.RawOrientation = rot;
  287. if (UpdateRigCache())
  288. {
  289. for (int i = 0; i < 3; ++i)
  290. m_Rigs[i].ForceCameraPosition(pos, rot);
  291. if (m_BindingMode != CinemachineTransposer.BindingMode.SimpleFollowWithWorldUp)
  292. m_XAxis.Value = mOrbitals[1].m_XAxis.Value;
  293. PushSettingsToRigs();
  294. InternalUpdateCameraState(up, -1);
  295. }
  296. base.ForceCameraPosition(pos, rot);
  297. }
  298. /// <summary>Internal use only. Called by CinemachineCore at designated update time
  299. /// so the vcam can position itself and track its targets. All 3 child rigs are updated,
  300. /// and a blend calculated, depending on the value of the Y axis.</summary>
  301. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  302. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than 0)</param>
  303. override public void InternalUpdateCameraState(Vector3 worldUp, float deltaTime)
  304. {
  305. UpdateTargetCache();
  306. UpdateRigCache();
  307. if (!RigsAreCreated)
  308. return;
  309. // Update the current state by invoking the component pipeline
  310. m_State = CalculateNewState(worldUp, deltaTime);
  311. ApplyPositionBlendMethod(ref m_State, m_Transitions.m_BlendHint);
  312. // Push the raw position back to the game object's transform, so it
  313. // moves along with the camera. Leave the orientation alone, because it
  314. // screws up camera dragging when there is a LookAt behaviour.
  315. if (Follow != null)
  316. {
  317. Vector3 delta = m_State.RawPosition - transform.position;
  318. #if UNITY_EDITOR
  319. // Avoid dirtying the scene with insignificant diffs
  320. if (!delta.AlmostZero())
  321. #endif
  322. {
  323. transform.position = m_State.RawPosition;
  324. m_Rigs[0].transform.position -= delta;
  325. m_Rigs[1].transform.position -= delta;
  326. m_Rigs[2].transform.position -= delta;
  327. }
  328. }
  329. InvokePostPipelineStageCallback(this, CinemachineCore.Stage.Finalize, ref m_State, deltaTime);
  330. // Set up for next frame
  331. bool activeCam = PreviousStateIsValid && CinemachineCore.Instance.IsLive(this);
  332. if (activeCam && deltaTime >= 0)
  333. {
  334. if (m_YAxis.Update(deltaTime))
  335. m_YAxisRecentering.CancelRecentering();
  336. }
  337. PushSettingsToRigs();
  338. if (m_BindingMode == CinemachineTransposer.BindingMode.SimpleFollowWithWorldUp)
  339. m_XAxis.Value = 0;
  340. PreviousStateIsValid = true;
  341. }
  342. /// <summary>If we are transitioning from another FreeLook, grab the axis values from it.</summary>
  343. /// <param name="fromCam">The camera being deactivated. May be null.</param>
  344. /// <param name="worldUp">Default world Up, set by the CinemachineBrain</param>
  345. /// <param name="deltaTime">Delta time for time-based effects (ignore if less than or equal to 0)</param>
  346. public override void OnTransitionFromCamera(
  347. ICinemachineCamera fromCam, Vector3 worldUp, float deltaTime)
  348. {
  349. base.OnTransitionFromCamera(fromCam, worldUp, deltaTime);
  350. if (!RigsAreCreated)
  351. return;
  352. InvokeOnTransitionInExtensions(fromCam, worldUp, deltaTime);
  353. // m_RecenterToTargetHeading.DoRecentering(ref m_XAxis, -1, 0);
  354. // m_YAxis.m_Recentering.DoRecentering(ref m_YAxis, -1, 0.5f);
  355. // m_RecenterToTargetHeading.CancelRecentering();
  356. // m_YAxis.m_Recentering.CancelRecentering();
  357. if (fromCam != null && m_Transitions.m_InheritPosition
  358. && !CinemachineCore.Instance.IsLiveInBlend(this))
  359. {
  360. var cameraPos = fromCam.State.RawPosition;
  361. // Special handling for FreeLook: get an undamped outgoing position
  362. if (fromCam is CinemachineFreeLook)
  363. {
  364. var flFrom = (fromCam as CinemachineFreeLook);
  365. var orbital = flFrom.mOrbitals != null ? flFrom.mOrbitals[1] : null;
  366. if (orbital != null)
  367. cameraPos = orbital.GetTargetCameraPosition(worldUp);
  368. }
  369. ForceCameraPosition(cameraPos, fromCam.State.FinalOrientation);
  370. }
  371. UpdateCameraState(worldUp, deltaTime);
  372. if (m_Transitions.m_OnCameraLive != null)
  373. m_Transitions.m_OnCameraLive.Invoke(this, fromCam);
  374. }
  375. /// <summary>
  376. /// Returns true, because FreeLook requires input.
  377. /// </summary>
  378. internal override bool RequiresUserInput()
  379. {
  380. return true;
  381. }
  382. float GetYAxisClosestValue(Vector3 cameraPos, Vector3 up)
  383. {
  384. if (Follow != null)
  385. {
  386. // Rotate the camera pos to the back
  387. Quaternion q = Quaternion.FromToRotation(up, Vector3.up);
  388. Vector3 dir = q * (cameraPos - Follow.position);
  389. Vector3 flatDir = dir; flatDir.y = 0;
  390. if (!flatDir.AlmostZero())
  391. {
  392. float angle = UnityVectorExtensions.SignedAngle(flatDir, Vector3.back, Vector3.up);
  393. dir = Quaternion.AngleAxis(angle, Vector3.up) * dir;
  394. }
  395. dir.x = 0;
  396. // We need to find the minimum of the angle of function using steepest descent
  397. return SteepestDescent(dir.normalized * (cameraPos - Follow.position).magnitude);
  398. }
  399. return m_YAxis.Value; // stay conservative
  400. }
  401. float SteepestDescent(Vector3 cameraOffset)
  402. {
  403. const int maxIteration = 10;
  404. const float epsilon = 0.005f;
  405. var x = InitialGuess();
  406. for (var i = 0; i < maxIteration; ++i)
  407. {
  408. var angle = AngleFunction(x);
  409. var slope = SlopeOfAngleFunction(x);
  410. if (Mathf.Abs(slope) < epsilon || Mathf.Abs(angle) < epsilon)
  411. break; // found best
  412. x = Mathf.Clamp01(x - (angle / slope)); // clamping is needed so we don't overshoot
  413. }
  414. return x;
  415. // localFunctions
  416. float AngleFunction(float input)
  417. {
  418. var point = GetLocalPositionForCameraFromInput(input);
  419. return Mathf.Abs(UnityVectorExtensions.SignedAngle(cameraOffset, point, Vector3.right));
  420. }
  421. // approximating derivative using symmetric difference quotient (finite diff)
  422. float SlopeOfAngleFunction(float input)
  423. {
  424. var angleBehind = AngleFunction(input - epsilon);
  425. var angleAfter = AngleFunction(input + epsilon);
  426. return (angleAfter - angleBehind) / (2f * epsilon);
  427. }
  428. float InitialGuess()
  429. {
  430. UpdateCachedSpline();
  431. const float step = 1.0f / 10;
  432. float best = 0.5f;
  433. float bestAngle = AngleFunction(best);
  434. for (int j = 0; j <= 5; ++j)
  435. {
  436. var t = j * step;
  437. ChooseBestAngle(0.5f + t);
  438. ChooseBestAngle(0.5f - t);
  439. void ChooseBestAngle(float referenceAngle)
  440. {
  441. var a = AngleFunction(referenceAngle);
  442. if (a < bestAngle)
  443. {
  444. bestAngle = a;
  445. best = referenceAngle;
  446. }
  447. }
  448. }
  449. return best;
  450. }
  451. }
  452. CameraState m_State = CameraState.Default; // Current state this frame
  453. // Serialized only to implement copy/paste of rigs.
  454. // Note however that this strategy has its limitations: the rigs
  455. // won't be pasted onto a prefab asset outside the scene unless the prefab
  456. // is opened in Prefab edit mode.
  457. [SerializeField][HideInInspector][NoSaveDuringPlay]
  458. CinemachineVirtualCamera[] m_Rigs = new CinemachineVirtualCamera[3];
  459. void InvalidateRigCache() { mOrbitals = null; }
  460. CinemachineOrbitalTransposer[] mOrbitals = null;
  461. CinemachineBlend mBlendA;
  462. CinemachineBlend mBlendB;
  463. /// <summary>
  464. /// Override component pipeline creation.
  465. /// This needs to be done by the editor to support Undo.
  466. /// The override must do exactly the same thing as the CreatePipeline method in this class.
  467. /// </summary>
  468. public static CreateRigDelegate CreateRigOverride;
  469. /// <summary>
  470. /// Override component pipeline creation.
  471. /// This needs to be done by the editor to support Undo.
  472. /// The override must do exactly the same thing as the CreatePipeline method in this class.
  473. /// </summary>
  474. public delegate CinemachineVirtualCamera CreateRigDelegate(
  475. CinemachineFreeLook vcam, string name, CinemachineVirtualCamera copyFrom);
  476. /// <summary>
  477. /// Override component pipeline destruction.
  478. /// This needs to be done by the editor to support Undo.
  479. /// </summary>
  480. public static DestroyRigDelegate DestroyRigOverride;
  481. /// <summary>
  482. /// Override component pipeline destruction.
  483. /// This needs to be done by the editor to support Undo.
  484. /// </summary>
  485. public delegate void DestroyRigDelegate(GameObject rig);
  486. private void DestroyRigs()
  487. {
  488. // First collect rigs because we will destroy as we go
  489. var rigs = new List<CinemachineVirtualCamera>(3);
  490. for (int i = 0; i < RigNames.Length; ++i)
  491. foreach (Transform child in transform)
  492. if (child.gameObject.name == RigNames[i])
  493. rigs.Add(child.GetComponent<CinemachineVirtualCamera>());
  494. foreach (var rig in rigs)
  495. {
  496. if (rig != null)
  497. {
  498. if (DestroyRigOverride != null)
  499. DestroyRigOverride(rig.gameObject);
  500. else
  501. {
  502. rig.DestroyPipeline();
  503. Destroy(rig);
  504. if (!RuntimeUtility.IsPrefab(gameObject))
  505. Destroy(rig.gameObject);
  506. }
  507. }
  508. }
  509. mOrbitals = null;
  510. m_Rigs = null;
  511. }
  512. private CinemachineVirtualCamera[] CreateRigs(CinemachineVirtualCamera[] copyFrom)
  513. {
  514. float[] softCenterDefaultsV = new float[] { 0.5f, 0.55f, 0.6f };
  515. // Invalidate the cache
  516. mOrbitals = null;
  517. m_Rigs = null;
  518. // Create the rig contents
  519. CinemachineVirtualCamera[] newRigs = new CinemachineVirtualCamera[3];
  520. for (int i = 0; i < newRigs.Length; ++i)
  521. {
  522. var src = (copyFrom != null && copyFrom.Length > i) ? copyFrom[i] : null;
  523. if (CreateRigOverride != null)
  524. newRigs[i] = CreateRigOverride(this, RigNames[i], src);
  525. else
  526. {
  527. // Recycle the game object if it exists
  528. GameObject go = null;
  529. foreach (Transform child in transform)
  530. {
  531. if (child.gameObject.name == RigNames[i])
  532. {
  533. go = child.gameObject;
  534. break;
  535. }
  536. }
  537. // Create a new rig with default components
  538. // Note: copyFrom only supported in Editor, not build
  539. if (go == null)
  540. {
  541. if (!RuntimeUtility.IsPrefab(gameObject))
  542. {
  543. go = new GameObject(RigNames[i]);
  544. go.transform.parent = transform;
  545. }
  546. }
  547. if (go == null)
  548. newRigs[i] = null;
  549. else
  550. {
  551. newRigs[i] = go.AddComponent<CinemachineVirtualCamera>();
  552. newRigs[i].AddCinemachineComponent<CinemachineOrbitalTransposer>();
  553. newRigs[i].AddCinemachineComponent<CinemachineComposer>();
  554. }
  555. }
  556. // Set up the defaults
  557. if (newRigs[i] != null)
  558. {
  559. newRigs[i].InvalidateComponentPipeline();
  560. CinemachineOrbitalTransposer orbital = newRigs[i].GetCinemachineComponent<CinemachineOrbitalTransposer>();
  561. if (orbital == null)
  562. orbital = newRigs[i].AddCinemachineComponent<CinemachineOrbitalTransposer>(); // should not happen
  563. if (src == null)
  564. {
  565. // Only set defaults if not copying
  566. orbital.m_YawDamping = 0;
  567. CinemachineComposer composer = newRigs[i].GetCinemachineComponent<CinemachineComposer>();
  568. if (composer != null)
  569. {
  570. composer.m_HorizontalDamping = composer.m_VerticalDamping = 0;
  571. composer.m_ScreenX = 0.5f;
  572. composer.m_ScreenY = softCenterDefaultsV[i];
  573. composer.m_DeadZoneWidth = composer.m_DeadZoneHeight = 0f;
  574. composer.m_SoftZoneWidth = composer.m_SoftZoneHeight = 0.8f;
  575. composer.m_BiasX = composer.m_BiasY = 0;
  576. }
  577. }
  578. }
  579. }
  580. return newRigs;
  581. }
  582. private bool UpdateRigCache()
  583. {
  584. if (mIsDestroyed)
  585. return false;
  586. #if UNITY_EDITOR
  587. // Special condition: Did we just get copy/pasted?
  588. if (m_Rigs != null && m_Rigs.Length == 3
  589. && m_Rigs[0] != null && m_Rigs[0].transform.parent != transform)
  590. {
  591. var copyFrom = m_Rigs;
  592. DestroyRigs();
  593. CreateRigs(copyFrom);
  594. }
  595. #endif
  596. // Early out if we're up to date
  597. if (mOrbitals != null && mOrbitals.Length == 3)
  598. return true;
  599. // Locate existing rigs, and recreate them if any are missing
  600. m_CachedXAxisHeading = 0;
  601. m_Rigs = null;
  602. mOrbitals = null;
  603. var rigs = LocateExistingRigs(false);
  604. if (rigs == null || rigs.Count != 3)
  605. {
  606. DestroyRigs();
  607. CreateRigs(null);
  608. rigs = LocateExistingRigs(true);
  609. }
  610. if (rigs != null && rigs.Count == 3)
  611. m_Rigs = rigs.ToArray();
  612. if (RigsAreCreated)
  613. {
  614. mOrbitals = new CinemachineOrbitalTransposer[m_Rigs.Length];
  615. for (int i = 0; i < m_Rigs.Length; ++i)
  616. mOrbitals[i] = m_Rigs[i].GetCinemachineComponent<CinemachineOrbitalTransposer>();
  617. #if UNITY_EDITOR
  618. foreach (var rig in m_Rigs)
  619. {
  620. // Configure the UI
  621. if (rig == null)
  622. continue;
  623. rig.m_ExcludedPropertiesInInspector = m_CommonLens
  624. ? new string[] { "m_Script", "Header", "Extensions", "m_Priority", "m_Transitions", "m_Follow", "m_StandbyUpdate", "m_Lens" }
  625. : new string[] { "m_Script", "Header", "Extensions", "m_Priority", "m_Transitions", "m_Follow", "m_StandbyUpdate" };
  626. rig.m_LockStageInInspector = new CinemachineCore.Stage[] { CinemachineCore.Stage.Body };
  627. }
  628. #endif
  629. // Create the blend objects
  630. mBlendA = new CinemachineBlend(m_Rigs[1], m_Rigs[0], AnimationCurve.Linear(0, 0, 1, 1), 1, 0);
  631. mBlendB = new CinemachineBlend(m_Rigs[2], m_Rigs[1], AnimationCurve.Linear(0, 0, 1, 1), 1, 0);
  632. return true;
  633. }
  634. return false;
  635. }
  636. private List<CinemachineVirtualCamera> LocateExistingRigs(bool forceOrbital)
  637. {
  638. m_CachedXAxisHeading = m_XAxis.Value;
  639. m_LastHeadingUpdateFrame = -1;
  640. var rigs = new List<CinemachineVirtualCamera>(3);
  641. foreach (Transform child in transform)
  642. {
  643. CinemachineVirtualCamera vcam = child.GetComponent<CinemachineVirtualCamera>();
  644. if (vcam != null)
  645. {
  646. GameObject go = child.gameObject;
  647. for (int i = 0; i < RigNames.Length; ++i)
  648. {
  649. if (go.name != RigNames[i])
  650. continue;
  651. // Must have an orbital transposer or it's no good
  652. var orbital = vcam.GetCinemachineComponent<CinemachineOrbitalTransposer>();
  653. if (orbital == null && forceOrbital)
  654. orbital = vcam.AddCinemachineComponent<CinemachineOrbitalTransposer>();
  655. if (orbital != null)
  656. {
  657. orbital.m_HeadingIsSlave = true;
  658. orbital.HideOffsetInInspector = true;
  659. orbital.m_XAxis.m_InputAxisName = string.Empty;
  660. orbital.HeadingUpdater = UpdateXAxisHeading;
  661. orbital.m_RecenterToTargetHeading.m_enabled = false;
  662. vcam.m_StandbyUpdate = m_StandbyUpdate;
  663. rigs.Add(vcam);
  664. }
  665. }
  666. }
  667. }
  668. return rigs;
  669. }
  670. float m_CachedXAxisHeading;
  671. float m_LastHeadingUpdateFrame;
  672. float UpdateXAxisHeading(CinemachineOrbitalTransposer orbital, float deltaTime, Vector3 up)
  673. {
  674. if (this == null)
  675. return 0; // deleted
  676. // Update the axis only once per frame
  677. if (!PreviousStateIsValid)
  678. deltaTime = -1;
  679. if (m_LastHeadingUpdateFrame != Time.frameCount || deltaTime < 0)
  680. {
  681. m_LastHeadingUpdateFrame = Time.frameCount;
  682. var oldValue = m_XAxis.Value;
  683. m_CachedXAxisHeading = orbital.UpdateHeading(
  684. deltaTime, up,
  685. ref m_XAxis, ref m_RecenterToTargetHeading,
  686. CinemachineCore.Instance.IsLive(this));
  687. // Allow externally-driven values to work in this mode
  688. if (m_BindingMode == CinemachineTransposer.BindingMode.SimpleFollowWithWorldUp)
  689. m_XAxis.Value = oldValue;
  690. }
  691. return m_CachedXAxisHeading;
  692. }
  693. void PushSettingsToRigs()
  694. {
  695. for (int i = 0; i < m_Rigs.Length; ++i)
  696. {
  697. if (m_CommonLens)
  698. m_Rigs[i].m_Lens = m_Lens;
  699. // If we just deserialized from a legacy version,
  700. // pull the orbits and targets from the rigs
  701. if (mUseLegacyRigDefinitions)
  702. {
  703. mUseLegacyRigDefinitions = false;
  704. m_Orbits[i].m_Height = mOrbitals[i].m_FollowOffset.y;
  705. m_Orbits[i].m_Radius = -mOrbitals[i].m_FollowOffset.z;
  706. if (m_Rigs[i].Follow != null)
  707. Follow = m_Rigs[i].Follow;
  708. }
  709. m_Rigs[i].Follow = null;
  710. m_Rigs[i].m_StandbyUpdate = m_StandbyUpdate;
  711. m_Rigs[i].FollowTargetAttachment = FollowTargetAttachment;
  712. m_Rigs[i].LookAtTargetAttachment = LookAtTargetAttachment;
  713. if (!PreviousStateIsValid)
  714. {
  715. m_Rigs[i].PreviousStateIsValid = false;
  716. m_Rigs[i].transform.ConservativeSetPositionAndRotation(transform.position, transform.rotation);
  717. }
  718. #if UNITY_EDITOR
  719. // Hide the rigs from prying eyes
  720. CinemachineVirtualCamera.SetFlagsForHiddenChild(m_Rigs[i].gameObject);
  721. #endif
  722. mOrbitals[i].m_FollowOffset = GetLocalPositionForCameraFromInput(GetYAxisValue());
  723. mOrbitals[i].m_BindingMode = m_BindingMode;
  724. mOrbitals[i].m_Heading = m_Heading;
  725. mOrbitals[i].m_XAxis.Value = m_XAxis.Value;
  726. // Hack to get SimpleFollow with heterogeneous dampings to work
  727. if (m_BindingMode == CinemachineTransposer.BindingMode.SimpleFollowWithWorldUp)
  728. m_Rigs[i].SetStateRawPosition(State.RawPosition);
  729. }
  730. }
  731. private float GetYAxisValue()
  732. {
  733. float range = m_YAxis.m_MaxValue - m_YAxis.m_MinValue;
  734. return (range > UnityVectorExtensions.Epsilon) ? m_YAxis.Value / range : 0.5f;
  735. }
  736. private CameraState CalculateNewState(Vector3 worldUp, float deltaTime)
  737. {
  738. CameraState state = PullStateFromVirtualCamera(worldUp, ref m_Lens);
  739. m_YAxisRecentering.DoRecentering(ref m_YAxis, deltaTime, 0.5f);
  740. // Blend from the appropriate rigs
  741. float t = GetYAxisValue();
  742. if (t > 0.5f)
  743. {
  744. if (mBlendA != null)
  745. {
  746. mBlendA.TimeInBlend = (t - 0.5f) * 2f;
  747. mBlendA.UpdateCameraState(worldUp, deltaTime);
  748. state = mBlendA.State;
  749. }
  750. }
  751. else
  752. {
  753. if (mBlendB != null)
  754. {
  755. mBlendB.TimeInBlend = t * 2f;
  756. mBlendB.UpdateCameraState(worldUp, deltaTime);
  757. state = mBlendB.State;
  758. }
  759. }
  760. return state;
  761. }
  762. /// <summary>
  763. /// Returns the local position of the camera along the spline used to connect the
  764. /// three camera rigs. Does not take into account the current heading of the
  765. /// camera (or its target)
  766. /// </summary>
  767. /// <param name="t">The t-value for the camera on its spline. Internally clamped to
  768. /// the value [0,1]</param>
  769. /// <returns>The local offset (back + up) of the camera WRT its target based on the
  770. /// supplied t-value</returns>
  771. public Vector3 GetLocalPositionForCameraFromInput(float t)
  772. {
  773. if (mOrbitals == null)
  774. return Vector3.zero;
  775. UpdateCachedSpline();
  776. int n = 1;
  777. if (t > 0.5f)
  778. {
  779. t -= 0.5f;
  780. n = 2;
  781. }
  782. return SplineHelpers.Bezier3(
  783. t * 2f, m_CachedKnots[n], m_CachedCtrl1[n], m_CachedCtrl2[n], m_CachedKnots[n+1]);
  784. }
  785. Orbit[] m_CachedOrbits;
  786. float m_CachedTension;
  787. Vector4[] m_CachedKnots;
  788. Vector4[] m_CachedCtrl1;
  789. Vector4[] m_CachedCtrl2;
  790. void UpdateCachedSpline()
  791. {
  792. bool cacheIsValid = (m_CachedOrbits != null && m_CachedOrbits.Length == 3
  793. && m_CachedTension == m_SplineCurvature);
  794. for (int i = 0; i < 3 && cacheIsValid; ++i)
  795. cacheIsValid = (m_CachedOrbits[i].m_Height == m_Orbits[i].m_Height
  796. && m_CachedOrbits[i].m_Radius == m_Orbits[i].m_Radius);
  797. if (!cacheIsValid)
  798. {
  799. float t = m_SplineCurvature;
  800. m_CachedKnots = new Vector4[5];
  801. m_CachedCtrl1 = new Vector4[5];
  802. m_CachedCtrl2 = new Vector4[5];
  803. m_CachedKnots[1] = new Vector4(0, m_Orbits[2].m_Height, -m_Orbits[2].m_Radius, 0);
  804. m_CachedKnots[2] = new Vector4(0, m_Orbits[1].m_Height, -m_Orbits[1].m_Radius, 0);
  805. m_CachedKnots[3] = new Vector4(0, m_Orbits[0].m_Height, -m_Orbits[0].m_Radius, 0);
  806. m_CachedKnots[0] = Vector4.Lerp(m_CachedKnots[1], Vector4.zero, t);
  807. m_CachedKnots[4] = Vector4.Lerp(m_CachedKnots[3], Vector4.zero, t);
  808. SplineHelpers.ComputeSmoothControlPoints(
  809. ref m_CachedKnots, ref m_CachedCtrl1, ref m_CachedCtrl2);
  810. m_CachedOrbits = new Orbit[3];
  811. for (int i = 0; i < 3; ++i)
  812. m_CachedOrbits[i] = m_Orbits[i];
  813. m_CachedTension = m_SplineCurvature;
  814. }
  815. }
  816. // This prevents the sensor size from dirtying the scene in the event of aspect ratio change
  817. internal override void OnBeforeSerialize()
  818. {
  819. if (!m_Lens.IsPhysicalCamera)
  820. m_Lens.SensorSize = Vector2.one;
  821. }
  822. }
  823. }