Search Unity

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

Discussion in 'Animation' started by castor, Jul 18, 2014.

  1. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    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() :

    Code (JavaScript):
    1. if(
    2.     !animController.anim.IsInTransition(0) &&
    3.     animController.currentBaseState.nameHash == animController.stateA
    4. ){
    5. //React to action
    6. }
    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!
     
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    What if you keep track of the previous state?

    Code (csharp):
    1. if(
    2.     !animController.anim.IsInTransition(0) &&
    3.     animController.currentBaseState.nameHash == animController.stateA &&
    4.     previousState == animController.stateB
    5. ){
    6.     //React to action
    7. }
    And of course set previousState whenever you transition.

    Or you could use Animation Events.
     
    Last edited: Jul 18, 2014
  3. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    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.
     
    Last edited: Jul 18, 2014
  4. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    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().)
     
  5. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    Thank you so much! I didn't know about GetNextAnimatorStateInfo.

    Just like you suggested, this seems to work:
    Code (JavaScript):
    1.     if(
    2.         animController.anim.IsInTransition(0) &&
    3.         animController.anim.GetNextAnimatorStateInfo(0).nameHash == animController.stateA
    4.     ){
    5.         //Do reaction
    6.     }
    Not sure what you meant about the exception since using the GetNextAnimatorStateInfo works even if I start from stateA?
     
  6. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    Good point. I didn't think through the logic. Please disregard that. :)
     
  7. snlehton

    snlehton

    Joined:
    Jun 11, 2010
    Posts:
    99
    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...
     
  8. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    This is an old thread. You can now add animation events.
     
  9. snlehton

    snlehton

    Joined:
    Jun 11, 2010
    Posts:
    99
    Where is info about that? Notice that we're talking about state transition events, not animation events.

    Documentation at http://docs.unity3d.com/Manual/StateMachineBasics.html 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://answers.unity3d.com/questions/615041/mecanim-animation-events-1.html
    http://docs.unity3d.com/Manual/animeditor-AnimationEvents.html

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

    http://docs.unity3d.com/Manual/MecanimFAQ.html

    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.
     
  10. snlehton

    snlehton

    Joined:
    Jun 11, 2010
    Posts:
    99
    In case someone needs to have the state transition events this is how I did it in the end:

    Code (CSharp):
    1. public Animator animator { get; set; }
    2.  
    3.     public int stateDay { get; private set; }
    4.  
    5.     public int stateNight { get; private set; }
    6.  
    7.     protected int lastAnimState { get; set; }
    8.  
    9.     public void Start()
    10.     {
    11.         animator = GetComponent<Animator>();
    12.  
    13.         stateDay = Animator.StringToHash("Base Layer.Day");
    14.         stateNight = Animator.StringToHash("Base Layer.Night");
    15.  
    16.         lastAnimState = animator.GetCurrentAnimatorStateInfo(0).nameHash;
    17.     }
    18.  
    19.    void Update()
    20.     {
    21.         int currentAnimState = animator.GetCurrentAnimatorStateInfo(0).nameHash;
    22.  
    23.         if (!animator.IsInTransition(0) && currentAnimState != lastAnimState)
    24.         {
    25.             if (currentAnimState == stateDay)
    26.                 EventDay();
    27.  
    28.             if (currentAnimState == stateNight)
    29.                 EventNight();
    30.         }
    31.         lastAnimState = currentAnimState;
    32.     }
    33.  
    Far from optimal, but seems to work.
     
  11. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
  12. sami1592

    sami1592

    Joined:
    Sep 18, 2013
    Posts:
    57
    one ques, instead of using Animator.GetNextAnimatorStateInfo() can Animator.GetAnimatorTransitionInfo() be used?
     
  13. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    Yes, but now that Unity 5 is out it's probably easier to attach a behavior to a specific state instead.
     
  14. sami1592

    sami1592

    Joined:
    Sep 18, 2013
    Posts:
    57
    any good blog or tutorial about this?
     
  15. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    dreasgrech and sami1592 like this.
  16. sami1592

    sami1592

    Joined:
    Sep 18, 2013
    Posts:
    57
    thanks man!
     
  17. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    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:

    Code (CSharp):
    1.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    2.         if(!animator.GetBool("isIdle")){
    3.             animator.SetBool("isIdle", true);
    4.         }
    5.     }
    6.  
    7.     override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    8.         if(animator.GetBool("isIdle")){
    9.             animator.SetBool("isIdle", false);
    10.         }
    11.     }
    The idea is that after the new state is triggered (isIdle = false), I wait on Update() for isIdle to become true again:

    Code (JavaScript):
    1. function Update (){
    2.     if (animController.anim.GetBool("isIdle")){
    3.         //Do whatever
    4.     }
    5. }
    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.
     
    Last edited: Jul 17, 2015
  18. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    With great power comes... a little more complexity, I guess. :)

    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:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. public class ActiveStates : MonoBehaviour {
    5.     // Records active animator states:
    6.     public List<MyStateMachineBehaviour> list= new List<MyStateMachineBehaviour>();
    7. }
    And this script to your states:
    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class MyStateMachineBehaviour : StateMachineBehaviour {
    4.  
    5.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    6.         // Just record that we're now also in this state:
    7.         var activeStates = animator.GetComponent<ActiveStates>();
    8.         activeStates.list.Add(this);
    9.     }
    10.  
    11.     override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    12.         // Record that we're out of this state, and let the remaining state know:
    13.         var activeStates = animator.GetComponent<ActiveStates>();
    14.         activeStates.list.Remove(this);
    15.         if (activeStates.list.Count > 0) {
    16.             activeStates.list[0].OnTransitionComplete(animator, stateInfo, layerIndex);
    17.         }
    18.     }
    19.  
    20.     public void OnTransitionComplete(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    21.         // Your code here: We're fully out of the previous state, and fully in this state now.
    22.     }
    23. }
    I typed this directly into the post. Pardon typos. If you want to track multiple layers, you could use a multidimensional list in ActiveStates.
     
  19. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    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.

    Code (CSharp):
    1.     // OnStateEnter is called before OnStateEnter is called on any state inside this state machine
    2.     override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    3.             animator.SetBool("isIdleStart", true);
    4.     }
    5.  
    6.     override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    7.         if(!animator.IsInTransition(layerIndex) && animator.GetBool("isIdleStart")){
    8.             animator.SetBool("isIdleStart", false);
    9.             animator.SetBool("isIdle", true);
    10.         }      
    11.         else if(animator.IsInTransition(layerIndex) && animator.GetBool("isIdle")){
    12.             animator.SetBool("isIdle", false);  
    13.         }
    14.     }
    15.      
    16.     override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    17.         if(animator.GetBool("isIdle")){
    18.             animator.SetBool("isIdle", false);
    19.         }
    20.     }
    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.
     
  20. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    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:

    Code (CSharp):
    1.  
    2. override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
    3.         stateIsStarting = true;
    4.     }
    5.  
    6.     override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {  
    7.         if(!animator.IsInTransition(layerIndex) && stateIsStarting){
    8.             animator.SetInteger("idleTotals", animator.GetInteger("idleTotals")+1);
    9.             stateIsStarting = false;
    10.             stateIsActive = true;
    11.         }
    12.         else if(animator.IsInTransition(layerIndex) && stateIsActive){
    13.             animator.SetInteger("idleTotals", animator.GetInteger("idleTotals")-1);
    14.             stateIsActive = false;
    15.         }  
    16.     }

    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.

    Code (JavaScript):
    1. 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.

    Code (JavaScript):
    1. 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:

    Code (JavaScript):
    1.        if(animController.anim.IsInTransition(0)){
    2.             triggerIdleStateHash = animController.anim.GetNextAnimatorStateInfo(0).fullPathHash;
    3.         }
    4.         else {
    5.             triggerIdleStateHash = animController.anim.GetCurrentAnimatorStateInfo(0).fullPathHash;
    6.         }
    And finally make sure I only look for the end Idle that I defined in 2) once I left a transition:
    Code (JavaScript):
    1.     if (
    2.         animController.anim.GetBool("isIdle") &&
    3.         !animController.anim.IsInTransition(0) &&
    4.         animController.anim.GetCurrentAnimatorStateInfo(0).fullPathHash != triggerIdleStateHash &&
    5.         destinationIdleHash.Contains(animController.anim.GetCurrentAnimatorStateInfo(0).fullPathHash)
    6.     ){
    7.         return true;
    8.     }
    9.     else {
    10.         return false;
    11.     }
    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.
     
  21. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    You gave some reasons early on for not using animation events. Given the complexity of the code above, perhaps it's worth looking at animation events again.

    Or maybe there's another way to structure your state machine and transitions? I've built some fairly complex animator controllers with multiple randomized idle states, and it worked fine to just let Mecanim handle it all.

    BTW, this is somewhat off topic, but I read a great Gamasutra article that you might find interesting: The Challenge of Having Both Responsiveness and Naturalness in Game Animation.
     
  22. castor

    castor

    Joined:
    Sep 9, 2012
    Posts:
    40
    I don't think animation events can/should be used in this case since they are relative to animations and not states.
    If I use them, it's because I want to trigger something relative to the animation timing (e.g. audio cues, fx, etc), no?
    Also, if I change an animation inside a state, I have to redo the event trigger. I might also be re-using an animation in a different state where I don't want to be triggering that event.

    I feel state behaviours are there for this kind of situations. I can repeat the same script for all Idle and clearly see what it's happening (Unlike anim events that I have to always select the animation and look inside its properties). If I used animation events I would do the same that I'm doing with behaviours right now.
     
  23. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    Good point!