AxisState.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. using UnityEngine;
  2. using System;
  3. using Cinemachine.Utility;
  4. using UnityEngine.Serialization;
  5. namespace Cinemachine
  6. {
  7. /// <summary>
  8. /// Axis state for defining how to react to player input.
  9. /// The settings here control the responsiveness of the axis to player input.
  10. /// </summary>
  11. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  12. [Serializable]
  13. public struct AxisState
  14. {
  15. /// <summary>The current value of the axis</summary>
  16. [NoSaveDuringPlay]
  17. [Tooltip("The current value of the axis.")]
  18. public float Value;
  19. /// <summary>How to interpret the Max Speed setting.</summary>
  20. public enum SpeedMode
  21. {
  22. /// <summary>
  23. /// The Max Speed setting will be interpreted as a maximum axis speed, in units/second
  24. /// </summary>
  25. MaxSpeed,
  26. /// <summary>
  27. /// The Max Speed setting will be interpreted as a direct multiplier on the input value
  28. /// </summary>
  29. InputValueGain
  30. };
  31. /// <summary>How to interpret the Max Speed setting.</summary>
  32. [Tooltip("How to interpret the Max Speed setting: in units/second, or as a "
  33. + "direct input value multiplier")]
  34. public SpeedMode m_SpeedMode;
  35. /// <summary>How fast the axis value can travel. Increasing this number
  36. /// makes the behaviour more responsive to joystick input</summary>
  37. [Tooltip("The maximum speed of this axis in units/second, or the input value "
  38. + "multiplier, depending on the Speed Mode")]
  39. public float m_MaxSpeed;
  40. /// <summary>The amount of time in seconds it takes to accelerate to
  41. /// MaxSpeed with the supplied Axis at its maximum value</summary>
  42. [Tooltip("The amount of time in seconds it takes to accelerate to MaxSpeed "
  43. + "with the supplied Axis at its maximum value")]
  44. public float m_AccelTime;
  45. /// <summary>The amount of time in seconds it takes to decelerate
  46. /// the axis to zero if the supplied axis is in a neutral position</summary>
  47. [Tooltip("The amount of time in seconds it takes to decelerate the axis to "
  48. + "zero if the supplied axis is in a neutral position")]
  49. public float m_DecelTime;
  50. /// <summary>The name of this axis as specified in Unity Input manager.
  51. /// Setting to an empty string will disable the automatic updating of this axis</summary>
  52. [FormerlySerializedAs("m_AxisName")]
  53. [Tooltip("The name of this axis as specified in Unity Input manager. "
  54. + "Setting to an empty string will disable the automatic updating of this axis")]
  55. public string m_InputAxisName;
  56. /// <summary>The value of the input axis. A value of 0 means no input
  57. /// You can drive this directly from a
  58. /// custom input system, or you can set the Axis Name and have the value
  59. /// driven by the internal Input Manager</summary>
  60. [NoSaveDuringPlay]
  61. [Tooltip("The value of the input axis. A value of 0 means no input. "
  62. + "You can drive this directly from a custom input system, or you can set "
  63. + "the Axis Name and have the value driven by the internal Input Manager")]
  64. public float m_InputAxisValue;
  65. /// <summary>If checked, then the raw value of the input axis will be inverted
  66. /// before it is used.</summary>
  67. [FormerlySerializedAs("m_InvertAxis")]
  68. [Tooltip("If checked, then the raw value of the input axis will be inverted "
  69. + "before it is used")]
  70. public bool m_InvertInput;
  71. /// <summary>The minimum value for the axis</summary>
  72. [Tooltip("The minimum value for the axis")]
  73. public float m_MinValue;
  74. /// <summary>The maximum value for the axis</summary>
  75. [Tooltip("The maximum value for the axis")]
  76. public float m_MaxValue;
  77. /// <summary>If checked, then the axis will wrap around at the
  78. /// min/max values, forming a loop</summary>
  79. [Tooltip("If checked, then the axis will wrap around at the min/max values, "
  80. + "forming a loop")]
  81. public bool m_Wrap;
  82. /// <summary>Automatic recentering. Valid only if HasRecentering is true</summary>
  83. [Tooltip("Automatic recentering to at-rest position")]
  84. public Recentering m_Recentering;
  85. float m_CurrentSpeed;
  86. float m_LastUpdateTime;
  87. int m_LastUpdateFrame;
  88. /// <summary>Constructor with specific values</summary>
  89. /// <param name="minValue"></param>
  90. /// <param name="maxValue"></param>
  91. /// <param name="wrap"></param>
  92. /// <param name="rangeLocked"></param>
  93. /// <param name="maxSpeed"></param>
  94. /// <param name="accelTime"></param>
  95. /// <param name="decelTime"></param>
  96. /// <param name="name"></param>
  97. /// <param name="invert"></param>
  98. public AxisState(
  99. float minValue, float maxValue, bool wrap, bool rangeLocked,
  100. float maxSpeed, float accelTime, float decelTime,
  101. string name, bool invert)
  102. {
  103. m_MinValue = minValue;
  104. m_MaxValue = maxValue;
  105. m_Wrap = wrap;
  106. ValueRangeLocked = rangeLocked;
  107. HasRecentering = false;
  108. m_Recentering = new Recentering(false, 1, 2);
  109. m_SpeedMode = SpeedMode.MaxSpeed;
  110. m_MaxSpeed = maxSpeed;
  111. m_AccelTime = accelTime;
  112. m_DecelTime = decelTime;
  113. Value = (minValue + maxValue) / 2;
  114. m_InputAxisName = name;
  115. m_InputAxisValue = 0;
  116. m_InvertInput = invert;
  117. m_CurrentSpeed = 0f;
  118. m_InputAxisProvider = null;
  119. m_InputAxisIndex = 0;
  120. m_LastUpdateTime = 0;
  121. m_LastUpdateFrame = 0;
  122. }
  123. /// <summary>Call from OnValidate: Make sure the fields are sensible</summary>
  124. public void Validate()
  125. {
  126. if (m_SpeedMode == SpeedMode.MaxSpeed)
  127. m_MaxSpeed = Mathf.Max(0, m_MaxSpeed);
  128. m_AccelTime = Mathf.Max(0, m_AccelTime);
  129. m_DecelTime = Mathf.Max(0, m_DecelTime);
  130. m_MaxValue = Mathf.Clamp(m_MaxValue, m_MinValue, m_MaxValue);
  131. }
  132. const float Epsilon = UnityVectorExtensions.Epsilon;
  133. /// <summary>
  134. /// Cancel current input state and reset input to 0
  135. /// </summary>
  136. public void Reset()
  137. {
  138. m_InputAxisValue = 0;
  139. m_CurrentSpeed = 0;
  140. m_LastUpdateTime = 0;
  141. m_LastUpdateFrame = 0;
  142. }
  143. /// <summary>
  144. /// This is an interface to override default querying of Unity's legacy Input system.
  145. /// If a befaviour implementing this interface is attached to a Cinemachine virtual camera that
  146. /// requires input, that interface will be polled for input instead of the standard Input system.
  147. /// </summary>
  148. public interface IInputAxisProvider
  149. {
  150. /// <summary>Get the value of the input axis</summary>
  151. /// <param name="axis">Which axis to query: 0, 1, or 2. These represent, respectively, the X, Y, and Z axes</param>
  152. /// <returns>The input value of the axis queried</returns>
  153. float GetAxisValue(int axis);
  154. }
  155. IInputAxisProvider m_InputAxisProvider;
  156. int m_InputAxisIndex;
  157. /// <summary>
  158. /// Set an input provider for this axis. If an input provider is set, the
  159. /// provider will be queried when user input is needed, and the Input Axis Name
  160. /// field will be ignored. If no provider is set, then the legacy Input system
  161. /// will be queried, using the Input Axis Name.
  162. /// </summary>
  163. /// <param name="axis">Which axis will be queried for input</param>
  164. /// <param name="provider">The input provider</param>
  165. public void SetInputAxisProvider(int axis, IInputAxisProvider provider)
  166. {
  167. m_InputAxisIndex = axis;
  168. m_InputAxisProvider = provider;
  169. }
  170. /// <summary>Returns true if this axis has an InputAxisProvider, in which case
  171. /// we ignore the input axis name</summary>
  172. public bool HasInputProvider { get => m_InputAxisProvider != null; }
  173. /// <summary>
  174. /// Updates the state of this axis based on the Input axis defined
  175. /// by AxisState.m_AxisName
  176. /// </summary>
  177. /// <param name="deltaTime">Delta time in seconds</param>
  178. /// <returns>Returns <b>true</b> if this axis's input was non-zero this Update,
  179. /// <b>false</b> otherwise</returns>
  180. public bool Update(float deltaTime)
  181. {
  182. // Update only once per frame
  183. if (Time.frameCount == m_LastUpdateFrame)
  184. return false;
  185. m_LastUpdateFrame = Time.frameCount;
  186. // Cheating: we want the render frame time, not the fixed frame time
  187. if (deltaTime > 0 && m_LastUpdateTime != 0)
  188. deltaTime = Time.realtimeSinceStartup - m_LastUpdateTime;
  189. m_LastUpdateTime = Time.realtimeSinceStartup;
  190. if (m_InputAxisProvider != null)
  191. m_InputAxisValue = m_InputAxisProvider.GetAxisValue(m_InputAxisIndex);
  192. else if (!string.IsNullOrEmpty(m_InputAxisName))
  193. {
  194. try { m_InputAxisValue = CinemachineCore.GetInputAxis(m_InputAxisName); }
  195. catch (ArgumentException e) { Debug.LogError(e.ToString()); }
  196. }
  197. float input = m_InputAxisValue;
  198. if (m_InvertInput)
  199. input *= -1f;
  200. if (m_SpeedMode == SpeedMode.MaxSpeed)
  201. return MaxSpeedUpdate(input, deltaTime); // legacy mode
  202. // Direct mode update: maxSpeed interpreted as multiplier
  203. input *= m_MaxSpeed; // apply gain
  204. if (deltaTime < 0)
  205. m_CurrentSpeed = 0;
  206. else if (deltaTime > 0.0001f)
  207. {
  208. float dampTime = Mathf.Abs(input) < Mathf.Abs(m_CurrentSpeed) ? m_DecelTime : m_AccelTime;
  209. m_CurrentSpeed += Damper.Damp(input - m_CurrentSpeed, dampTime, deltaTime);
  210. // Decelerate to the end points of the range if not wrapping
  211. float range = m_MaxValue - m_MinValue;
  212. if (!m_Wrap && m_DecelTime > Epsilon && range > Epsilon)
  213. {
  214. float v0 = ClampValue(Value);
  215. float v = ClampValue(v0 + m_CurrentSpeed * deltaTime);
  216. float d = (m_CurrentSpeed > 0) ? m_MaxValue - v : v - m_MinValue;
  217. if (d < (0.1f * range) && Mathf.Abs(m_CurrentSpeed) > Epsilon)
  218. m_CurrentSpeed = Damper.Damp(v - v0, m_DecelTime, deltaTime) / deltaTime;
  219. }
  220. input = m_CurrentSpeed * deltaTime;
  221. }
  222. Value = ClampValue(Value + m_CurrentSpeed);
  223. return Mathf.Abs(input) > Epsilon;
  224. }
  225. float ClampValue(float v)
  226. {
  227. float r = m_MaxValue - m_MinValue;
  228. if (m_Wrap && r > Epsilon)
  229. {
  230. v = (v - m_MinValue) % r;
  231. v += m_MinValue + ((v < 0) ? r : 0);
  232. }
  233. return Mathf.Clamp(v, m_MinValue, m_MaxValue);
  234. }
  235. bool MaxSpeedUpdate(float input, float deltaTime)
  236. {
  237. if (m_MaxSpeed > Epsilon)
  238. {
  239. float targetSpeed = input * m_MaxSpeed;
  240. if (Mathf.Abs(targetSpeed) < Epsilon
  241. || (Mathf.Sign(m_CurrentSpeed) == Mathf.Sign(targetSpeed)
  242. && Mathf.Abs(targetSpeed) < Mathf.Abs(m_CurrentSpeed)))
  243. {
  244. // Need to decelerate
  245. float a = Mathf.Abs(targetSpeed - m_CurrentSpeed) / Mathf.Max(Epsilon, m_DecelTime);
  246. float delta = Mathf.Min(a * deltaTime, Mathf.Abs(m_CurrentSpeed));
  247. m_CurrentSpeed -= Mathf.Sign(m_CurrentSpeed) * delta;
  248. }
  249. else
  250. {
  251. // Accelerate to the target speed
  252. float a = Mathf.Abs(targetSpeed - m_CurrentSpeed) / Mathf.Max(Epsilon, m_AccelTime);
  253. m_CurrentSpeed += Mathf.Sign(targetSpeed) * a * deltaTime;
  254. if (Mathf.Sign(m_CurrentSpeed) == Mathf.Sign(targetSpeed)
  255. && Mathf.Abs(m_CurrentSpeed) > Mathf.Abs(targetSpeed))
  256. {
  257. m_CurrentSpeed = targetSpeed;
  258. }
  259. }
  260. }
  261. // Clamp our max speeds so we don't go crazy
  262. float maxSpeed = GetMaxSpeed();
  263. m_CurrentSpeed = Mathf.Clamp(m_CurrentSpeed, -maxSpeed, maxSpeed);
  264. if (Mathf.Abs(m_CurrentSpeed) < Epsilon)
  265. m_CurrentSpeed = 0;
  266. Value += m_CurrentSpeed * deltaTime;
  267. bool isOutOfRange = (Value > m_MaxValue) || (Value < m_MinValue);
  268. if (isOutOfRange)
  269. {
  270. if (m_Wrap)
  271. {
  272. if (Value > m_MaxValue)
  273. Value = m_MinValue + (Value - m_MaxValue);
  274. else
  275. Value = m_MaxValue + (Value - m_MinValue);
  276. }
  277. else
  278. {
  279. Value = Mathf.Clamp(Value, m_MinValue, m_MaxValue);
  280. m_CurrentSpeed = 0f;
  281. }
  282. }
  283. return Mathf.Abs(input) > Epsilon;
  284. }
  285. // MaxSpeed may be limited as we approach the range ends, in order
  286. // to prevent a hard bump
  287. float GetMaxSpeed()
  288. {
  289. float range = m_MaxValue - m_MinValue;
  290. if (!m_Wrap && range > 0)
  291. {
  292. float threshold = range / 10f;
  293. if (m_CurrentSpeed > 0 && (m_MaxValue - Value) < threshold)
  294. {
  295. float t = (m_MaxValue - Value) / threshold;
  296. return Mathf.Lerp(0, m_MaxSpeed, t);
  297. }
  298. else if (m_CurrentSpeed < 0 && (Value - m_MinValue) < threshold)
  299. {
  300. float t = (Value - m_MinValue) / threshold;
  301. return Mathf.Lerp(0, m_MaxSpeed, t);
  302. }
  303. }
  304. return m_MaxSpeed;
  305. }
  306. /// <summary>Value range is locked, i.e. not adjustable by the user (used by editor)</summary>
  307. public bool ValueRangeLocked { get; set; }
  308. /// <summary>True if the Recentering member is valid (bcak-compatibility support:
  309. /// old versions had recentering in a separate structure)</summary>
  310. public bool HasRecentering { get; set; }
  311. /// <summary>Helper for automatic axis recentering</summary>
  312. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  313. [Serializable]
  314. public struct Recentering
  315. {
  316. /// <summary>If checked, will enable automatic recentering of the
  317. /// axis. If FALSE, recenting is disabled.</summary>
  318. [Tooltip("If checked, will enable automatic recentering of the axis. If unchecked, recenting is disabled.")]
  319. public bool m_enabled;
  320. /// <summary>If no input has been detected, the camera will wait
  321. /// this long in seconds before moving its heading to the default heading.</summary>
  322. [Tooltip("If no user input has been detected on the axis, the axis will wait this long in seconds before recentering.")]
  323. public float m_WaitTime;
  324. /// <summary>How long it takes to reach destination once recentering has started</summary>
  325. [Tooltip("How long it takes to reach destination once recentering has started.")]
  326. public float m_RecenteringTime;
  327. float m_LastUpdateTime;
  328. /// <summary>Constructor with specific field values</summary>
  329. /// <param name="enabled"></param>
  330. /// <param name="waitTime"></param>
  331. /// <param name="recenteringTime"></param>
  332. public Recentering(bool enabled, float waitTime, float recenteringTime)
  333. {
  334. m_enabled = enabled;
  335. m_WaitTime = waitTime;
  336. m_RecenteringTime = recenteringTime;
  337. mLastAxisInputTime = 0;
  338. mRecenteringVelocity = 0;
  339. m_LegacyHeadingDefinition = m_LegacyVelocityFilterStrength = -1;
  340. m_LastUpdateTime = 0;
  341. }
  342. /// <summary>Call this from OnValidate()</summary>
  343. public void Validate()
  344. {
  345. m_WaitTime = Mathf.Max(0, m_WaitTime);
  346. m_RecenteringTime = Mathf.Max(0, m_RecenteringTime);
  347. }
  348. // Internal state
  349. float mLastAxisInputTime;
  350. float mRecenteringVelocity;
  351. /// <summary>
  352. /// Copy Recentering state from another Recentering component.
  353. /// </summary>
  354. /// <param name="other"></param>
  355. public void CopyStateFrom(ref Recentering other)
  356. {
  357. if (mLastAxisInputTime != other.mLastAxisInputTime)
  358. other.mRecenteringVelocity = 0;
  359. mLastAxisInputTime = other.mLastAxisInputTime;
  360. }
  361. /// <summary>Cancel any recenetering in progress.</summary>
  362. public void CancelRecentering()
  363. {
  364. mLastAxisInputTime = Time.realtimeSinceStartup;
  365. mRecenteringVelocity = 0;
  366. }
  367. /// <summary>Skip the wait time and start recentering now (only if enabled).</summary>
  368. public void RecenterNow() => mLastAxisInputTime = -1;
  369. /// <summary>Bring the axis back to the centered state (only if enabled).</summary>
  370. /// <param name="axis">The axis to recenter</param>
  371. /// <param name="deltaTime">Current effective deltaTime</param>
  372. /// <param name="recenterTarget">The value that is considered to be centered</param>
  373. public void DoRecentering(ref AxisState axis, float deltaTime, float recenterTarget)
  374. {
  375. // Cheating: we want the render frame time, not the fixed frame time
  376. if (deltaTime > 0)
  377. deltaTime = Time.realtimeSinceStartup - m_LastUpdateTime;
  378. m_LastUpdateTime = Time.realtimeSinceStartup;
  379. if (!m_enabled && deltaTime >= 0)
  380. return;
  381. recenterTarget = axis.ClampValue(recenterTarget);
  382. if (deltaTime < 0)
  383. {
  384. CancelRecentering();
  385. if (m_enabled)
  386. axis.Value = recenterTarget;
  387. return;
  388. }
  389. float v = axis.ClampValue(axis.Value);
  390. float delta = recenterTarget - v;
  391. if (delta == 0)
  392. return;
  393. // Time to start recentering?
  394. if (mLastAxisInputTime >= 0 && Time.realtimeSinceStartup < (mLastAxisInputTime + m_WaitTime))
  395. return; // nope
  396. // Determine the direction
  397. float r = axis.m_MaxValue - axis.m_MinValue;
  398. if (axis.m_Wrap && Mathf.Abs(delta) > r * 0.5f)
  399. v += Mathf.Sign(recenterTarget - v) * r;
  400. // Damp our way there
  401. if (m_RecenteringTime < 0.001f || Mathf.Abs(v - recenterTarget) < 0.001f)
  402. v = recenterTarget;
  403. else
  404. v = Mathf.SmoothDamp(
  405. v, recenterTarget, ref mRecenteringVelocity,
  406. m_RecenteringTime, 9999, deltaTime);
  407. axis.Value = axis.ClampValue(v);
  408. }
  409. // Legacy support
  410. [SerializeField] [HideInInspector] [FormerlySerializedAs("m_HeadingDefinition")]
  411. int m_LegacyHeadingDefinition;
  412. [SerializeField] [HideInInspector] [FormerlySerializedAs("m_VelocityFilterStrength")]
  413. int m_LegacyVelocityFilterStrength;
  414. internal bool LegacyUpgrade(ref int heading, ref int velocityFilter)
  415. {
  416. if (m_LegacyHeadingDefinition != -1 && m_LegacyVelocityFilterStrength != -1)
  417. {
  418. heading = m_LegacyHeadingDefinition;
  419. velocityFilter = m_LegacyVelocityFilterStrength;
  420. m_LegacyHeadingDefinition = m_LegacyVelocityFilterStrength = -1;
  421. return true;
  422. }
  423. return false;
  424. }
  425. }
  426. }
  427. }