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