AxisState.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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. private float m_CurrentSpeed;
  86. private float m_LastUpdateTime;
  87. private 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 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' 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 (CinemachineCore.UniformDeltaTimeOverride >= 0)
  188. deltaTime = CinemachineCore.UniformDeltaTimeOverride;
  189. else if (deltaTime >= 0 && m_LastUpdateTime != 0)
  190. deltaTime = Time.time - m_LastUpdateTime;
  191. m_LastUpdateTime = Time.time;
  192. if (m_InputAxisProvider != null)
  193. m_InputAxisValue = m_InputAxisProvider.GetAxisValue(m_InputAxisIndex);
  194. else if (!string.IsNullOrEmpty(m_InputAxisName))
  195. {
  196. try { m_InputAxisValue = CinemachineCore.GetInputAxis(m_InputAxisName); }
  197. catch (ArgumentException e) { Debug.LogError(e.ToString()); }
  198. }
  199. float input = m_InputAxisValue;
  200. if (m_InvertInput)
  201. input *= -1f;
  202. if (m_SpeedMode == SpeedMode.MaxSpeed)
  203. return MaxSpeedUpdate(input, deltaTime); // legacy mode
  204. // Direct mode update: maxSpeed interpreted as multiplier
  205. input *= m_MaxSpeed;
  206. if (deltaTime < Epsilon)
  207. m_CurrentSpeed = 0;
  208. else
  209. {
  210. float speed = input / deltaTime;
  211. float dampTime = Mathf.Abs(speed) < Mathf.Abs(m_CurrentSpeed) ? m_DecelTime : m_AccelTime;
  212. speed = m_CurrentSpeed + Damper.Damp(speed - m_CurrentSpeed, dampTime, deltaTime);
  213. m_CurrentSpeed = speed;
  214. // Decelerate to the end points of the range if not wrapping
  215. float range = m_MaxValue - m_MinValue;
  216. if (!m_Wrap && m_DecelTime > Epsilon && range > Epsilon)
  217. {
  218. float v0 = ClampValue(Value);
  219. float v = ClampValue(v0 + speed * deltaTime);
  220. float d = (speed > 0) ? m_MaxValue - v : v - m_MinValue;
  221. if (d < (0.1f * range) && Mathf.Abs(speed) > Epsilon)
  222. speed = Damper.Damp(v - v0, m_DecelTime, deltaTime) / deltaTime;
  223. }
  224. input = speed * deltaTime;
  225. }
  226. Value = ClampValue(Value + input);
  227. return Mathf.Abs(input) > Epsilon;
  228. }
  229. float ClampValue(float v)
  230. {
  231. float r = m_MaxValue - m_MinValue;
  232. if (m_Wrap && r > Epsilon)
  233. {
  234. v = (v - m_MinValue) % r;
  235. v += m_MinValue + ((v < 0) ? r : 0);
  236. }
  237. return Mathf.Clamp(v, m_MinValue, m_MaxValue);
  238. }
  239. bool MaxSpeedUpdate(float input, float deltaTime)
  240. {
  241. if (m_MaxSpeed > Epsilon)
  242. {
  243. float targetSpeed = input * m_MaxSpeed;
  244. if (Mathf.Abs(targetSpeed) < Epsilon
  245. || (Mathf.Sign(m_CurrentSpeed) == Mathf.Sign(targetSpeed)
  246. && Mathf.Abs(targetSpeed) < Mathf.Abs(m_CurrentSpeed)))
  247. {
  248. // Need to decelerate
  249. float a = Mathf.Abs(targetSpeed - m_CurrentSpeed) / Mathf.Max(Epsilon, m_DecelTime);
  250. float delta = Mathf.Min(a * deltaTime, Mathf.Abs(m_CurrentSpeed));
  251. m_CurrentSpeed -= Mathf.Sign(m_CurrentSpeed) * delta;
  252. }
  253. else
  254. {
  255. // Accelerate to the target speed
  256. float a = Mathf.Abs(targetSpeed - m_CurrentSpeed) / Mathf.Max(Epsilon, m_AccelTime);
  257. m_CurrentSpeed += Mathf.Sign(targetSpeed) * a * deltaTime;
  258. if (Mathf.Sign(m_CurrentSpeed) == Mathf.Sign(targetSpeed)
  259. && Mathf.Abs(m_CurrentSpeed) > Mathf.Abs(targetSpeed))
  260. {
  261. m_CurrentSpeed = targetSpeed;
  262. }
  263. }
  264. }
  265. // Clamp our max speeds so we don't go crazy
  266. float maxSpeed = GetMaxSpeed();
  267. m_CurrentSpeed = Mathf.Clamp(m_CurrentSpeed, -maxSpeed, maxSpeed);
  268. Value += m_CurrentSpeed * deltaTime;
  269. bool isOutOfRange = (Value > m_MaxValue) || (Value < m_MinValue);
  270. if (isOutOfRange)
  271. {
  272. if (m_Wrap)
  273. {
  274. if (Value > m_MaxValue)
  275. Value = m_MinValue + (Value - m_MaxValue);
  276. else
  277. Value = m_MaxValue + (Value - m_MinValue);
  278. }
  279. else
  280. {
  281. Value = Mathf.Clamp(Value, m_MinValue, m_MaxValue);
  282. m_CurrentSpeed = 0f;
  283. }
  284. }
  285. return Mathf.Abs(input) > Epsilon;
  286. }
  287. // MaxSpeed may be limited as we approach the range ends, in order
  288. // to prevent a hard bump
  289. private float GetMaxSpeed()
  290. {
  291. float range = m_MaxValue - m_MinValue;
  292. if (!m_Wrap && range > 0)
  293. {
  294. float threshold = range / 10f;
  295. if (m_CurrentSpeed > 0 && (m_MaxValue - Value) < threshold)
  296. {
  297. float t = (m_MaxValue - Value) / threshold;
  298. return Mathf.Lerp(0, m_MaxSpeed, t);
  299. }
  300. else if (m_CurrentSpeed < 0 && (Value - m_MinValue) < threshold)
  301. {
  302. float t = (Value - m_MinValue) / threshold;
  303. return Mathf.Lerp(0, m_MaxSpeed, t);
  304. }
  305. }
  306. return m_MaxSpeed;
  307. }
  308. /// <summary>Value range is locked, i.e. not adjustable by the user (used by editor)</summary>
  309. public bool ValueRangeLocked { get; set; }
  310. /// <summary>True if the Recentering member is valid (bcak-compatibility support:
  311. /// old versions had recentering in a separate structure)</summary>
  312. public bool HasRecentering { get; set; }
  313. /// <summary>Helper for automatic axis recentering</summary>
  314. [DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
  315. [Serializable]
  316. public struct Recentering
  317. {
  318. /// <summary>If checked, will enable automatic recentering of the
  319. /// axis. If FALSE, recenting is disabled.</summary>
  320. [Tooltip("If checked, will enable automatic recentering of the axis. If unchecked, recenting is disabled.")]
  321. public bool m_enabled;
  322. /// <summary>If no input has been detected, the camera will wait
  323. /// this long in seconds before moving its heading to the default heading.</summary>
  324. [Tooltip("If no user input has been detected on the axis, the axis will wait this long in seconds before recentering.")]
  325. public float m_WaitTime;
  326. /// <summary>How long it takes to reach destination once recentering has started</summary>
  327. [Tooltip("How long it takes to reach destination once recentering has started.")]
  328. public float m_RecenteringTime;
  329. /// <summary>Constructor with specific field values</summary>
  330. /// <param name="enabled"></param>
  331. /// <param name="waitTime"></param>
  332. /// <param name="recenteringTime"></param>
  333. public Recentering(bool enabled, float waitTime, float recenteringTime)
  334. {
  335. m_enabled = enabled;
  336. m_WaitTime = waitTime;
  337. m_RecenteringTime = recenteringTime;
  338. mLastAxisInputTime = 0;
  339. mRecenteringVelocity = 0;
  340. m_LegacyHeadingDefinition = m_LegacyVelocityFilterStrength = -1;
  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 = CinemachineCore.CurrentTime;
  365. mRecenteringVelocity = 0;
  366. }
  367. /// <summary>Skip the wait time and start recentering now (only if enabled).</summary>
  368. public void RecenterNow()
  369. {
  370. mLastAxisInputTime = 0;
  371. }
  372. /// <summary>Bring the axis back to the centered state (only if enabled).</summary>
  373. /// <param name="axis">The axis to recenter</param>
  374. /// <param name="deltaTime">Current effective deltaTime</param>
  375. /// <param name="recenterTarget">The value that is considered to be centered</param>
  376. public void DoRecentering(ref AxisState axis, float deltaTime, float recenterTarget)
  377. {
  378. if (!m_enabled && deltaTime >= 0)
  379. return;
  380. recenterTarget = axis.ClampValue(recenterTarget);
  381. if (deltaTime < 0)
  382. {
  383. CancelRecentering();
  384. if (m_enabled)
  385. axis.Value = recenterTarget;
  386. return;
  387. }
  388. float v = axis.ClampValue(axis.Value);
  389. float delta = recenterTarget - v;
  390. if (delta == 0)
  391. return;
  392. if (CinemachineCore.CurrentTime < (mLastAxisInputTime + m_WaitTime))
  393. return;
  394. // Determine the direction
  395. float r = axis.m_MaxValue - axis.m_MinValue;
  396. if (axis.m_Wrap && Mathf.Abs(delta) > r * 0.5f)
  397. v += Mathf.Sign(recenterTarget - v) * r;
  398. // Damp our way there
  399. if (m_RecenteringTime < 0.001f)
  400. v = recenterTarget;
  401. else
  402. v = Mathf.SmoothDamp(
  403. v, recenterTarget, ref mRecenteringVelocity,
  404. m_RecenteringTime, 9999, deltaTime);
  405. axis.Value = axis.ClampValue(v);
  406. }
  407. // Legacy support
  408. [SerializeField] [HideInInspector] [FormerlySerializedAs("m_HeadingDefinition")] private int m_LegacyHeadingDefinition;
  409. [SerializeField] [HideInInspector] [FormerlySerializedAs("m_VelocityFilterStrength")] private int m_LegacyVelocityFilterStrength;
  410. internal bool LegacyUpgrade(ref int heading, ref int velocityFilter)
  411. {
  412. if (m_LegacyHeadingDefinition != -1 && m_LegacyVelocityFilterStrength != -1)
  413. {
  414. heading = m_LegacyHeadingDefinition;
  415. velocityFilter = m_LegacyVelocityFilterStrength;
  416. m_LegacyHeadingDefinition = m_LegacyVelocityFilterStrength = -1;
  417. return true;
  418. }
  419. return false;
  420. }
  421. }
  422. }
  423. }