// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik //
using System;
namespace Animancer
{
/// [Pro-Only] A callback for when an becomes 0.
///
///
/// Most Finite State Machine systems
/// already have their own mechanism for notifying your code when a state is exited so this system is generally
/// only useful when something like that is not already available.
///
///
///
/// [SerializeField] private AnimancerComponent _Animancer;
/// [SerializeField] private AnimationClip _Clip;
///
/// private void Awake()
/// {
/// // Play the animation.
/// var state = _Animancer.Play(_Clip);
///
/// // Then give its state an exit event.
/// ExitEvent.Register(state, () => Debug.Log("State Exited"));
///
/// // That event will never actually get triggered because we are never playing anything else.
/// }
///
/// Unlike Animancer Events, an will not be cleared automatically when you play something
/// (because that's the whole point) so if you are playing the same animation repeatedly you will need to check its
/// before registering the event (otherwise all the callbacks you register will
/// stay active).
///
/// private void Update()
/// {
/// // Only register the exit event if the state was at 0 weight before.
/// var state = _Animancer.GetOrCreate(_Clip);
/// if (state.EffectiveWeight == 0)
/// ExitEvent.Register(state, () => Debug.Log("State Exited"));
///
/// // Then play the state normally.
/// _Animancer.Play(state);
/// // _Animancer.Play(_Clip); would work too, but we already have the state so using it directly is faster.
/// }
///
///
/// https://kybernetik.com.au/animancer/api/Animancer/ExitEvent
///
public class ExitEvent : Key, IUpdatable
{
/************************************************************************************************************************/
private Action _Callback;
private AnimancerNode _Node;
/************************************************************************************************************************/
///
/// Registers the `callback` to be triggered when the becomes 0.
///
///
/// The is only checked at the end of the animation update so if it
/// is set to 0 then back to a higher number before the next update, the callback will not be triggered.
///
public static void Register(AnimancerNode node, Action callback)
{
#if UNITY_ASSERTIONS
AnimancerUtilities.Assert(node != null, "Node is null.");
AnimancerUtilities.Assert(node.IsValid, "Node is not valid.");
#endif
var exit = ObjectPool.Acquire();
exit._Callback = callback;
exit._Node = node;
node.Root.RequirePostUpdate(exit);
}
/************************************************************************************************************************/
/// Removes a registered and returns true if there was one.
public static bool Unregister(AnimancerPlayable animancer)
{
for (int i = animancer.PostUpdatableCount - 1; i >= 0; i--)
{
if (animancer.GetPostUpdatable(i) is ExitEvent exit)
{
animancer.CancelPostUpdate(exit);
exit.Release();
return true;
}
}
return false;
}
///
/// Removes a registered targeting the specified `node` and returns true if there was
/// one.
///
public static bool Unregister(AnimancerNode node)
{
var animancer = node.Root;
for (int i = animancer.PostUpdatableCount - 1; i >= 0; i--)
{
if (animancer.GetPostUpdatable(i) is ExitEvent exit &&
exit._Node == node)
{
animancer.CancelPostUpdate(exit);
exit.Release();
return true;
}
}
return false;
}
/************************************************************************************************************************/
void IUpdatable.Update()
{
if (_Node.IsValid() && _Node.EffectiveWeight > 0)
return;
_Callback();
_Node.Root.CancelPostUpdate(this);
Release();
}
/************************************************************************************************************************/
private void Release()
{
_Callback = null;
_Node = null;
ObjectPool.Release(this);
}
/************************************************************************************************************************/
}
}