// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 Kybernetik // using UnityEngine; namespace Animancer { /// /// A component which uses Animation Events with the Function Name "End" to invoke the /// of the that triggered the event. /// /// /// This component must always be attached to the same as the in /// order to receive Animation Events from it. /// /// Note that Unity will allocate some garbage every time it triggers an Animation Event with an /// parameter. /// /// Documentation: End Animation Events /// /// https://kybernetik.com.au/animancer/api/Animancer/EndEventReceiver /// [AddComponentMenu(Strings.MenuPrefix + "End Event Receiver")] [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(EndEventReceiver))] public class EndEventReceiver : MonoBehaviour { /************************************************************************************************************************/ [SerializeField] private AnimancerComponent _Animancer; /// [] /// The on which the will be /// triggered. /// public ref AnimancerComponent Animancer => ref _Animancer; /************************************************************************************************************************/ /// /// The called 'End' which is currently being triggered. /// public static AnimationEvent CurrentEvent { get; private set; } /************************************************************************************************************************/ /// Calls . /// Called by Animation Events with the Function Name "End". private void End(AnimationEvent animationEvent) { End(_Animancer, animationEvent); } /// /// Tries to invoke the of the that /// triggered the `animationEvent`. /// /// /// Note that Unity will allocate some garbage every time it triggers an Animation Event with an /// parameter. /// public static bool End(AnimancerComponent animancer, AnimationEvent animationEvent) { if (!animancer.IsPlayableInitialized) { // This could only happen if another Animator triggers the event on this object somehow. Debug.LogWarning($"{nameof(AnimationEvent)} '{nameof(End)}' was triggered by {animationEvent.animatorClipInfo.clip}" + $", but the {nameof(AnimancerComponent)}.{nameof(AnimancerComponent.Playable)} hasn't been initialized.", animancer); return false; } var layers = animancer.Layers; var count = layers.Count; // Try targeting the current state on each layer first. for (int i = 0; i < count; i++) { if (TryInvokeOnEndEventRecursive(layers[i].CurrentState, animationEvent)) return true; } // Otherwise try every state. for (int i = 0; i < count; i++) { if (TryInvokeOnEndEventRecursive(layers[i], animationEvent)) return true; } if (animationEvent.messageOptions == SendMessageOptions.RequireReceiver) { Debug.LogWarning($"{nameof(AnimationEvent)} '{nameof(End)}' was triggered by {animationEvent.animatorClipInfo.clip}" + $", but no state was found with that {nameof(AnimancerState.Key)}.", animancer); } return false; } /************************************************************************************************************************/ /// /// Invokes the callback of the state that is playing the animation /// which triggered the event. Returns true if such a state exists (even if it doesn't have a callback). /// private static bool OnEndEventReceived(AnimancerPlayable animancer, AnimationEvent animationEvent) { var layers = animancer.Layers; var count = layers.Count; // Try targeting the current state on each layer first. for (int i = 0; i < count; i++) { if (TryInvokeOnEndEventRecursive(layers[i].CurrentState, animationEvent)) return true; } // Otherwise try every state. for (int i = 0; i < count; i++) { if (TryInvokeOnEndEventRecursive(layers[i], animationEvent)) return true; } return false; } /************************************************************************************************************************/ /// /// Invokes the callback of the state that is playing the animation /// which triggered the event. Returns true if such a state exists (even if it doesn't have a callback). /// private static bool TryInvokeOnEndEventRecursive(AnimancerNode node, AnimationEvent animationEvent) { var childCount = node.ChildCount; for (int i = 0; i < childCount; i++) { var child = node.GetChild(i); if (child != null && (TryInvokeOnEndEvent(child, animationEvent) || TryInvokeOnEndEventRecursive(child, animationEvent))) return true; } return false; } /************************************************************************************************************************/ /// /// If the and match the /// , this method invokes the callback /// and returns true. /// private static bool TryInvokeOnEndEvent(AnimancerState state, AnimationEvent animationEvent) { if (state.Weight != animationEvent.animatorClipInfo.weight || state.Clip != animationEvent.animatorClipInfo.clip || !state.HasEvents) return false; var endEvent = state.Events.endEvent; if (endEvent.callback != null) { Debug.Assert(CurrentEvent == null, $"Recursive call to {nameof(TryInvokeOnEndEvent)} detected"); try { CurrentEvent = animationEvent; endEvent.Invoke(state); } finally { CurrentEvent = null; } } return true; } /************************************************************************************************************************/ /// /// If the has a float parameter above 0, this method returns that value. /// Otherwise this method calls so if you aren't using an /// Animation Event with the function name "End" you can just call that method directly. /// public static float GetFadeOutDuration(float minDuration) { if (CurrentEvent != null && CurrentEvent.floatParameter > 0) return CurrentEvent.floatParameter; return AnimancerEvent.GetFadeOutDuration(minDuration); } /// /// If the has a float parameter above 0, this method returns that value. /// Otherwise this method calls so if you aren't using an /// Animation Event with the function name "End" you can just call that method directly. /// /// /// Calls using the /// . /// public static float GetFadeOutDuration() => GetFadeOutDuration(AnimancerPlayable.DefaultFadeDuration); /************************************************************************************************************************/ #if UNITY_EDITOR /// [Editor-Only] /// Called when this component is first added in Edit Mode. /// Tries to automatically find the component using /// . /// protected virtual void Reset() { gameObject.GetComponentInParentOrChildren(ref _Animancer); } #endif /************************************************************************************************************************/ } }