You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
9.8 KiB
224 lines
9.8 KiB
// Animancer // https://kybernetik.com.au/animancer // Copyright 2021 Kybernetik // |
|
|
|
using UnityEngine; |
|
|
|
namespace Animancer |
|
{ |
|
/// <summary> |
|
/// A component which uses Animation Events with the Function Name "End" to invoke the |
|
/// <see cref="AnimancerEvent.Sequence.endEvent"/> of the <see cref="AnimancerState"/> that triggered the event. |
|
/// </summary> |
|
/// <remarks> |
|
/// This component must always be attached to the same <see cref="GameObject"/> as the <see cref="Animator"/> in |
|
/// order to receive Animation Events from it. |
|
/// <para></para> |
|
/// Note that Unity will allocate some garbage every time it triggers an Animation Event with an |
|
/// <see cref="AnimationEvent"/> parameter. |
|
/// <para></para> |
|
/// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/events/animation#end-animation-events">End Animation Events</see> |
|
/// </remarks> |
|
/// 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; |
|
|
|
/// <summary>[<see cref="SerializeField"/>] |
|
/// The <see cref="AnimancerComponent"/> on which the <see cref="AnimancerEvent.Sequence.endEvent"/> will be |
|
/// triggered. |
|
/// </summary> |
|
public ref AnimancerComponent Animancer => ref _Animancer; |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// The <see cref="AnimationEvent"/> called 'End' which is currently being triggered. |
|
/// </summary> |
|
public static AnimationEvent CurrentEvent { get; private set; } |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary>Calls <see cref="End(AnimancerComponent, AnimationEvent)"/>.</summary> |
|
/// <remarks>Called by Animation Events with the Function Name "End".</remarks> |
|
private void End(AnimationEvent animationEvent) |
|
{ |
|
End(_Animancer, animationEvent); |
|
} |
|
|
|
/// <summary> |
|
/// Tries to invoke the <see cref="AnimancerEvent.Sequence.endEvent"/> of the <see cref="AnimancerState"/> that |
|
/// triggered the `animationEvent`. |
|
/// </summary> |
|
/// <remarks> |
|
/// Note that Unity will allocate some garbage every time it triggers an Animation Event with an |
|
/// <see cref="AnimationEvent"/> parameter. |
|
/// </remarks> |
|
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; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Invokes the <see cref="AnimancerEvent.Sequence.OnEnd"/> 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). |
|
/// </summary> |
|
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; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// Invokes the <see cref="AnimancerEvent.Sequence.OnEnd"/> 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). |
|
/// </summary> |
|
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; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// If the <see cref="AnimancerState.Clip"/> and <see cref="AnimancerNode.Weight"/> match the |
|
/// <see cref="AnimationEvent"/>, this method invokes the <see cref="AnimancerEvent.Sequence.OnEnd"/> callback |
|
/// and returns true. |
|
/// </summary> |
|
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; |
|
} |
|
|
|
/************************************************************************************************************************/ |
|
|
|
/// <summary> |
|
/// If the <see cref="CurrentEvent"/> has a float parameter above 0, this method returns that value. |
|
/// Otherwise this method calls <see cref="AnimancerEvent.GetFadeOutDuration"/> so if you aren't using an |
|
/// Animation Event with the function name "End" you can just call that method directly. |
|
/// </summary> |
|
public static float GetFadeOutDuration(float minDuration) |
|
{ |
|
if (CurrentEvent != null && CurrentEvent.floatParameter > 0) |
|
return CurrentEvent.floatParameter; |
|
|
|
return AnimancerEvent.GetFadeOutDuration(minDuration); |
|
} |
|
|
|
/// <summary> |
|
/// If the <see cref="CurrentEvent"/> has a float parameter above 0, this method returns that value. |
|
/// Otherwise this method calls <see cref="AnimancerEvent.GetFadeOutDuration"/> so if you aren't using an |
|
/// Animation Event with the function name "End" you can just call that method directly. |
|
/// </summary> |
|
/// <remarks> |
|
/// Calls <see cref="GetFadeOutDuration(float)"/> using the |
|
/// <see cref="AnimancerPlayable.DefaultFadeDuration"/>. |
|
/// </remarks> |
|
public static float GetFadeOutDuration() |
|
=> GetFadeOutDuration(AnimancerPlayable.DefaultFadeDuration); |
|
|
|
/************************************************************************************************************************/ |
|
|
|
#if UNITY_EDITOR |
|
/// <summary>[Editor-Only] |
|
/// Called when this component is first added in Edit Mode. |
|
/// Tries to automatically find the <see cref="Animancer"/> component using |
|
/// <see cref="AnimancerUtilities.GetComponentInParentOrChildren{T}(GameObject, ref T)"/>. |
|
/// </summary> |
|
protected virtual void Reset() |
|
{ |
|
gameObject.GetComponentInParentOrChildren(ref _Animancer); |
|
} |
|
#endif |
|
|
|
/************************************************************************************************************************/ |
|
} |
|
}
|
|
|