// Animancer // Copyright 2020 Kybernetik // using System; using System.Text; using UnityEngine; namespace Animancer { /// <summary> /// A <see cref="callback"/> delegate paired with a <see cref="normalizedTime"/> to determine when to invoke it. /// </summary> public partial struct 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> public const float AlmostOne = 0.99999994f; /************************************************************************************************************************/ /// <summary>Constructs a new <see cref="AnimancerEvent"/>.</summary> public AnimancerEvent(float normalizedTime, Action callback) { this.normalizedTime = normalizedTime; this.callback = callback; } /************************************************************************************************************************/ /// <summary>Returns "AnimancerEvent(normalizedTime, callbackTarget.CallbackMethod)".</summary> public override string ToString() { var text = new StringBuilder() .Append("AnimancerEvent(") .Append(normalizedTime) .Append(", "); if (callback == null) { text.Append("null)"); } else if (callback.Target == null) { text.Append(callback.Method.Name) .Append(")"); } else { text.Append(callback.Target) .Append('.') .Append(callback.Method.Name) .Append(")"); } return text.ToString(); } /************************************************************************************************************************/ /// <summary>Appends the details of this event to the `text`.</summary> public void AppendDetails(StringBuilder text, string name, string delimiter = "\n") { text.Append(delimiter).Append(name).Append(".NormalizedTime=").Append(normalizedTime); if (callback != null) { text.Append(delimiter).Append(name).Append(".Target=").Append(callback.Target); text.Append(delimiter).Append(name).Append(".Method=").Append(callback.Method); } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Invocation /************************************************************************************************************************/ /// <summary>The <see cref="AnimancerState"/> currently triggering an event using <see cref="Invoke"/>.</summary> public static AnimancerState CurrentState { get { return _CurrentState; } } private static AnimancerState _CurrentState; /************************************************************************************************************************/ /// <summary>The <see cref="AnimancerEvent"/> currently being triggered by <see cref="Invoke"/>.</summary> public static AnimancerEvent CurrentEvent { get { return _CurrentEvent; } } private static AnimancerEvent _CurrentEvent; /************************************************************************************************************************/ /// <summary> /// Sets the static <see cref="CurrentState"/> and <see cref="CurrentEvent"/> then invokes the <see cref="callback"/>. /// <para></para> /// This method catches and logs any exception thrown by the <see cref="callback"/>. /// </summary> /// <exception cref="NullReferenceException">Thrown if the <see cref="callback"/> is null.</exception> public void Invoke(AnimancerState state) { var previousState = _CurrentState; var previousEvent = _CurrentEvent; _CurrentState = state; _CurrentEvent = this; try { callback(); } catch (Exception ex) { Debug.LogException(ex); } _CurrentState = previousState; _CurrentEvent = previousEvent; } /************************************************************************************************************************/ /// <summary> /// Returns either the `minDuration` or the <see cref="AnimancerState.RemainingDuration"/> of the /// <see cref="CurrentState"/> state (whichever is higher). /// </summary> public static float GetFadeOutDuration(float minDuration = AnimancerPlayable.DefaultFadeDuration) { var state = CurrentState; if (state == null) return minDuration; var time = state.Time; var speed = state.EffectiveSpeed; 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 /************************************************************************************************************************/ } }