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