Mecanim - Going from stateA/stateB/stateA. Knowing when second stateA has started?

In my state machine, if certain event happens the player goes from AnyState → stateB → stateA.
Now this also means he might do: stateA → stateB → stateA.

stateB can change depending on the situation but the stateA (its kind of an Idle) is always the same.
Because of this, for me to know when stateB is finished and I’m back to stateA I run this on Update() :

if(
    !animController.anim.IsInTransition(0) &&
    animController.currentBaseState.nameHash == animController.stateA
){
//React to action
}

Now my problem is, if I start from stateA this ‘if’ statement is immediately true. How can I know when stateB is over and stateA is starting?

Hope I explained it clearly!

What if you keep track of the previous state?

if(
    !animController.anim.IsInTransition(0) &&
    animController.currentBaseState.nameHash == animController.stateA &&
    previousState == animController.stateB
){
    //React to action
}

And of course set previousState whenever you transition.

Or you could use Animation Events.

I could try that, but how would I know the previousState? The moment the animation is triggered, because of transitions, the previous state is still stateA.

Also the stateB can be anything, it can be stateC or stateD. Using animation events means having to place events for all these animations. Also I can’t see Animation Events in the state machine nor in script, I need to open the individual animations so, if they change I have to redo them again.
I would rather keep Animation events only for SFX and PFX.

The final thing I could mention is that stateA is a Idle. I have this problem with other idle animations that trigger a smaller animation (stateB) and then return back to the Idle.
I can’t figure out a way to distinguish between the moment I left the Idle animation and the moment I’m back to it.

Maybe I’m not following, but what about checking Animator.IsInTransition(0) && Animator.GetNextAnimatorStateInfo(0)==stateA? If it’s heading to stateA, react. The exception would be if you start in stateA. You could just a Boolean variable to flag whether you’re just starting or not. (Also reset this flag in OnEnable().)

Thank you so much! I didn’t know about GetNextAnimatorStateInfo.

Just like you suggested, this seems to work:

    if(
        animController.anim.IsInTransition(0) &&
        animController.anim.GetNextAnimatorStateInfo(0).nameHash == animController.stateA
    ){
        //Do reaction
    }

Not sure what you meant about the exception since using the GetNextAnimatorStateInfo works even if I start from stateA?

Good point. I didn’t think through the logic. Please disregard that. :slight_smile:

Sorry I have trouble grasping the reality here. The only way to get state events is to write… A BUSY LOOP? How is that possible?

How hard would it be to add a simple event when the state is entered or left? The same way the events in the new UI system work…

This is an old thread. You can now add animation events.

Where is info about that? Notice that we’re talking about state transition events, not animation events.

Documentation at Unity - Manual: State Machine Basics says

“State Machines consist of States, Transitions and Events and smaller Sub-State Machines can be used as components in larger machines. See the reference pages forAnimation States and Animation Transitions for further information.”

But there is no info about these “Events”.

All the info I could find was:

http://docs.unity3d.com/Manual/animeditor-AnimationEvents.html

But these are about animation events, not state transition events.

In the FAQ, they say:

Can you add animation events to Mecanim?

This is high in our priorities for future development. For the time being, we suggest using additional animation curves to simulate events approximately. Although this technique doesn’t recreate events exactly, many of our users have reported it as useful.

So far as I know, you can’t add events to mecanim state machines.

In case someone needs to have the state transition events this is how I did it in the end:

public Animator animator { get; set; }

    public int stateDay { get; private set; }

    public int stateNight { get; private set; }

    protected int lastAnimState { get; set; }

    public void Start()
    {
        animator = GetComponent<Animator>();

        stateDay = Animator.StringToHash("Base Layer.Day");
        stateNight = Animator.StringToHash("Base Layer.Night");

        lastAnimState = animator.GetCurrentAnimatorStateInfo(0).nameHash;
    }

   void Update()
    {
        int currentAnimState = animator.GetCurrentAnimatorStateInfo(0).nameHash;

        if (!animator.IsInTransition(0) && currentAnimState != lastAnimState)
        {
            if (currentAnimState == stateDay)
                EventDay();

            if (currentAnimState == stateNight)
                EventNight();
        }
        lastAnimState = currentAnimState;
    }

Far from optimal, but seems to work.

State machine behavior callbacks are new in Unity 5: Unity Blog

If you’re on Unity 4, unless you want to use animation events to record when you enter and exit states, your solution is best.

one ques, instead of using Animator.GetNextAnimatorStateInfo() can Animator.GetAnimatorTransitionInfo() be used?

Yes, but now that Unity 5 is out it’s probably easier to attach a behavior to a specific state instead.

any good blog or tutorial about this?

Yup! http://unity3d.com/learn/tutorials/modules/beginner/5-pre-order-beta/state-machine-behaviours

2 Likes

thanks man!

Well, actually I just tested this new feature hoping it would fix my original problem but it doesn’t.
I created a StateMachineBehaviour that sets a variable isIdle to true when it enters that state and false when it leaves:

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        if(!animator.GetBool("isIdle")){
            animator.SetBool("isIdle", true);
        }
    }

    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        if(animator.GetBool("isIdle")){
            animator.SetBool("isIdle", false);
        }
    }

The idea is that after the new state is triggered (isIdle = false), I wait on Update() for isIdle to become true again:

function Update (){
    if (animController.anim.GetBool("isIdle")){
        //Do whatever
    }
}

But the problem remains…the damn transitions. The frame after I set a trigger to true, to start a new state, the previous state (the one that contains the StateMachineBehaviour, is still true, so it does whatever code I have on Update() immediately.

With great power comes… a little more complexity, I guess. :slight_smile:

What if you do the following?

In OnStateEnter, record that you’re in a state.

In OnStateExit, record that you’re out of a state, and call some new method on the remaining state (e.g., OnTransitionComplete).

So the calls would be something like:

  • B.OnStateEnter: Record that we’re in state B.
  • A.OnStateEnter: Record that we’re in state A. (We’re still in B, too.)
  • B.OnStateExit: No longer in state B. Call A.OnTransitionComplete.

You could add a script like this to your GameObject:

using UnityEngine;
using System.Collections.Generic;

public class ActiveStates : MonoBehaviour {
    // Records active animator states:
    public List<MyStateMachineBehaviour> list= new List<MyStateMachineBehaviour>();
}

And this script to your states:

using UnityEngine;

public class MyStateMachineBehaviour : StateMachineBehaviour {

    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        // Just record that we're now also in this state:
        var activeStates = animator.GetComponent<ActiveStates>();
        activeStates.list.Add(this);
    }

    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        // Record that we're out of this state, and let the remaining state know:
        var activeStates = animator.GetComponent<ActiveStates>();
        activeStates.list.Remove(this);
        if (activeStates.list.Count > 0) {
            activeStates.list[0].OnTransitionComplete(animator, stateInfo, layerIndex);
        }
    }

    public void OnTransitionComplete(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        // Your code here: We're fully out of the previous state, and fully in this state now.
    }
}

I typed this directly into the post. Pardon typos. If you want to track multiple layers, you could use a multidimensional list in ActiveStates.

One small thing is that my project is all written in Javascript and Unity decided that StateMachineBehaviour can only be written in C#.
I already have other dependencies forcing me to load C# before Javascript, this means I can only use StateMachineBehaviour to communicate with the State machine but nothing else outside.

Last night I tried something that almost worked, on all IDLE states I added this script.
When I enter a state I flag it as isIdleStart and once I’m out of transition I turn the isIdle flag true.
The moment I’m back into a transition (which means I’m leaving this state) I turn the flag back to false.
The problem now is actually different. I’m also using the new Enter/Exit states and apparently when you are in Enter transitioning between two state machines, the previous state is active for a few frames…anyway my conclusion is that this is getting overcomplicated for no reason. I feel I’m forcing the system to work in a different way than its supposed to.

    // OnStateEnter is called before OnStateEnter is called on any state inside this state machine
    override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            animator.SetBool("isIdleStart", true);
    }

    override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        if(!animator.IsInTransition(layerIndex) && animator.GetBool("isIdleStart")){
            animator.SetBool("isIdleStart", false);
            animator.SetBool("isIdle", true);
        }       
        else if(animator.IsInTransition(layerIndex) && animator.GetBool("isIdle")){
            animator.SetBool("isIdle", false);   
        }
    }
      
    override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
        if(animator.GetBool("isIdle")){
            animator.SetBool("isIdle", false);
        }
    }

I like your idea a lot, to actually have a list of the current states that are active and use that as a guideline. Right now my old hack solution is still working and I’ve already wasted too much time, so I’ll tackle this once it’s time to add more detail to the animation system again.

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:

  1. Always know if I’m in an Idle state.
  2. Know exactly what is the end Idle state I’m looking for.
  3. 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.