Search Unity

Resolved Animation clips not starting over as expected in my Animator script

Discussion in 'Animation' started by PaperMouseGames, Apr 6, 2022.

  1. PaperMouseGames

    PaperMouseGames

    Joined:
    Jul 31, 2018
    Posts:
    434
    Hi there, I'm working on using a script to change my animator's states because I don't think transitions would work great for my current system.

    Basically I have an animator controller with a bunch of different states, each one is a 2D sprite animation. What I want is, when the player clicks, the animation gets passed in to a coroutine, and the coroutine should stop currently playing animations and play the new one that was passed in from the beginning.

    Here is my code:

    Code (CSharp):
    1. public class ActionAnimator : MonoBehaviour
    2.     {
    3.         [SerializeField] Animator animator;
    4.  
    5.         string currentState;
    6.  
    7.         IEnumerator changeAnimationStateCoroutine;
    8.      
    9.         public void ChangeAnimationState(string newState, GameObject originObject, GameObject targetObject)
    10.         {        
    11.             if (changeAnimationStateCoroutine != null)
    12.             {
    13.                 StopCoroutine(changeAnimationStateCoroutine);
    14.             }
    15.             changeAnimationStateCoroutine = ChangeAnimationStateCoroutine(newState, originObject, targetObject);
    16.             StartCoroutine(changeAnimationStateCoroutine);
    17.         }
    18.         private IEnumerator ChangeAnimationStateCoroutine(string newState, GameObject originObject, GameObject targetObject)
    19.         {
    20.             IdleState();
    21.  
    22.             currentState = newState;
    23.  
    24.             SetUpActionPosition(originObject, targetObject);
    25.             Debug.Log("Playing currentState...");
    26.             animator.Play(currentState);        
    27.  
    28.             yield return new WaitForSeconds(animator.GetCurrentAnimatorStateInfo(0).length);
    29.             Debug.Log("Finished currentState");
    30.             IdleState();
    31.         }
    32.         private void SetUpActionPosition(GameObject originObject, GameObject targetObject)
    33.         {
    34.             if (originObject != null && targetObject != null)
    35.             {
    36.                 transform.position = targetObject.transform.position;
    37.             }        
    38.         }
    39.         private void IdleState()
    40.         {
    41.             Debug.Log("IdleState");
    42.             currentState = null;
    43.             animator.Play("idle");
    44.         }
    45.     }
    So this is mostly working. When I click the button, the coroutine starts and plays the desired animation. The idle state is a state with no animation by the way, meant to act as a reset of sorts.

    The issue is when I click the button again, before the animation is finished, it just goes to the idle state and stays there instead of restarting the animation I passed in.

    The strange part is I know the coroutine is working through the logic because when I double click I get the debugs as follows:

    "IdleSate"
    "Playering currentState"
    "IdleSate"
    "Playering currentState"
    "Finished currentState"

    So that is behaving as expected, the double click interrupts the first coroutine and starts it again, but on the double click, the animation just stays in the idle state instead of restarting the passed in state, even though it is going through the
    animator.Play(currentState)
    line.

    Any ideas about what's going on?
     
  2. Kybernetik

    Kybernetik

    Joined:
    Jan 3, 2013
    Posts:
    2,570
    There are two issues here:
    • Calling Play multiple times in the same frame will execute the first one only and ignore the rest (without giving you any indication that it's ignoring you unless you wait a frame and check).
    • Calling Play does nothing immediately so getting the state info right afterwards is actually the info of the previous state. Then next time the Animator updates it will probably change to the new state as long as you haven't already told it to Play something else that frame.
    This page explains them in more detail.

    So if you start Idle, it doesn't count the first IdleState() which calls Play("idle") so the Play(currentState) actually works. But if you are in another state, the Play("idle") takes effect and it ignores the Play(currentState) which happens right afterwards.

    The second issue means that you're likely waiting for the duration of the wrong animation. The simple workaround is to call Play, yield return null to wait a frame, check the current state, if it's now in the target state you can wait for its duration, otherwise call Play again, wait another frame, check again, and keep doing that until the Animator stops ignoring you.
     
  3. PaperMouseGames

    PaperMouseGames

    Joined:
    Jul 31, 2018
    Posts:
    434
    @Kybernetik

    Thanks! You got it, I just needed to wait. I think I knew that at some point but I honeslty don't work that much with Animator stuff so I might have forgotten.

    Since I'm using a coroutine it was an easy fix; Just added
    yield return new WaitForEndOfFrame();
    right before the line
    animator.Play(currentState);
    and it's worked exactly as intended now :)