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.
200 lines
7.2 KiB
C#
200 lines
7.2 KiB
C#
2 months ago
|
// 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>
|
||
|
/// /// <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);
|
||
|
/// }
|
||
|
/// </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;
|
||
|
}
|
||
|
|
||
|
/************************************************************************************************************************/
|
||
|
}
|
||
|
}
|