// 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
/************************************************************************************************************************/
}
}