Search Unity

Custom Sprite Animation Script: Trouble with Coroutines

Discussion in 'Animation' started by MoonJellyGames, Jul 20, 2018.

  1. MoonJellyGames

    MoonJellyGames

    Joined:
    Oct 2, 2014
    Posts:
    331
    Hey all,

    I'm starting my second project, and I decided that I'd like to try using my own animation script instead of using Unity's built-in system. I started doing this near the end of my last project as it just seemed easier for what I was doing. Those animated objects were simple, and tended to either play one animation just once, or loop endlessly. Now that I'm trying to apply that same code to a slightly more complex object, I'm running into a problem.

    When you see my code, you're probably going to wonder why I have a walk animation for each arm. And it's not what you think-- well, it kinda is-- but just trust me that it's appropriate for my project.

    Here's the AnimationScript code

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections;
    4.  
    5. public class AnimationScript : MonoBehaviour
    6. {
    7.     public Sprite[] frames;
    8.     public bool loop = false;
    9.     public float frameDelay = 0.13f;
    10.     private SpriteRenderer spriteRend;
    11.     private int currentFrame;
    12.     public bool playing = false;
    13.     public bool stopped = false;    // flag set to interrupt animation; restart from beginning
    14.     public bool paused = false;
    15.     public int cachedFrame = 0;     // save the index of the paused frame. May be used in a "Resume" function
    16.     public bool playOnStart = false;
    17.     public bool disableRendererOnFinish = false;    // disable the sprite renderer when the animation completes
    18.  
    19.     public void Awake()
    20.     {
    21.         if (GetComponent<SpriteRenderer>())
    22.             spriteRend = GetComponent<SpriteRenderer>();
    23.         else
    24.             Debug.LogError("SpriteRenderer missing!");
    25.  
    26.         if (playOnStart)
    27.             Play();
    28.     }
    29.  
    30.     public void Play()
    31.     {
    32.         if (!playing)
    33.         {
    34.             stopped = false;
    35.             paused = false;
    36.             StartCoroutine(PlayAnim());
    37.         }
    38.     }
    39.  
    40.     public void Pause()
    41.     {
    42.         if (playing)
    43.         {
    44.             paused = true;
    45.             playing = false;
    46.         }
    47.     }
    48.  
    49.    
    50.     //public void Resume()
    51.     //{
    52.     //    if (!playing)
    53.     //    {
    54.     //        paused = false;
    55.     //       // StartCoroutine(PlayAnim(cachedFrame));
    56.     //    }
    57.     //}
    58.  
    59.     public void Stop()
    60.     {
    61.         stopped = true;
    62.         paused = false;
    63.         playing = false;
    64.     }
    65.  
    66.     private IEnumerator PlayAnim()
    67.     {
    68.         Debug.Log("PlayAnim");
    69.         if (!playing)
    70.         {
    71.             playing = true;
    72.             //spriteRend.enabled = true; // make sure sprite renderer is enabled
    73.  
    74.             currentFrame = 0;
    75.  
    76.             while (currentFrame < frames.Length && !stopped && !paused)
    77.             {
    78.                 yield return new WaitForSeconds(frameDelay);
    79.  
    80.                 if (!paused && !stopped)
    81.                 {
    82.                     spriteRend.sprite = frames[currentFrame];
    83.                 }
    84.                 else
    85.                     yield break;
    86.  
    87.                 // if the last frame is already displayed, go back to the first
    88.                 if (currentFrame >= frames.Length - 1)
    89.                 {
    90.                     currentFrame = 0;
    91.  
    92.                     if (!loop)
    93.                         Stop();
    94.                 }
    95.                 else
    96.                     currentFrame++;
    97.             }
    98.         }
    99.     }
    100. }
    101.  
    And here is where the animations are being played in the PlayerController script. This snippet is in Update():

    Code (CSharp):
    1.             if (currentSpeed != 0)
    2.             {
    3.                 if (!leftArm.walkAnimation.playing)
    4.                     leftArm.walkAnimation.Play();
    5.                 if (!rightArm.walkAnimation.playing)
    6.                     rightArm.walkAnimation.Play();
    7.             }
    8.             else
    9.             {
    10.                 leftArm.walkAnimation.Stop();
    11.                 rightArm.walkAnimation.Stop();
    12.             }
    When I make the character walk, the animations play as they should, but if I change directions, the animation speeds up and gets a little janky. This is undoubtedly because the coroutine is running multiple times overtop of eachother, and this is being allowed because currentSpeed never equals 0. Eventually, I'd have an idle animation that would play in that case, but that's another issue. I've tried using flags to check if a coroutine is running, found that it didn't work, then did some reading and found that this shouldn't be surprising because my coroutine uses WaitForSeconds().

    As is always the case for me, I've come up with a possible solution mid-way through asking for help. I realized that the heart of my problem is my inability to stop an animation. I'll have to keep a reference of what the current playing animation is so that I can stop it, but I don't think that will be an issue. Here is my updated AnimationScript which seems to work.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections;
    4.  
    5. public class AnimationScript : MonoBehaviour
    6. {
    7.     public Sprite[] frames;
    8.     public bool loop = false;
    9.     public float frameDelay = 0.13f;
    10.     private SpriteRenderer spriteRend;
    11.     private int currentFrame;
    12.     public bool playing = false;
    13.     public bool stopped = false;    // flag set to interrupt animation; restart from beginning
    14.     public bool paused = false;
    15.     public int cachedFrame = 0;     // save the index of the paused frame. May be used in a "Resume" function
    16.     public bool playOnStart = false;
    17.     public bool disableRendererOnFinish = false;    // disable the sprite renderer when the animation completes
    18.     private Coroutine animCoroutine;
    19.  
    20.     public void Awake()
    21.     {
    22.         if (GetComponent<SpriteRenderer>())
    23.             spriteRend = GetComponent<SpriteRenderer>();
    24.         else
    25.             Debug.LogError("SpriteRenderer missing!");
    26.  
    27.         if (playOnStart)
    28.             Play();
    29.     }
    30.  
    31.     public void Play()
    32.     {
    33.         // should probably do a StopCoroutine here, just to be safe
    34.         if (!playing)
    35.         {
    36.             stopped = false;
    37.             paused = false;
    38.             animCoroutine = StartCoroutine(PlayAnim());
    39.             //StartCoroutine(PlayAnim());
    40.         }
    41.     }
    42.  
    43.     public void Pause()
    44.     {
    45.         if (playing)
    46.         {
    47.             paused = true;
    48.             playing = false;
    49.         }
    50.     }
    51.  
    52.    
    53.     //public void Resume()
    54.     //{
    55.     //    if (!playing)
    56.     //    {
    57.     //        paused = false;
    58.     //       // StartCoroutine(PlayAnim(cachedFrame));
    59.     //    }
    60.     //}
    61.  
    62.     public void Stop()
    63.     {
    64.         if (animCoroutine != null)
    65.             StopCoroutine(animCoroutine);
    66.         stopped = true;
    67.         paused = false;
    68.         playing = false;
    69.     }
    70.  
    71.     private IEnumerator PlayAnim()
    72.     {
    73.         Debug.Log("PlayAnim");
    74.         if (!playing)
    75.         {
    76.             playing = true;
    77.             //spriteRend.enabled = true; // make sure sprite renderer is enabled
    78.  
    79.             currentFrame = 0;
    80.  
    81.             while (currentFrame < frames.Length && !stopped && !paused)
    82.             {
    83.                 yield return new WaitForSeconds(frameDelay);
    84.  
    85.                 if (!paused && !stopped)
    86.                 {
    87.                     spriteRend.sprite = frames[currentFrame];
    88.                 }
    89.                 else
    90.                     yield break;
    91.  
    92.                 // if the last frame is already displayed, go back to the first
    93.                 if (currentFrame >= frames.Length - 1)
    94.                 {
    95.                     currentFrame = 0;
    96.  
    97.                     if (!loop)
    98.                         Stop();
    99.                 }
    100.                 else
    101.                     currentFrame++;
    102.             }
    103.         }
    104.     }
    105. }
    106.  
    So, the remaining question is: Do any of you more experienced and skilled programmers see any fatal flaws with this?

    Thanks for reading. :)