AreaClock.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. namespace Chronos
  6. {
  7. /// <summary>
  8. /// Determines how objects should behave when progressing within an area clock.
  9. /// </summary>
  10. public enum AreaClockMode
  11. {
  12. /// <summary>
  13. /// Objects that enter the clock are instantly affected by its full time scale, without any smoothing.
  14. /// </summary>
  15. Instant = 0,
  16. /// <summary>
  17. /// Objects that enter the clock are progressively affected by its time scale, depending on a A / B ratio where:
  18. /// <para>A is the distance between center and the object</para>
  19. /// <para>B is the distance between center and the collider's edge in the object's direction</para>
  20. /// </summary>
  21. PointToEdge = 1,
  22. /// <summary>
  23. /// Objects that enter the clock are progressively affected by its time scale, depending on a A / B ratio where:
  24. /// <para>A is the distance between the object's entry point and its current position</para>
  25. /// <para>B is the value of padding</para>
  26. /// </summary>
  27. DistanceFromEntry = 2
  28. }
  29. public interface IAreaClock
  30. {
  31. void Release(Timeline timeline);
  32. void ReleaseAll();
  33. float TimeScale(Timeline timeline);
  34. float timeScale { get; }
  35. AreaClockMode mode { get; }
  36. ClockBlend innerBlend { get; set; }
  37. bool ContainsPoint(Vector3 point);
  38. AnimationCurve curve { get; set; }
  39. }
  40. [HelpURL("http://ludiq.io/chronos/documentation#AreaClock")]
  41. public abstract class AreaClock<TCollider, TVector> : Clock, IAreaClock
  42. {
  43. public AreaClock()
  44. {
  45. within = new Dictionary<Timeline, Vector3>();
  46. outOfBounds = new HashSet<Timeline>();
  47. }
  48. protected override void Awake()
  49. {
  50. base.Awake();
  51. CacheComponents();
  52. }
  53. protected override void OnDisable()
  54. {
  55. ReleaseAll();
  56. base.OnDisable();
  57. }
  58. #region Fields
  59. protected Dictionary<Timeline, Vector3> within;
  60. protected HashSet<Timeline> outOfBounds;
  61. protected new TCollider collider;
  62. #endregion
  63. #region Properties
  64. [SerializeField]
  65. private AreaClockMode _mode;
  66. /// <summary>
  67. /// Determines how objects should behave when progressing within area clock.
  68. /// </summary>
  69. public AreaClockMode mode
  70. {
  71. get { return _mode; }
  72. set { _mode = value; }
  73. }
  74. [SerializeField]
  75. private AnimationCurve _curve = AnimationCurve.EaseInOut(0, 1, 1, 0);
  76. /// <summary>
  77. /// The curve of the area clock. This value is only used for the PointToEdge and DistanceFromEntry modes.
  78. ///
  79. /// <para>
  80. /// A good use for this curve is to dampen an area clock's effect to make it seem more natural. Think of the curve's left being the centermost part of the clock, and right being the edge.
  81. /// </para>
  82. ///
  83. /// <para>
  84. /// The Y-axis of the curve indicates a multiplier of the clock's time scale, from 1 to -1.
  85. /// </para>
  86. ///
  87. /// <para>
  88. /// The X-axis of the curve indicates the object's progress.
  89. /// </para>
  90. ///
  91. /// <para>
  92. /// For PointToEdge,
  93. /// X = 0 is at the center;
  94. /// X = 1 is at the collider's edge.
  95. /// </para>
  96. ///
  97. /// <para>
  98. /// For DistanceFromEntry,
  99. /// X = 1 is at entry, and
  100. /// X = 0 is at a distance of padding or more from entry.
  101. /// </para>
  102. /// </summary>
  103. public AnimationCurve curve
  104. {
  105. get { return _curve; }
  106. set { _curve = value; }
  107. }
  108. [SerializeField]
  109. private TVector _center;
  110. /// <summary>
  111. /// The center of the area clock. This value is only used for the PointToEdge mode.
  112. /// </summary>
  113. public TVector center
  114. {
  115. get { return _center; }
  116. set { _center = value; }
  117. }
  118. [SerializeField]
  119. private float _padding = 0.5f;
  120. /// <summary>
  121. /// The padding of the area clock. This value is only used for the DistanceFromEntry mode.
  122. /// </summary>
  123. public float padding
  124. {
  125. get { return _padding; }
  126. set { _padding = value; }
  127. }
  128. [SerializeField]
  129. private ClockBlend _innerBlend = ClockBlend.Multiplicative;
  130. /// <summary>
  131. /// Determines how the clock combines its time scale with that of the timelines within.
  132. /// </summary>
  133. public ClockBlend innerBlend
  134. {
  135. get { return _innerBlend; }
  136. set { _innerBlend = value; }
  137. }
  138. #endregion
  139. #region Timelines
  140. protected virtual void Capture(Timeline timeline, Vector3 entry)
  141. {
  142. if (timeline == null) throw new ArgumentNullException("timeline");
  143. if (!within.ContainsKey(timeline))
  144. {
  145. within.Add(timeline, entry);
  146. }
  147. if (!timeline.areaClocks.Contains(this))
  148. {
  149. timeline.areaClocks.Add(this);
  150. }
  151. }
  152. /// <summary>
  153. /// Releases the specified timeline from the clock's effects.
  154. /// </summary>
  155. public virtual void Release(Timeline timeline)
  156. {
  157. if (timeline == null) throw new ArgumentNullException("timeline");
  158. if (within.ContainsKey(timeline))
  159. {
  160. within.Remove(timeline);
  161. }
  162. if (timeline.areaClocks.Contains(this))
  163. {
  164. timeline.areaClocks.Remove(this);
  165. }
  166. }
  167. /// <summary>
  168. /// Releases all timelines within the clock.
  169. /// </summary>
  170. public virtual void ReleaseAll()
  171. {
  172. foreach (Timeline timeline in within.Keys.Where(w => w != null))
  173. {
  174. if (timeline.areaClocks.Contains(this))
  175. {
  176. timeline.areaClocks.Remove(this);
  177. }
  178. }
  179. within.Clear();
  180. }
  181. #endregion
  182. #region Time scale
  183. float IAreaClock.TimeScale(Timeline timeline)
  184. {
  185. if (timeline == null) throw new ArgumentNullException("timeline");
  186. if (mode == AreaClockMode.Instant)
  187. {
  188. return timeScale;
  189. }
  190. else if (mode == AreaClockMode.PointToEdge)
  191. {
  192. Vector3 position = timeline.gameObject.transform.position;
  193. return PointToEdgeTimeScale(position);
  194. }
  195. else if (mode == AreaClockMode.DistanceFromEntry)
  196. {
  197. Vector3 position = timeline.gameObject.transform.position;
  198. Vector3 entry = within[timeline];
  199. return DistanceFromEntryTimeScale(entry, position);
  200. }
  201. else
  202. {
  203. throw new ChronosException("Unknown area clock mode.");
  204. }
  205. }
  206. protected virtual float DistanceFromEntryTimeScale(Vector3 entry, Vector3 position)
  207. {
  208. if (padding < 0)
  209. {
  210. throw new ChronosException("Area clock padding must be positive.");
  211. }
  212. entry = transform.TransformPoint(entry); // Get back to global coordinates
  213. Vector3 delta = position - entry;
  214. Vector3 direction = delta.normalized;
  215. float distance = delta.magnitude;
  216. Vector3 innerEdge = entry + padding * direction;
  217. float noEffect = innerBlend == ClockBlend.Multiplicative ? 1 : 0;
  218. if (distance < padding)
  219. {
  220. if (Timekeeper.instance.debug)
  221. {
  222. Debug.DrawLine(entry, innerEdge, Color.cyan);
  223. Debug.DrawLine(entry, position, Color.magenta);
  224. }
  225. return Mathf.Lerp(noEffect, timeScale, curve.Evaluate(1 - (distance / padding)));
  226. }
  227. else
  228. {
  229. if (Timekeeper.instance.debug)
  230. {
  231. Debug.DrawLine(entry, position, Color.magenta);
  232. }
  233. return timeScale;
  234. }
  235. }
  236. protected abstract float PointToEdgeTimeScale(Vector3 position);
  237. // Required as public because of the interface. Never call directly.
  238. // http://stackoverflow.com/questions/795108
  239. public abstract bool ContainsPoint(Vector3 point);
  240. #endregion
  241. #region Components
  242. public virtual void CacheComponents() { }
  243. #endregion
  244. }
  245. }