// Animancer // Copyright 2020 Kybernetik //
using System;
using System.Text;
using UnityEngine;
namespace Animancer
{
///
/// A callback to be invoked by Animation Events that have been triggered by a specific animation.
///
///
/// To set up a receiver for an Animation Event with the Function Name "Event":
///
/// /// <summary>A callback for Animation Events with the Function Name "Event".</summary>
/// public AnimationEventReceiver onEvent;
///
/// /// <summary>Called by Animation Events.</summary>
/// 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);
/// }
///
/// Then to register a callback to that receiver:
///
/// var state = animancer.Play(clip);
/// onEvent.Set(state, (animationEvent) =>
/// {
/// ...
/// });
///
///
public struct AnimationEventReceiver
{
/************************************************************************************************************************/
private AnimancerState _Source;
private int _SourceID;
///
/// If set, only Animation Events caused by this state before the
/// changes will actually trigger the .
///
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;
}
}
/************************************************************************************************************************/
///
/// A delegate that will be invoked by .
///
/// It is recommended that you use or manually specify a 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.
///
public Action Callback { get; set; }
/************************************************************************************************************************/
///
/// Constructs a new and sets the and
/// .
///
public AnimationEventReceiver(AnimancerState source, Action callback)
{
_Source = source;
_SourceID = source != null ? source.Layer.CommandCount : -1;
Callback = callback;
#if UNITY_EDITOR
FunctionName = null;
ValidateSourceHasCorrectEvent();
#endif
}
///
/// Sets the and .
///
public void Set(AnimancerState source, Action callback)
{
Source = source;
Callback = callback;
#if UNITY_EDITOR
ValidateSourceHasCorrectEvent();
#endif
}
/************************************************************************************************************************/
#if UNITY_EDITOR
/// [Editor-Only] The function name of the event that this receiver is intended for.
public string FunctionName { get; private set; }
#endif
/// [Editor-Conditional]
/// Sets the so can perform additional safety checks to ensure
/// that the actually has an event with the expected `name` and also to allow
/// to verify that any events it is given have that `name` as well.
///
[System.Diagnostics.Conditional(Strings.EditorOnly)]
public void SetFunctionName(string name)
{
#if UNITY_EDITOR
FunctionName = name;
#endif
}
#if UNITY_EDITOR
/// [Editor-Only]
/// If a and have been assigned but the
/// has no event with that name, this method logs a warning.
///
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
/************************************************************************************************************************/
///
/// Clears the and .
///
public void Clear()
{
_Source = null;
Callback = null;
}
/************************************************************************************************************************/
///
/// Invokes the if either no has been set or if it is still
/// current and its matches the one triggering the event.
///
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;
}
/************************************************************************************************************************/
}
}