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

Coroutines and such

Discussion in 'Scripting' started by Foestar, Aug 20, 2017.

  1. Foestar

    Foestar

    Joined:
    Aug 12, 2013
    Posts:
    350
    Howdy howdy. So basically I've been reading up on Coroutines ever since I decided to code in CSharp rather than Java. And from what I gather I've gotten it down so far. But one thing is kinda bothering me and I'm not sure why it's happening the way it's happening. So I created my IEnumerator called StaminaRecharge like so.
    Code (CSharp):
    1.  
    2. IEnumerator StaminaRecharge()
    3.     {
    4.         yield return new WaitForSeconds(2);    //Wait 2 seconds before charging
    5.         CurrentStam += 1;
    6.     }
    7.  
    Then I declared this to go off when the character isn't running and made sure the Coroutine wasn't running when the character was like so.

    Code (CSharp):
    1.  
    2. if (Input.GetKey(KeyCode.LeftShift) && Walking)
    3.         {
    4.             StopAllCoroutines();
    5.             CurrentStam -= 1;
    6.         }
    7.         else
    8.         {
    9.             StartCoroutine(StaminaRecharge());
    10.         }
    11.  
    So as to clarify what the Walking is it's a bool based off whether the player is moving or not that I started at the beginning of my code and works fine. And CurrentStam is a float also defined at the beginning along with a MaxStam. I just thought I'd mention that so no one is wondering what that even is. BUT, from what I gather Coroutines are used in CSharp as a generally procedural task that you normally couldn't do within a single frame? Have I got that right? It happens, pauses, then continues its task.

    So my issue I was having is when I do the above code but with a specific Coroutine stop like
    Code (CSharp):
    1.  
    2. StopCoroutine(StaminaRecharge());
    3.  
    it works, but it's delayed on stopping and has a weird effect of trying to both recharge and drain the stamina as if the Coroutine isn't stopping fast enough. And when I watch my variables/floats I can see that the numbers are immediately trying to drain when the player running continues but the recharge is still happening for like half a second to a full second before it stops and the drain goes uncontested. Not really sure why it's doing this and I didn't know if maybe i'm not fully understanding how it works within the frame. But if I stop ALL coroutines like the original above it works exactly as I want it to with no delay.
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Sounds like you are starting the coroutine every frame. You normally only want to have one version of a coroutine running at a time.
     
    TaleOf4Gamers likes this.
  3. Foestar

    Foestar

    Joined:
    Aug 12, 2013
    Posts:
    350
    Could be, that would do it. But I'm not sure how it would be doing that since it's within the parameters of keypress'. It's almost as if both parts of the above if statement are happening for a brief period in between the switch and that's pretty much what I've been trying to figure out. It is all within the update function, so maybe that would be why for some reason. But then why does stop all coroutines work and not specific without delay. Hmmm, still looking into it.
     
  4. johne5

    johne5

    Joined:
    Dec 4, 2011
    Posts:
    1,133
    create a bool to keep track of the active Coroutine

    Code (CSharp):
    1. private bool StaminaRechargeIsRunning = false;
    2.  
    3. IEnumerator StaminaRecharge()
    4.     {
    5.         StaminaRechargeIsRunning = true;
    6.         CurrentStam += 1; // moved this up, it might fix the stop Coroutine delay issue
    7.         yield return new WaitForSeconds(2);    //Wait 2 seconds after charging
    8.         StaminaRechargeIsRunning = false;
    9.     }
    10.  
    11.  
    12.  
    13. if (Input.GetKey(KeyCode.LeftShift) && Walking)
    14.     {
    15.         StopAllCoroutines(); //this might not be needed anymore
    16.         CurrentStam -= 1;
    17.     }
    18.     else
    19.     {
    20.         if(StaminaRechargeIsRunning == false)
    21.         {
    22.             StartCoroutine(StaminaRecharge());
    23.         }
    24.     }
     
  5. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    I'm not a super big fan of StopAllCoroutines() since that's kind of a brute force way of doing things (plus it can screw you up if you decide to add another coroutine to the script that you do not want stopped). Prefer this version instead:

    Code (csharp):
    1.  
    2. Coroutine staminaCoroutine = StartCoroutine(StaminaRecharge());
    3. ....
    4. StopCoroutine(staminaCoroutine);
    5.  
    You can even kill two birds with one stone and check if staminaCoroutine != null to check if the coroutine's already running, and if not, start it.
     
    Kiwasi likes this.
  6. Foestar

    Foestar

    Joined:
    Aug 12, 2013
    Posts:
    350
    Yeah I tried that and still have a bool in called "AbleToRun". But I still got the result as if it were overlapping for like 1-2 seconds before cutting off the coroutine as intended. Not sure why.

    Yeah, I feel like it will pose a problem in the near future which is why I wanted to see if I was doing it wrong and get it straight using only a single direct stop. So far I still haven't managed to get the code to work without the overlap.
    Code (csharp):
    1.  
    2. StopCoroutine(StaminaRecharge());
    3.  
    But, I never thought to check if and when it was running. That will help see how long it continues past the intended period. I'm gonna start putting in debug log's and such to filter it out and find the direct source. It could be the delay maybe in my Stamina Recharge itself causing some form of slow ending process maybe?
     
  7. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    OK, when you call StaminaRecharge(), you get back a new iterator. So StopCoroutine(StaminaRecharge()) won't work because you keep generating a new iterator.

    That's why you need to pass in staminaCoroutine, which is the return value from the last time you started the coroutine.

    Also, in your original post, you showed this code:

    Code (csharp):
    1.  
    2. if (Input.GetKey(KeyCode.LeftShift) && Walking)
    3.         {
    4.             StopAllCoroutines();
    5.             CurrentStam -= 1;
    6.         }
    7.         else
    8.         {
    9.             StartCoroutine(StaminaRecharge());
    10.         }
    11.  
    12.  
    If this is in an Update() function, then what's happening is you're calling StartCoroutine(StaminaRecharge()) every frame (as long as you don't hold down shift).

    So in the end, you have who knows how many overlapping coroutines running, and I'm sure this is likely what's causing you headaches.

    That's why you need to check to make sure you don't already have a coroutine running before you start a new one.
     
    Kiwasi likes this.
  8. Foestar

    Foestar

    Joined:
    Aug 12, 2013
    Posts:
    350
    For anyone wondering this is the actual full code that I'm currently using to make it work. But replace

    Ohhhhhhhhhhh! That makes a ton more sense now. When you put it like that I feel kinda derp. Got it though! Much thanks!