Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[Bug, or misunderstanding?] animator.Play(stateName) called multiple times in one frame has issue

Discussion in 'Animation' started by Jamez0r, Jan 26, 2020.

  1. Jamez0r

    Jamez0r

    Joined:
    Jul 29, 2019
    Posts:
    205
    I'm using an Animator as a State Machine. I'm not using it to play an animation.

    I have States called "State_A", "State_B", and "State_C".

    I set the state using animator.Play(stateName). I do not want to use transitions or triggers.

    The issue:

    I'm currently in State_C.

    I have a script that does this:

    Code (CSharp):
    1.       if (Input.GetKeyDown(KeyCode.K)) {
    2.             animator.Play("State_A");
    3.             animator.Play("State_B");
    4.             animator.Play("State_C");
    5.         }
    When I press K, I end up in State_B. I don't think that is what it should do.

    Also note that it does end up in State_B, and not State_A. So my thoughts are that it is doing this:

    -Some sort of queue to switch to State_A on the next frame
    -Overwrite that queue with State_B
    -Checks that the current state is State_C, so when animator.Play("State_C") is called, it is ignored

    Barebones repro project:

    Press A, B, or C to go directly to that state. Press K for it to do the code above.

    https://drive.google.com/open?id=1uoCuA4RNssoXz2gTkHFaJCHa5348l-IJ



    Please let me know if there is anything I can do. Ideally I'd like to be able to "re-enter" State_C (meaning OnStateEnter is called, etc), even if I'm currently in State_C. So if there is some other workaround to do that I'd appreciate any help.
     
  2. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,554
    There's probably nothing you can do, that's just how Animator Controllers work (though I've never tried it with more than two so I assumed it would end up in State_A). You could report a bug if you want to be told that it's either "by design" or they "won't fix it because users might already depend on this stupid behaviour". In my experience they'll probably start with "by design" and swap to "won't fix" when you go into detail about why that's a stupid design.

    You might be able to make a hacky workaround by manually calling your OnStateEnter when you know it is already in the target state, but I don't think there's anything you can do about it ignoring the last state you tell it to play other than waiting a frame and trying again.
     
    VolodyaSaiyajin likes this.
  3. Jamez0r

    Jamez0r

    Joined:
    Jul 29, 2019
    Posts:
    205
    Thanks for the reply ~

    Wow, yeah that is pretty silly behavior.

    My situation is that the player can "dodge" (switches to the Dodge) state, and they should be able to dodge multiple times back-to-back by pressing the dodge button again (so it queues up the next dodge). When the first dodge completes, the state returns to Idle, and then gets set back to Dodge on the same frame. But yeah, instead of going back to Dodge it just sits at Idle. Very frustrating. Forcing a single frame of Idle between the dodges does indeed work but I'd rather not have to do that.

    One tidbit of information I did find is that instead of saying animator.Play(stateName) you can do animator.Play(statename, layer, 0) -> this would totally fix the issue (being on State_C, and wanting to "restart" State_C like in my original post) including triggering OnStateEnter() again for State_C, but alas for my very specific setup even that won't work. I'll probably stick to the single-frame inbetweener state
     
    VolodyaSaiyajin and lumeriith like this.
  4. sketchygio

    sketchygio

    Joined:
    Nov 6, 2014
    Posts:
    31
    Old thread, but wanted to post my workaround in case it helps anyone.
    One workaround is to use Coroutines and allow them to attempt to set your animator state for you. So instead of just using Animator.Play(), which sometimes misfires if it's called multiple times in same frame, you can call a coroutine and use the the target animator and the name of the target animator state as parameters. The coroutine can then check every frame to see if the animator has updated, and attempt to do so if it hasn't.

    Code (CSharp):
    1.  public IEnumerator SetAnimState(Animator targetAnimator, string targetAnimState)
    2. {
    3.         float timeOut = 1f;
    4.         //If animator is not playing a state called targetAnimState,
    5.         while(!targetAnimator.GetCurrentAnimatorStateInfo(0).IsName(targetAnimState))
    6.         {
    7.             //Play it
    8.             targetAnimator.Play(targetAnimState);
    9.  
    10.             timeOut -= Time.deltaTime;
    11.             if(timeOut <= 0)
    12.                 yield break;
    13.            
    14.             yield return 0;
    15.         }
    16. }
    You just need to be careful if you are going to call the same coroutine for the SAME animator more than once a frame though. If that might happen, it might be best to call the coroutine by assigning it to a Coroutine variable so you can stop the first instance and then re-start it when it's called. You could do this via a method.

    Code (CSharp):
    1. public Coroutine setAnimCoroutine;
    2.  
    3. public void SetAnimatorCoroutine(Animator targetAnimator, string targetAnimState)
    4. {
    5.      if(setAnimCoroutine != null)
    6.             StopCoroutine(setAnimCoroutine);
    7.  
    8.      setAnimCoroutine = StartCoroutine(SetAnimState(targetAnimator, targetAnimState));
    9. }
    If you will be calling this method for multiple animators multiple times a frame, you may need to have multiple Coroutine variables and pass the needed one as a parameter to the method for finer control.

    Not the most elegant solution I guess, nor am I sure if this causes any other problems on the back end, but unless you're doing something really complicated it should work.
     
    VolodyaSaiyajin and AsaEx like this.