AnimancerEvent.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
  2. using System;
  3. using System.Text;
  4. using UnityEngine;
  5. using Object = UnityEngine.Object;
  6. namespace Animancer
  7. {
  8. /// <summary>
  9. /// A <see cref="callback"/> delegate paired with a <see cref="normalizedTime"/> to determine when to invoke it.
  10. /// </summary>
  11. /// <remarks>
  12. /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Events</see>
  13. /// </remarks>
  14. /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerEvent
  15. ///
  16. public partial struct AnimancerEvent : IEquatable<AnimancerEvent>
  17. {
  18. /************************************************************************************************************************/
  19. #region Event
  20. /************************************************************************************************************************/
  21. /// <summary>The <see cref="AnimancerState.NormalizedTime"/> at which to invoke the <see cref="callback"/>.</summary>
  22. public float normalizedTime;
  23. /// <summary>The delegate to invoke when the <see cref="normalizedTime"/> passes.</summary>
  24. public Action callback;
  25. /************************************************************************************************************************/
  26. /// <summary>The largest possible float value less than 1.</summary>
  27. /// <remarks>
  28. /// This value is useful for placing events at the end of a looping animation since they do not allow the
  29. /// <see cref="normalizedTime"/> to be greater than or equal to 1.
  30. /// </remarks>
  31. public const float AlmostOne = 0.99999994f;
  32. /************************************************************************************************************************/
  33. /// <summary>Does nothing.</summary>
  34. /// <remarks>This delegate is used for events which would otherwise have a <c>null</c> <see cref="callback"/>.</remarks>
  35. public static readonly Action DummyCallback = Dummy;
  36. /// <summary>Does nothing.</summary>
  37. /// <remarks>Used by <see cref="DummyCallback"/>.</remarks>
  38. private static void Dummy() { }
  39. /// <summary>Is the `callback` <c>null</c> or the <see cref="DummyCallback"/>?</summary>
  40. public static bool IsNullOrDummy(Action callback) => callback == null || callback == DummyCallback;
  41. /************************************************************************************************************************/
  42. /// <summary>Creates a new <see cref="AnimancerEvent"/>.</summary>
  43. public AnimancerEvent(float normalizedTime, Action callback)
  44. {
  45. this.normalizedTime = normalizedTime;
  46. this.callback = callback;
  47. }
  48. /************************************************************************************************************************/
  49. /// <summary>Returns a string describing the details of this event.</summary>
  50. public override string ToString()
  51. {
  52. var text = ObjectPool.AcquireStringBuilder();
  53. text.Append($"{nameof(AnimancerEvent)}(");
  54. AppendDetails(text);
  55. text.Append(')');
  56. return text.ReleaseToString();
  57. }
  58. /************************************************************************************************************************/
  59. /// <summary>Appends the details of this event to the `text`.</summary>
  60. public void AppendDetails(StringBuilder text)
  61. {
  62. text.Append("NormalizedTime: ")
  63. .Append(normalizedTime)
  64. .Append(", Callback: ");
  65. if (callback == null)
  66. {
  67. text.Append("null");
  68. }
  69. else if (callback.Target == null)
  70. {
  71. text.Append(callback.Method.DeclaringType.FullName)
  72. .Append('.')
  73. .Append(callback.Method.Name);
  74. }
  75. else
  76. {
  77. text.Append("(Target: '")
  78. .Append(callback.Target)
  79. .Append("', Method: ")
  80. .Append(callback.Method.DeclaringType.FullName)
  81. .Append('.')
  82. .Append(callback.Method.Name)
  83. .Append(')');
  84. }
  85. }
  86. /************************************************************************************************************************/
  87. #endregion
  88. /************************************************************************************************************************/
  89. #region Invocation
  90. /************************************************************************************************************************/
  91. /// <summary>The <see cref="AnimancerState"/> currently triggering an event via <see cref="Invoke"/>.</summary>
  92. public static AnimancerState CurrentState => _CurrentState;
  93. private static AnimancerState _CurrentState;
  94. /************************************************************************************************************************/
  95. /// <summary>The <see cref="AnimancerEvent"/> currently being triggered via <see cref="Invoke"/>.</summary>
  96. public static ref readonly AnimancerEvent CurrentEvent => ref _CurrentEvent;
  97. private static AnimancerEvent _CurrentEvent;
  98. /************************************************************************************************************************/
  99. /// <summary>
  100. /// Sets the <see cref="CurrentState"/> and <see cref="CurrentEvent"/> then invokes the <see cref="callback"/>.
  101. /// </summary>
  102. /// <remarks>This method catches and logs any exception thrown by the <see cref="callback"/>.</remarks>
  103. /// <exception cref="NullReferenceException">The <see cref="callback"/> is null.</exception>
  104. public void Invoke(AnimancerState state)
  105. {
  106. #if UNITY_ASSERTIONS
  107. if (IsNullOrDummy(callback))
  108. OptionalWarning.UselessEvent.Log(
  109. $"An {nameof(AnimancerEvent)} that does nothing was invoked." +
  110. " Most likely it was not configured correctly." +
  111. " Unused events should be removed to avoid wasting performance checking and invoking them.",
  112. state?.Root?.Component);
  113. #endif
  114. var previousState = _CurrentState;
  115. var previousEvent = _CurrentEvent;
  116. _CurrentState = state;
  117. _CurrentEvent = this;
  118. try
  119. {
  120. callback();
  121. }
  122. catch (Exception exception)
  123. {
  124. Debug.LogException(exception, state?.Root?.Component as Object);
  125. }
  126. _CurrentState = previousState;
  127. _CurrentEvent = previousEvent;
  128. }
  129. /************************************************************************************************************************/
  130. /// <summary>
  131. /// Returns either the <see cref="AnimancerPlayable.DefaultFadeDuration"/> or the
  132. /// <see cref="AnimancerState.RemainingDuration"/> of the <see cref="CurrentState"/> (whichever is higher).
  133. /// </summary>
  134. public static float GetFadeOutDuration()
  135. => GetFadeOutDuration(CurrentState, AnimancerPlayable.DefaultFadeDuration);
  136. /// <summary>
  137. /// Returns either the `minDuration` or the <see cref="AnimancerState.RemainingDuration"/> of the
  138. /// <see cref="CurrentState"/> (whichever is higher).
  139. /// </summary>
  140. public static float GetFadeOutDuration(float minDuration)
  141. => GetFadeOutDuration(CurrentState, minDuration);
  142. /// <summary>
  143. /// Returns either the `minDuration` or the <see cref="AnimancerState.RemainingDuration"/> of the
  144. /// `state` (whichever is higher).
  145. /// </summary>
  146. public static float GetFadeOutDuration(AnimancerState state, float minDuration)
  147. {
  148. if (state == null)
  149. return minDuration;
  150. var time = state.Time;
  151. var speed = state.EffectiveSpeed;
  152. if (speed == 0)
  153. return minDuration;
  154. float remainingDuration;
  155. if (state.IsLooping)
  156. {
  157. var previousTime = time - speed * Time.deltaTime;
  158. var inverseLength = 1f / state.Length;
  159. // If we just passed the end of the animation, the remaining duration would technically be the full
  160. // duration of the animation, so we most likely want to use the minimum duration instead.
  161. if (Math.Floor(time * inverseLength) != Math.Floor(previousTime * inverseLength))
  162. return minDuration;
  163. }
  164. if (speed > 0)
  165. {
  166. remainingDuration = (state.Length - time) / speed;
  167. }
  168. else
  169. {
  170. remainingDuration = time / -speed;
  171. }
  172. return Math.Max(minDuration, remainingDuration);
  173. }
  174. /************************************************************************************************************************/
  175. #endregion
  176. /************************************************************************************************************************/
  177. #region Operators
  178. /************************************************************************************************************************/
  179. /// <summary>Are the <see cref="normalizedTime"/> and <see cref="callback"/> equal?</summary>
  180. public static bool operator ==(AnimancerEvent a, AnimancerEvent b) =>
  181. a.normalizedTime == b.normalizedTime &&
  182. a.callback == b.callback;
  183. /// <summary>Are the <see cref="normalizedTime"/> and <see cref="callback"/> not equal?</summary>
  184. public static bool operator !=(AnimancerEvent a, AnimancerEvent b) => !(a == b);
  185. /************************************************************************************************************************/
  186. /// <summary>[<see cref="IEquatable{AnimancerEvent}"/>]
  187. /// Are the <see cref="normalizedTime"/> and <see cref="callback"/> of this event equal to those of the
  188. /// `animancerEvent`?
  189. /// </summary>
  190. public bool Equals(AnimancerEvent animancerEvent) => this == animancerEvent;
  191. /// <inheritdoc/>
  192. public override bool Equals(object obj) => obj is AnimancerEvent animancerEvent && this == animancerEvent;
  193. /// <inheritdoc/>
  194. public override int GetHashCode()
  195. {
  196. const int Multiplyer = -1521134295;
  197. var hashCode = -78069441;
  198. hashCode = hashCode * Multiplyer + normalizedTime.GetHashCode();
  199. if (callback != null)
  200. hashCode = hashCode * Multiplyer + callback.GetHashCode();
  201. return hashCode;
  202. }
  203. /************************************************************************************************************************/
  204. #endregion
  205. /************************************************************************************************************************/
  206. }
  207. }