Last active
November 6, 2022 17:52
-
-
Save stonstad/7bdd805d52f43d6880cc073a48757aa9 to your computer and use it in GitHub Desktop.
Unity Animation State Start and Stop Notifications
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Reflection; | |
using UnityEngine; | |
public static class AnimatorExtensions | |
{ | |
/// <summary>Gets an instance method with single argument of type <typeparamref | |
/// name="TArg0"/> and return type of <typeparamref name="TReturn"/> from <typeparamref | |
/// name="TThis"/> and compiles it into a fast open delegate.</summary> | |
/// <typeparam name="TThis">Type of the class owning the instance method.</typeparam> | |
/// <typeparam name="TArg0">Type of the single parameter to the instance method to | |
/// find.</typeparam> | |
/// <typeparam name="TReturn">Type of the return for the method</typeparam> | |
/// <param name="methodName">The name of the method the compile.</param> | |
/// <returns>The compiled delegate, which should be about as fast as calling the function | |
/// directly on the instance.</returns> | |
/// <exception cref="ArgumentException">If the method can't be found, or it has an | |
/// unexpected return type (the return type must match exactly).</exception> | |
/// <see href="https://codeblog.jonskeet.uk/2008/08/09/making-reflection-fly-and-exploring-delegates/"/> | |
private static Func<TThis, TArg0, TReturn> BuildFastOpenMemberDelegate<TThis, TArg0, TReturn>(string methodName) | |
{ | |
var method = typeof(TThis).GetMethod( | |
methodName, | |
BindingFlags.Instance | BindingFlags.NonPublic, | |
null, | |
CallingConventions.Any, | |
new[] { typeof(TArg0) }, | |
null); | |
if (method == null) | |
throw new ArgumentException("Can't find method " + typeof(TThis).FullName + "." + methodName + "(" + typeof(TArg0).FullName + ")"); | |
else if (method.ReturnType != typeof(TReturn)) | |
throw new ArgumentException("Expected " + typeof(TThis).FullName + "." + methodName + "(" + typeof(TArg0).FullName + ") to have return type of string but was " + method.ReturnType.FullName); | |
return (Func<TThis, TArg0, TReturn>)Delegate.CreateDelegate(typeof(Func<TThis, TArg0, TReturn>), method); | |
} | |
private static Func<Animator, int, string> _getCurrentStateName; | |
/// <summary>[FOR DEBUGGING ONLY] Calls an internal method on <see cref="Animator"/> that | |
/// returns the name of the current state for a layer. The internal method could be removed | |
/// or refactored at any time, and may not have good performance.</summary> | |
/// <param name="animator">The animator to get the current state from.</param> | |
/// <param name="layer">The layer to get the current state from.</param> | |
/// <returns>The name of the currently running state.</returns> | |
public static string GetCurrentStateName(this Animator animator, int layer) | |
{ | |
if (_getCurrentStateName == null) | |
_getCurrentStateName = BuildFastOpenMemberDelegate<Animator, int, string>("GetCurrentStateName"); | |
return _getCurrentStateName(animator, layer); | |
} | |
private static Func<Animator, int, string> _getNextStateName; | |
/// <summary>[FOR DEBUGGING ONLY] Calls an internal method on <see cref="Animator"/> that | |
/// returns the name of the next state for a layer. The internal method could be removed or | |
/// refactored at any time, and may not have good performance.</summary> | |
/// <param name="animator">The animator to get the next state from.</param> | |
/// <param name="layer">The layer to get the next state from.</param> | |
/// <returns>The name of the next running state.</returns> | |
public static string GetNextStateName(this Animator animator, int layer) | |
{ | |
if (_getNextStateName == null) | |
_getNextStateName = BuildFastOpenMemberDelegate<Animator, int, string>("GetNextStateName"); | |
return _getNextStateName(animator, layer); | |
} | |
private static Func<Animator, int, string> _resolveHash; | |
/// <summary>[FOR DEBUGGING ONLY] Calls an internal method on <see cref="Animator"/> that | |
/// returns the string used to create a hash from | |
/// <see cref="Animator.StringToHash(string)"/>. The internal method could be removed or | |
/// refactored at any time, and may not have good performance.</summary> | |
/// <param name="animator">The animator to get the string from.</param> | |
/// <param name="hash">The hash to get the original string for.</param> | |
/// <returns>The name of the string for <paramref name="hash"/>.</returns> | |
public static string ResolveHash(this Animator animator, int hash) | |
{ | |
if (_resolveHash == null) | |
_resolveHash = BuildFastOpenMemberDelegate<Animator, int, string>("ResolveHash"); | |
return _resolveHash(animator, hash); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace StellarConquest.Presentation.Unity | |
{ | |
public class StateMachineBehaviorListener : StateMachineBehaviour | |
{ | |
public event Action<int, string> OnStateEnterEvent; | |
public event Action<int, string> OnStateExitEvent; | |
public static Dictionary<int, string> States = new Dictionary<int, string>(); | |
static StateMachineBehaviorListener() | |
{ | |
AddState("Grounded"); | |
AddState("Pickup"); | |
AddState("Mine"); | |
AddState("Downward Chop"); | |
AddState("Horizontal Chop"); | |
} | |
private static void AddState(string stateName) | |
{ | |
States[Animator.StringToHash(stateName)] = stateName; | |
} | |
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) | |
{ | |
base.OnStateEnter(animator, stateInfo, layerIndex); | |
if (Valid(animator, stateInfo, layerIndex)) | |
OnStateEnterEvent?.Invoke(stateInfo.shortNameHash, States[stateInfo.shortNameHash]); | |
} | |
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) | |
{ | |
base.OnStateExit(animator, stateInfo, layerIndex); | |
if (Valid(animator, stateInfo, layerIndex)) | |
OnStateExitEvent?.Invoke(stateInfo.shortNameHash, States[stateInfo.shortNameHash]); | |
} | |
private bool Valid(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) | |
{ | |
#if (UNITY_EDITOR) | |
if (!States.ContainsKey(stateInfo.shortNameHash)) | |
{ | |
string stateName = animator.GetCurrentStateName(layerIndex); | |
Debug.LogError("State '" + stateName + "' missing."); | |
States[stateInfo.shortNameHash] = stateName; | |
} | |
return true; | |
#else | |
return States.ContainsKey(stateInfo.shortNameHash); | |
#endif | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using UnityEngine; | |
using UnityEngine.Animations; | |
namespace Example | |
{ | |
[RequireComponent(typeof(Animator))] | |
public class Usage : MonoBehaviour | |
{ | |
private Animator _Animator; | |
private void Start() | |
{ | |
_Animator = GetComponent<Animator>(); | |
StateMachineBehaviorListener[] listeners = _Animator.GetBehaviours<StateMachineBehaviorListener>(); | |
foreach (StateMachineBehaviorListener listener in listeners) | |
{ | |
listener.OnStateEnterEvent += OnStateEnter; | |
listener.OnStateExitEvent += OnStateExit; | |
} | |
} | |
private void OnStateEnter(int stateHash, string stateName) | |
{ | |
Debug.Log("ENTER " + stateName); | |
} | |
private void OnStateExit(int stateHash, string stateName) | |
{ | |
Debug.Log("EXIT " + stateName); | |
} | |
} | |
} |
FYI, your
Valid
method should pass thelayerIndex
param, and then use that in theGetCurrentStateName
call. Thanks for the helpful code!!
Good call -- added!
Good code! Is there any way to check the percent of the transition?
Basically I have an Grounded animation clip and a GroundedWithGun in one transition (state)
How can I check the state time?
Grounded -> GroundedWithGun ---> 0.1...0.3.....1 DONE
Thanks
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
FYI, your
Valid
method should pass thelayerIndex
param, and then use that in theGetCurrentStateName
call. Thanks for the helpful code!!