Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Having trouble with Coroutines and WaitForSeconds...

Discussion in 'Scripting' started by tomtomeow, Aug 1, 2017.

  1. tomtomeow

    tomtomeow

    Joined:
    Jul 5, 2017
    Posts:
    14
    So my script is pretty straight forward:
    Code (csharp):
    1.  
    2. public void Speedboost(Rigidbody m_Rigidbody, float SpeedAmount)
    3.     {
    4.         m_Rigidbody.GetComponent<TankMovement>().m_Speed += SpeedAmount;
    5.         StartCoroutine(PowerUpTime());
    6.         m_Rigidbody.GetComponent<TankMovement>().m_Speed -= SpeedAmount;
    7.     }
    8.     private IEnumerator PowerUpTime()
    9.     {
    10.         yield return new WaitForSeconds(m_BoostTime);
    11.     }
    12.  
    After applying a certain speed to the object, I want it to wait a few seconds (3 in this case) before going back to the normal speed. What happens instead is that the speed doesn't change at all, as if the WaitForSeconds was given the number 0.
    My boost time is set to 3f which should be in seconds, I also tried multiplying by time.DeltaTime but it still won't work.
    What is the problem? do co routines work differently?
     
  2. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    The only time you can wait is INSIDE the coroutine. The way you have it right now, it increases speed, starts the coroutine, and decreases the speed all in one go. The coroutine starts, waits, then returns "m_BoostTime" later.

    You could do this:
    Code (CSharp):
    1. public void Speedboost(Rigidbody m_Rigidbody, float SpeedAmount) {
    2.     StartCoroutine(SpeedBoostCoroutine());
    3. }
    4. private IEnumerator SpeedBoostCoroutine() {
    5.     m_Rigidbody.GetComponent<TankMovement>().m_Speed += SpeedAmount;
    6.     yield return new WaitForSeconds(m_BoostTime);
    7.     m_Rigidbody.GetComponent<TankMovement>().m_Speed -= SpeedAmount;
    8. }
     
    Ryiah likes this.
  3. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    The problem is you're treating the function Speedboost as if it is a coroutine. Basically what will happen when you call Speedboost function....

    Increment the speed....
    Kick off the coroutine...
    Decrement the speed...

    That all happens in ONE frame.

    The next frame, PowerUpTime will pause for a bit, but it's not doing anything in that function aside from pausing.... i.e. it's not actually pausing anything.

    What you probably want to do is move your decrement (and possibly increment, just for readability, I guess) into the coroutine.. something like this:

    Code (csharp):
    1. public void Speedboost(Rigidbody m_Rigidbody, float SpeedAmount)
    2.     {    
    3.         StartCoroutine(PowerUpTime(m_Rigidbody, SpeedAmount));    
    4.     }
    5.     private IEnumerator PowerUpTime(Rigidbody m_Rigidbody, float SpeedAmount)
    6.     {
    7.         m_Rigidbody.GetComponent<TankMovement>().m_Speed += SpeedAmount;
    8.         yield return new WaitForSeconds(m_BoostTime);
    9.         m_Rigidbody.GetComponent<TankMovement>().m_Speed -= SpeedAmount;
    10.     }
    Note: I'm not really sure what your expectation is, I'm just saying this is how you would create the effect you're looking for of boosting, then going back to normal after a pause of whatever number of seconds.

    Edit: hah, I'm a step behind all day today, beaten to it. But I think one thing I'll point out is i think you'll need your parameters in the coroutine.
     
    jeffreyschoch and Ryiah like this.
  4. tomtomeow

    tomtomeow

    Joined:
    Jul 5, 2017
    Posts:
    14
    I think I understand co-routines better now, and why people say they happen "in the background" while the code is still running, thanks. What I am trying to do though is have the speed increase for a certain time (just like a speed power up you'd see in games) and then return to normal after the "power up" ends.
    I tried writing the code like this:
    Code (csharp):
    1.  
    2.  public void Speedboost(Rigidbody m_Rigidbody, float SpeedAmount)
    3.     {
    4.         m_Rigidbody.GetComponent<TankMovement>().m_Speed += SpeedAmount;
    5.         StartCoroutine(PowerUpTime(m_Rigidbody,SpeedAmount));
    6.     }
    7.     private IEnumerator PowerUpTime(Rigidbody m_Rigidbody, float SpeedAmount)
    8.     {
    9.         yield return new WaitForSeconds(m_BoostTime);
    10.         m_Rigidbody.GetComponent<TankMovement>().m_Speed -= SpeedAmount;
    11.     }
    12.  
    but this time speed boost stays forever, as if it doesn't ever finish the co routine! I'll try debugging this...
    EDIT: Strange, debugger shows that it doesn't even get to the line where the speed decreases, it does execute the line:
    Code (csharp):
    1.  yield return new WaitForSeconds(m_BoostTime); line"
    and then just skips to the end of the function, never seen this before.
     
  5. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    Are you in any way disabling or deleting the monobehaviour that started the coroutine?
     
    tomtomeow and cstooch like this.
  6. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    Are you sure it doesn't ever execute the line after this:
    Code (csharp):
    1. yield return new WaitForSeconds(m_BoostTime);
    If you didn't, put a Debug.Log statement after to check to see if it ever gets past that statement. If it doesn't, be very sure about what your m_BoostTime value is.. maybe it's huge?

    If that is all fine, and it DOES get past this line, then I'm wondering if you're accidentally re-triggering the coroutine over and over....how often are you calling "Speedboost" ? How are you calling it? Is it in an Update function or in some other way?

    Jeffrey has a great answer though if you're for sure not re-running the coroutine over and over, and the line after the yield statement is definitely not called.
     
  7. tomtomeow

    tomtomeow

    Joined:
    Jul 5, 2017
    Posts:
    14
    It's a very good point from both of you, but I don't think it's being called more than once, this is the only place I call it:
    Code (csharp):
    1.  
    2. void OnTriggerEnter(Collider col)
    3.     {
    4.         Rigidbody targetRigidbody = col.GetComponent<Rigidbody>();
    5.         if (targetRigidbody)
    6.         {
    7.             Speedboost(targetRigidbody, 20);
    8.             GameObject.Destroy(gameObject);
    9.         }
    10.     }
    11.  
    and I should've probably explained this before, but this entire script is running on a game object that is the "power up". I'm calling the speedboost once when this object collides with a player and right after that I destroy this gameobject, meaning it won't ever call this function again because the gameobject in which this script is attached to is no longer in the game.
     
  8. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    Ahh, then @jeffreyschoch wins the prize.. when you destroy the object the coroutine is attached to, you also destroy the coroutine. Might need to move the coroutine to the player or something that persists after this particular object is destroyed.
     
    jeffreyschoch likes this.
  9. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    4,597
    Yep, or try out the more effective coroutine asset which will avoid this issue. https://www.assetstore.unity3d.com/en/#!/content/54975
     
    cstooch likes this.
  10. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    Nice!

    Sorry to briefly derail the thread, but do you think the Pro version is worth purchasing? I am not sure if "You cannot query whether a coroutine is running" means you can't stop a coroutine with the which probably would make me want Pro, or if that just means you can stop a coroutine, but you can't tell if it's running (like how Unity works right now).
     
  11. tomtomeow

    tomtomeow

    Joined:
    Jul 5, 2017
    Posts:
    14
    Oh silly me... yes I am in fact, I completely forgot that it could be the reason that the co routine doesn't finish.
    Code (csharp):
    1.  
    2. if (targetRigidbody)
    3.         {
    4.             Speedboost(targetRigidbody, 20);
    5.             GameObject.Destroy(gameObject);
    6.         }
    7.  
    Okay I finally solved it!
    I had to move the GameObject.Destroy(gameObject); to the co routine as well, and disable the renderer instead so the power up still won't be visible after it's picked up. This all works now, @jeffreyschoch was indeed the one who solved it, but thanks to everyone!
    here's the finished solution:
    Code (csharp):
    1.  
    2. void OnTriggerEnter(Collider col)
    3.     {
    4.         Rigidbody targetRigidbody = col.GetComponent<Rigidbody>();
    5.         if (targetRigidbody)
    6.         {
    7.             Speedboost(targetRigidbody, 20);
    8.             gameObject.GetComponent<Renderer>().enabled = false;
    9.         }
    10.     }
    11.     public void Speedboost(Rigidbody m_Rigidbody, float SpeedAmount)
    12.     {
    13.         m_Rigidbody.GetComponent<TankMovement>().m_Speed += SpeedAmount;
    14.         StartCoroutine(PowerUpTime(m_Rigidbody,SpeedAmount));
    15.     }
    16.     private IEnumerator PowerUpTime(Rigidbody m_Rigidbody, float SpeedAmount)
    17.     {
    18.         yield return new WaitForSeconds(m_BoostTime);
    19.         m_Rigidbody.GetComponent<TankMovement>().m_Speed -= SpeedAmount;
    20.         GameObject.Destroy(gameObject);
    21.     }
    22.  
     
  12. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    4,597
    Based on the faq here http://trinary.tech/category/mec/ (scroll towards the bottom) there is a stopcoroutine type call. It doesn't state it is pro only. As far as pro worth purchasing, it does seem to have some extra features, so it's a matter if those are worth it. I have only used the free version myself.

    and glad you got this working OP!
     
    tomtomeow and cstooch like this.
  13. cstooch

    cstooch

    Joined:
    Apr 16, 2014
    Posts:
    354
    You should probably disable your collider too


    Sold... on the free version. That will do everything I need. Really, I'm just looking for the nice performance boost. Looks great, thanks for recommending that. I also like the "SlowUpdate".. that's a nice extra perk!
     
    Last edited: Aug 1, 2017
  14. jeffreyschoch

    jeffreyschoch

    Joined:
    Jan 21, 2015
    Posts:
    2,539
    Glad it all worked out while I was away getting lunch :D

    Just a tip, you can start a coroutine from any object, on any object, just with a reference to that MonoBehaviour. So if you did want to run the coroutine on the player component for instance, one option would be to (from the powerup script) call "playerScript.StartCoroutine(powerupCoroutine)", and then the coroutine would persist on the player even after the powerup is destroyed.

    That's not necessarily the best way to do things, but that is how it works so it's good to be aware of that.
     
    tomtomeow likes this.