Clock.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. using System;
  2. using UnityEngine;
  3. namespace Chronos
  4. {
  5. /// <summary>
  6. /// Determines how a clock combines its time scale with that of others.
  7. /// </summary>
  8. public enum ClockBlend
  9. {
  10. /// <summary>
  11. /// The clock's time scale is multiplied with that of others.
  12. /// </summary>
  13. Multiplicative = 0,
  14. /// <summary>
  15. /// The clock's time scale is added to that of others.
  16. /// </summary>
  17. Additive = 1
  18. }
  19. /// <summary>
  20. /// An abstract base component that provides common timing functionality to all types of clocks.
  21. /// </summary>
  22. [HelpURL("http://ludiq.io/chronos/documentation#Clock")]
  23. public abstract class Clock : MonoBehaviour
  24. {
  25. private bool enabledOnce;
  26. protected virtual void Awake() { }
  27. private void Start()
  28. {
  29. OnStartOrReEnable();
  30. }
  31. private void OnEnable()
  32. {
  33. if (enabledOnce)
  34. {
  35. OnStartOrReEnable();
  36. }
  37. else
  38. {
  39. enabledOnce = true;
  40. }
  41. }
  42. protected virtual void OnStartOrReEnable()
  43. {
  44. if (string.IsNullOrEmpty(_parentKey))
  45. {
  46. parent = null;
  47. }
  48. else if (Timekeeper.instance.HasClock(_parentKey))
  49. {
  50. parent = Timekeeper.instance.Clock(_parentKey);
  51. }
  52. else
  53. {
  54. throw new ChronosException(string.Format("Missing parent clock: '{0}'.", _parentKey));
  55. }
  56. startTime = Time.unscaledTime;
  57. if (parent != null)
  58. {
  59. parent.Register(this);
  60. parent.ComputeTimeScale();
  61. }
  62. ComputeTimeScale();
  63. }
  64. protected virtual void Update()
  65. {
  66. if (isLerping)
  67. {
  68. _localTimeScale = Mathf.Lerp(lerpFrom, lerpTo, (unscaledTime - lerpStart) / (lerpEnd - lerpStart));
  69. if (unscaledTime >= lerpEnd)
  70. {
  71. isLerping = false;
  72. }
  73. }
  74. ComputeTimeScale();
  75. float unscaledDeltaTime = Timekeeper.unscaledDeltaTime;
  76. deltaTime = unscaledDeltaTime * timeScale;
  77. fixedDeltaTime = Time.fixedDeltaTime * timeScale;
  78. time += deltaTime;
  79. unscaledTime += unscaledDeltaTime;
  80. }
  81. protected virtual void OnDisable()
  82. {
  83. if (parent != null)
  84. {
  85. parent.Unregister(this);
  86. }
  87. }
  88. #region Fields
  89. protected bool isLerping;
  90. protected float lerpStart;
  91. protected float lerpEnd;
  92. protected float lerpFrom;
  93. protected float lerpTo;
  94. #endregion
  95. #region Properties
  96. [SerializeField]
  97. private float _localTimeScale = 1;
  98. /// <summary>
  99. /// The scale at which the time is passing for the clock. This can be used for slow motion, acceleration, pause or even rewind effects.
  100. /// </summary>
  101. public float localTimeScale
  102. {
  103. get { return _localTimeScale; }
  104. set { _localTimeScale = value; }
  105. }
  106. /// <summary>
  107. /// The computed time scale of the clock. This value takes into consideration all of the clock's parameters (parent, paused, etc.) and multiplies their effect accordingly.
  108. /// </summary>
  109. public float timeScale { get; protected set; }
  110. /// <summary>
  111. /// The time in seconds since the creation of the clock, affected by the time scale. Returns the same value if called multiple times in a single frame.
  112. /// Unlike Time.time, this value will not return Time.fixedTime when called inside MonoBehaviour's FixedUpdate.
  113. /// </summary>
  114. public float time { get; protected set; }
  115. /// <summary>
  116. /// The time in seconds since the creation of the clock, regardless of the time scale. Returns the same value if called multiple times in a single frame.
  117. /// </summary>
  118. public float unscaledTime { get; protected set; }
  119. /// <summary>
  120. /// The time in seconds it took to complete the last frame, multiplied by the time scale. Returns the same value if called multiple times in a single frame.
  121. /// Unlike Time.deltaTime, this value will not return Time.fixedDeltaTime when called inside MonoBehaviour's FixedUpdate.
  122. /// </summary>
  123. public float deltaTime { get; protected set; }
  124. /// <summary>
  125. /// The interval in seconds at which physics and other fixed frame rate updates, multiplied by the time scale.
  126. /// </summary>
  127. public float fixedDeltaTime { get; protected set; }
  128. /// <summary>
  129. /// The unscaled time in seconds between the start of the game and the creation of the clock.
  130. /// </summary>
  131. public float startTime { get; protected set; }
  132. [SerializeField]
  133. private bool _paused;
  134. /// <summary>
  135. /// Determines whether the clock is paused. This toggle is especially useful if you want to pause a clock without having to worry about storing its previous time scale to restore it afterwards.
  136. /// </summary>
  137. public bool paused
  138. {
  139. get { return _paused; }
  140. set { _paused = value; }
  141. }
  142. [SerializeField, GlobalClock]
  143. private string _parentKey;
  144. private GlobalClock _parent;
  145. /// <summary>
  146. /// The parent global clock. The parent clock will multiply its time scale with all of its children, allowing for cascading time effects.
  147. /// </summary>
  148. public GlobalClock parent
  149. {
  150. get { return _parent; }
  151. set
  152. {
  153. if (_parent != null)
  154. {
  155. _parent.Unregister(this);
  156. }
  157. if (value != null)
  158. {
  159. if (value == this)
  160. {
  161. throw new ChronosException("Global clock parent cannot be itself.");
  162. }
  163. _parentKey = value.key;
  164. _parent = value;
  165. _parent.Register(this);
  166. }
  167. else
  168. {
  169. _parentKey = null;
  170. _parent = null;
  171. }
  172. }
  173. }
  174. [SerializeField]
  175. private ClockBlend _parentBlend = ClockBlend.Multiplicative;
  176. /// <summary>
  177. /// Determines how the clock combines its time scale with that of its parent.
  178. /// </summary>
  179. public ClockBlend parentBlend
  180. {
  181. get { return _parentBlend; }
  182. set { _parentBlend = value; }
  183. }
  184. /// <summary>
  185. /// Indicates the state of the clock.
  186. /// </summary>
  187. public TimeState state
  188. {
  189. get { return Timekeeper.GetTimeState(timeScale); }
  190. }
  191. #endregion
  192. /// <summary>
  193. /// The time scale is only computed once per update to improve performance. If you need to ensure it is correct in the same frame, call this method to compute it manually.
  194. /// </summary>
  195. public virtual void ComputeTimeScale()
  196. {
  197. if (paused)
  198. {
  199. timeScale = 0;
  200. }
  201. else if (parent == null)
  202. {
  203. timeScale = localTimeScale;
  204. }
  205. else
  206. {
  207. if (parentBlend == ClockBlend.Multiplicative)
  208. {
  209. timeScale = parent.timeScale * localTimeScale;
  210. }
  211. else // if (combinationMode == ClockCombinationMode.Additive)
  212. {
  213. timeScale = parent.timeScale + localTimeScale;
  214. }
  215. }
  216. }
  217. /// <summary>
  218. /// Changes the local time scale smoothly over the given duration in seconds.
  219. /// </summary>
  220. public void LerpTimeScale(float timeScale, float duration, bool steady = false)
  221. {
  222. if (duration < 0) throw new ArgumentException("Duration must be positive.", "duration");
  223. if (duration == 0)
  224. {
  225. localTimeScale = timeScale;
  226. isLerping = false;
  227. return;
  228. }
  229. float modifier = 1;
  230. if (steady)
  231. {
  232. modifier = Mathf.Abs(localTimeScale - timeScale);
  233. }
  234. if (modifier != 0)
  235. {
  236. lerpFrom = localTimeScale;
  237. lerpStart = unscaledTime;
  238. lerpTo = timeScale;
  239. lerpEnd = lerpStart + (duration * modifier);
  240. isLerping = true;
  241. }
  242. }
  243. }
  244. }