So I finally implemented what I think its a good solution, but far from being simple.
In case it wasn’t clear before, my goal was to know when I’ve arrived at an Idle state, even if that could be the state I started from (e.g. stateA → stateB → stateA.)
My game has many Idle states (e.g. IdleStanding, IdleSitting, IdleLaying, etc) and I need to properly know when I’ve arrived at one.
The first thing that took me a while to understand (and gonna write it here for future reference) is how Mecanim transitions between states.
There is always a currentState and if I’m inTransition then there is also a nextState.
During the transition moment, both these states are listening to any possible Parameters (e.g. triggers, booleans, etc).
In order for me to properly know if I’ve arrived at my Idle state I need the following:
- Always know if I’m in an Idle state.
- Know exactly what is the end Idle state I’m looking for.
- Only wait for the end Idle state once I’ve left the original Idle that triggered the action.
1) Always know if I’m in an Idle state.
For this to work I used the State Machine Behaviours. I added a script to every single Idle state that sets a boolean isIdle to true everytime that state is running and its NOT in a transition:
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
stateIsStarting = true;
}
override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
if(!animator.IsInTransition(layerIndex) && stateIsStarting){
animator.SetInteger("idleTotals", animator.GetInteger("idleTotals")+1);
stateIsStarting = false;
stateIsActive = true;
}
else if(animator.IsInTransition(layerIndex) && stateIsActive){
animator.SetInteger("idleTotals", animator.GetInteger("idleTotals")-1);
stateIsActive = false;
}
}
2) Know exactly what is the end Idle state I’m looking for.
I still have to do the boring list of all Idles in code so I can compare them later.
var handcuffedIdleState : int = Animator.StringToHash("Base Layer.Handcuffed.HandcuffedIdle");
And once I trigger my new animation, the first thing I do is save a List of all the “allowed” Idle end states for that animation.
destinationIdleHash.Add(animController.handcuffedIdleState);
3) Only wait for the end Idle state once I’ve left the original Idle that triggered the action.
On my actor script, right after I’ve triggered the new animation, I record what is the Idle state that called it:
if(animController.anim.IsInTransition(0)){
triggerIdleStateHash = animController.anim.GetNextAnimatorStateInfo(0).fullPathHash;
}
else {
triggerIdleStateHash = animController.anim.GetCurrentAnimatorStateInfo(0).fullPathHash;
}
And finally make sure I only look for the end Idle that I defined in 2) once I left a transition:
if (
animController.anim.GetBool("isIdle") &&
!animController.anim.IsInTransition(0) &&
animController.anim.GetCurrentAnimatorStateInfo(0).fullPathHash != triggerIdleStateHash &&
destinationIdleHash.Contains(animController.anim.GetCurrentAnimatorStateInfo(0).fullPathHash)
){
return true;
}
else {
return false;
}
Now this works well so far, but the amount of maintenance I need to do if I add a new Idle is frustrating. I’m wondering if I’m just using Mecanim incorrectly or there is another way to deal with this that I’m not seeing.