You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/Plugins/Animancer/Utilities/AnimationEventReceiver.cs

200 lines
7.2 KiB
C#

// Animancer // Copyright 2020 Kybernetik //
using System;
using System.Text;
using UnityEngine;
namespace Animancer
{
/// <summary>
/// A callback to be invoked by Animation Events that have been triggered by a specific animation.
/// </summary>
/// <example>
/// To set up a receiver for an Animation Event with the Function Name "Event":
/// <para></para><code>
/// /// &lt;summary&gt;A callback for Animation Events with the Function Name "Event".&lt;/summary&gt;
/// public AnimationEventReceiver onEvent;
///
/// /// &lt;summary&gt;Called by Animation Events.&lt;/summary&gt;
/// private void Event(AnimationEvent animationEvent)
/// {
/// // This is optional and will automatically be compiled out of runtime builds.
/// // It allows the receiver to perform additional safety checks.
/// onEvent.SetFunctionName("Event");
///
/// onEvent.HandleEvent(animationEvent);
/// }
/// </code>
/// Then to register a callback to that receiver:
/// <para></para><code>
/// var state = animancer.Play(clip);
/// onEvent.Set(state, (animationEvent) =>
/// {
/// ...
/// });
/// </code>
/// </example>
public struct AnimationEventReceiver
{
/************************************************************************************************************************/
private AnimancerState _Source;
private int _SourceID;
/// <summary>
/// If set, only Animation Events caused by this state before the
/// <see cref="AnimancerLayer.CommandCount"/> changes will actually trigger the <see cref="Callback"/>.
/// </summary>
public AnimancerState Source
{
get
{
if (_Source == null ||
_Source.Layer.CommandCount != _SourceID)
return null;
return _Source;
}
set
{
_Source = value;
if (value != null)
_SourceID = value.Layer.CommandCount;
}
}
/************************************************************************************************************************/
/// <summary>
/// A delegate that will be invoked by <see cref="HandleEvent"/>.
/// <para></para>
/// It is recommended that you use <see cref="Set"/> or manually specify a <see cref="Source"/> when assigning
/// this reference. Otherwise events from an animation that is fading out might trigger a callback you just
/// registered for a new animation that is fading in.
/// </summary>
public Action<AnimationEvent> Callback { get; set; }
/************************************************************************************************************************/
/// <summary>
/// Constructs a new <see cref="AnimationEventReceiver"/> and sets the <see cref="Source"/> and
/// <see cref="Callback"/>.
/// </summary>
public AnimationEventReceiver(AnimancerState source, Action<AnimationEvent> callback)
{
_Source = source;
_SourceID = source != null ? source.Layer.CommandCount : -1;
Callback = callback;
#if UNITY_EDITOR
FunctionName = null;
ValidateSourceHasCorrectEvent();
#endif
}
/// <summary>
/// Sets the <see cref="Source"/> and <see cref="Callback"/>.
/// </summary>
public void Set(AnimancerState source, Action<AnimationEvent> callback)
{
Source = source;
Callback = callback;
#if UNITY_EDITOR
ValidateSourceHasCorrectEvent();
#endif
}
/************************************************************************************************************************/
#if UNITY_EDITOR
/// <summary>[Editor-Only] The function name of the event that this receiver is intended for.</summary>
public string FunctionName { get; private set; }
#endif
/// <summary>[Editor-Conditional]
/// Sets the <see cref="FunctionName"/> so <see cref="Set"/> can perform additional safety checks to ensure
/// that the <see cref="AnimancerState.Clip"/> actually has an event with the expected `name` and also to allow
/// <see cref="HandleEvent"/> to verify that any events it is given have that `name` as well.
/// </summary>
[System.Diagnostics.Conditional(Strings.EditorOnly)]
public void SetFunctionName(string name)
{
#if UNITY_EDITOR
FunctionName = name;
#endif
}
#if UNITY_EDITOR
/// <summary>[Editor-Only]
/// If a <see cref="Source"/> and <see cref="FunctionName"/> have been assigned but the
/// <see cref="AnimancerState.Clip"/> has no event with that name, this method logs a warning.
/// </summary>
private void ValidateSourceHasCorrectEvent()
{
if (FunctionName == null || _Source == null || AnimancerUtilities.HasEvent(_Source, FunctionName))
return;
var message = new StringBuilder()
.Append("No Animation Event was found in ")
.Append(_Source.Clip)
.Append(" with the Function Name '")
.Append(FunctionName)
.Append('\'');
if (_Source != null)
{
message.Append('\n');
_Source.Root.AppendDescription(message);
}
Debug.LogWarning(message);
}
#endif
/************************************************************************************************************************/
/// <summary>
/// Clears the <see cref="Source"/> and <see cref="Callback"/>.
/// </summary>
public void Clear()
{
_Source = null;
Callback = null;
}
/************************************************************************************************************************/
/// <summary>
/// Invokes the <see cref="Callback"/> if either no <see cref="Source"/> has been set or if it is still
/// current and its <see cref="AnimancerState.Clip"/> matches the one triggering the event.
/// </summary>
public bool HandleEvent(AnimationEvent animationEvent)
{
if (Callback == null)
return false;
if (_Source != null)
{
if (_Source.Layer.CommandCount != _SourceID ||
!ReferenceEquals(_Source.Clip, animationEvent.animatorClipInfo.clip))
return false;
}
#if UNITY_EDITOR
if (FunctionName != null && FunctionName != animationEvent.functionName)
throw new ArgumentException(string.Concat(
"Function Name Mismatch: receiver.FunctionName='", FunctionName,
"' while event.functionName='", animationEvent.functionName, "'"));
#endif
Callback(animationEvent);
return true;
}
/************************************************************************************************************************/
}
}