123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
- using System;
- using System.Text;
- using UnityEngine;
- using Object = UnityEngine.Object;
- namespace Animancer
- {
- /// <summary>
- /// A <see cref="callback"/> delegate paired with a <see cref="normalizedTime"/> to determine when to invoke it.
- /// </summary>
- /// <remarks>
- /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">Animancer Events</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/AnimancerEvent
- ///
- public partial struct AnimancerEvent : IEquatable<AnimancerEvent>
- {
- /************************************************************************************************************************/
- #region Event
- /************************************************************************************************************************/
- /// <summary>The <see cref="AnimancerState.NormalizedTime"/> at which to invoke the <see cref="callback"/>.</summary>
- public float normalizedTime;
- /// <summary>The delegate to invoke when the <see cref="normalizedTime"/> passes.</summary>
- public Action callback;
- /************************************************************************************************************************/
- /// <summary>The largest possible float value less than 1.</summary>
- /// <remarks>
- /// This value is useful for placing events at the end of a looping animation since they do not allow the
- /// <see cref="normalizedTime"/> to be greater than or equal to 1.
- /// </remarks>
- public const float AlmostOne = 0.99999994f;
- /************************************************************************************************************************/
- /// <summary>Does nothing.</summary>
- /// <remarks>This delegate is used for events which would otherwise have a <c>null</c> <see cref="callback"/>.</remarks>
- public static readonly Action DummyCallback = Dummy;
- /// <summary>Does nothing.</summary>
- /// <remarks>Used by <see cref="DummyCallback"/>.</remarks>
- private static void Dummy() { }
- /// <summary>Is the `callback` <c>null</c> or the <see cref="DummyCallback"/>?</summary>
- public static bool IsNullOrDummy(Action callback) => callback == null || callback == DummyCallback;
- /************************************************************************************************************************/
- /// <summary>Creates a new <see cref="AnimancerEvent"/>.</summary>
- public AnimancerEvent(float normalizedTime, Action callback)
- {
- this.normalizedTime = normalizedTime;
- this.callback = callback;
- }
- /************************************************************************************************************************/
- /// <summary>Returns a string describing the details of this event.</summary>
- public override string ToString()
- {
- var text = ObjectPool.AcquireStringBuilder();
- text.Append($"{nameof(AnimancerEvent)}(");
- AppendDetails(text);
- text.Append(')');
- return text.ReleaseToString();
- }
- /************************************************************************************************************************/
- /// <summary>Appends the details of this event to the `text`.</summary>
- public void AppendDetails(StringBuilder text)
- {
- text.Append("NormalizedTime: ")
- .Append(normalizedTime)
- .Append(", Callback: ");
- if (callback == null)
- {
- text.Append("null");
- }
- else if (callback.Target == null)
- {
- text.Append(callback.Method.DeclaringType.FullName)
- .Append('.')
- .Append(callback.Method.Name);
- }
- else
- {
- text.Append("(Target: '")
- .Append(callback.Target)
- .Append("', Method: ")
- .Append(callback.Method.DeclaringType.FullName)
- .Append('.')
- .Append(callback.Method.Name)
- .Append(')');
- }
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Invocation
- /************************************************************************************************************************/
- /// <summary>The <see cref="AnimancerState"/> currently triggering an event via <see cref="Invoke"/>.</summary>
- public static AnimancerState CurrentState => _CurrentState;
- private static AnimancerState _CurrentState;
- /************************************************************************************************************************/
- /// <summary>The <see cref="AnimancerEvent"/> currently being triggered via <see cref="Invoke"/>.</summary>
- public static ref readonly AnimancerEvent CurrentEvent => ref _CurrentEvent;
- private static AnimancerEvent _CurrentEvent;
- /************************************************************************************************************************/
- /// <summary>
- /// Sets the <see cref="CurrentState"/> and <see cref="CurrentEvent"/> then invokes the <see cref="callback"/>.
- /// </summary>
- /// <remarks>This method catches and logs any exception thrown by the <see cref="callback"/>.</remarks>
- /// <exception cref="NullReferenceException">The <see cref="callback"/> is null.</exception>
- public void Invoke(AnimancerState state)
- {
- #if UNITY_ASSERTIONS
- if (IsNullOrDummy(callback))
- OptionalWarning.UselessEvent.Log(
- $"An {nameof(AnimancerEvent)} that does nothing was invoked." +
- " Most likely it was not configured correctly." +
- " Unused events should be removed to avoid wasting performance checking and invoking them.",
- state?.Root?.Component);
- #endif
- var previousState = _CurrentState;
- var previousEvent = _CurrentEvent;
- _CurrentState = state;
- _CurrentEvent = this;
- try
- {
- callback();
- }
- catch (Exception exception)
- {
- Debug.LogException(exception, state?.Root?.Component as Object);
- }
- _CurrentState = previousState;
- _CurrentEvent = previousEvent;
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Returns either the <see cref="AnimancerPlayable.DefaultFadeDuration"/> or the
- /// <see cref="AnimancerState.RemainingDuration"/> of the <see cref="CurrentState"/> (whichever is higher).
- /// </summary>
- public static float GetFadeOutDuration()
- => GetFadeOutDuration(CurrentState, AnimancerPlayable.DefaultFadeDuration);
- /// <summary>
- /// Returns either the `minDuration` or the <see cref="AnimancerState.RemainingDuration"/> of the
- /// <see cref="CurrentState"/> (whichever is higher).
- /// </summary>
- public static float GetFadeOutDuration(float minDuration)
- => GetFadeOutDuration(CurrentState, minDuration);
- /// <summary>
- /// Returns either the `minDuration` or the <see cref="AnimancerState.RemainingDuration"/> of the
- /// `state` (whichever is higher).
- /// </summary>
- public static float GetFadeOutDuration(AnimancerState state, float minDuration)
- {
- if (state == null)
- return minDuration;
- var time = state.Time;
- var speed = state.EffectiveSpeed;
- if (speed == 0)
- return minDuration;
- float remainingDuration;
- if (state.IsLooping)
- {
- var previousTime = time - speed * Time.deltaTime;
- var inverseLength = 1f / state.Length;
- // If we just passed the end of the animation, the remaining duration would technically be the full
- // duration of the animation, so we most likely want to use the minimum duration instead.
- if (Math.Floor(time * inverseLength) != Math.Floor(previousTime * inverseLength))
- return minDuration;
- }
- if (speed > 0)
- {
- remainingDuration = (state.Length - time) / speed;
- }
- else
- {
- remainingDuration = time / -speed;
- }
- return Math.Max(minDuration, remainingDuration);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Operators
- /************************************************************************************************************************/
- /// <summary>Are the <see cref="normalizedTime"/> and <see cref="callback"/> equal?</summary>
- public static bool operator ==(AnimancerEvent a, AnimancerEvent b) =>
- a.normalizedTime == b.normalizedTime &&
- a.callback == b.callback;
- /// <summary>Are the <see cref="normalizedTime"/> and <see cref="callback"/> not equal?</summary>
- public static bool operator !=(AnimancerEvent a, AnimancerEvent b) => !(a == b);
- /************************************************************************************************************************/
- /// <summary>[<see cref="IEquatable{AnimancerEvent}"/>]
- /// Are the <see cref="normalizedTime"/> and <see cref="callback"/> of this event equal to those of the
- /// `animancerEvent`?
- /// </summary>
- public bool Equals(AnimancerEvent animancerEvent) => this == animancerEvent;
- /// <inheritdoc/>
- public override bool Equals(object obj) => obj is AnimancerEvent animancerEvent && this == animancerEvent;
- /// <inheritdoc/>
- public override int GetHashCode()
- {
- const int Multiplyer = -1521134295;
- var hashCode = -78069441;
- hashCode = hashCode * Multiplyer + normalizedTime.GetHashCode();
- if (callback != null)
- hashCode = hashCode * Multiplyer + callback.GetHashCode();
- return hashCode;
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
|