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

The coroutine that ate my brain ...

Discussion in 'Scripting' started by marty, Feb 3, 2006.

  1. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    What is a coroutine, why would I want to use one, how do I make one? And, why can the Update function not be one?
     
  2. Marble

    Marble

    Joined:
    Aug 29, 2005
    Posts:
    1,268
    I could use some clarification on how this is used as well, in particular some actual examples on how coroutines are used in C#. I've read the manual section on it, but it would be no end of help to see how something simple like a yield statement is put into a method and used effectively through the process of a script.
     
  3. freyr

    freyr

    Joined:
    Apr 7, 2005
    Posts:
    1,148
    Done.

    A coroutine is a method that can be temporarily paused by yielding back to the caller, which in Unity's case is usually the engine's main loop. The main loop parses the value returned from the yield statement to determine when the coroutine wants to be called again. The default is to wait until next frame.

    You tell the engine to start a new coroutine by calling StartCoroutine giving it the name of the method as parameter.

    Coroutines are useful for handling behaviour that progresses through different steps over time. It is possible to do using a state variable and a switch or set of if statements inside Update, but much more readable if done with a coroutine.

    Here's a simple pseudo-code to illustrate the difference:
    Code (csharp):
    1.  
    2. // Example A - using plain old state variables.
    3. // Assume this script is attached to a GUI object (either GuiText or GuiTexture)
    4. // When the user clicks the object, the following happens:
    5. //    a. It prints "one" to the console and waits until the next frame
    6. //    b. It prints "two" and then waits for one second
    7. //    c. It prints "three"
    8.  
    9. var state = 0; // 0 - not running
    10. var timeVariable = 0.0; // used for keeping track of time
    11.  
    12. function OnMouseUp() {
    13.    DoComplexAction();
    14. }
    15.  
    16. function DoComplexAction() {
    17.     state = 1;
    18. }
    19.  
    20. function Update() {
    21.  
    22.   if(state == 1) { // first step
    23.     print("one");
    24.     state ++;
    25.   }
    26.   else if (state == 2) { // second step
    27.     print("two");
    28.     timeVariable = Time.time;
    29.     state ++;
    30.   }
    31.   else if (state == 3) { // third step is just waiting until some time has passed
    32.      if( Time.time-timeVariable > 1.0)
    33.        state ++;
    34.   }
    35.   else if (state == 4) { // last step
    36.     print("three");
    37.     state = 0;
    38.   }
    39. }
    40.  
    As you can see in the example above, quite a lot of code goes into just keeping track of where we are between calls to Update.

    Here's the same thing using a coroutine:
    Code (csharp):
    1.  
    2. // Example B - using a coroutine.
    3. // Assume this script is attached to a GUI object (either GuiText or GuiTexture)
    4. // When the user clicks the object, the following happens:
    5. //    a. It prints "one" to the console and waits until the next frame
    6. //    b. It prints "two" and then waits for one second
    7. //    c. It prints "three"
    8.  
    9. function OnMouseUp() {
    10.    StartCoroutine("DoComplexAction");
    11. }
    12.  
    13. function DoComplexAction() {
    14.   print("one");
    15.   yield;
    16.   print("two");
    17.   yield WaitForSeconds(1.0);
    18.   print("three");
    19. }
    20.  
    Update can not be a coroutine because that would cause a new coroutine to be started every frame, which probably isn't what you wanted.

    By the way, there is one subtle difference between the behaviour of the two examples above. Can anyone guess what it is?
     
  4. dan

    dan

    Joined:
    Jul 22, 2005
    Posts:
    151
    I think...The second sample always prints all the steps if multiple mouse up events are detected, but the first sample will be restarted and will not finish the loop if additional mouse up events are detected.

    Am I right? :D
     
  5. freyr

    freyr

    Joined:
    Apr 7, 2005
    Posts:
    1,148
    Yup... I'll give a beer next time you are in Copenhagen... :p
     
  6. dan

    dan

    Joined:
    Jul 22, 2005
    Posts:
    151
    D'oh. That's a long trip for free beer... :(



    Wait, what kind of beer are we talking here? Being that close to Germany you must have access to some really good beer. It might be worth the trip! :D
     
  7. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    Can coroutines be nested?

    That is to say, can a coroutine call a coroutine?
     
  8. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Yes. You can chain coroutines and you can also just let them execute in paralell.
    (Note that this has nothing to do with threading, so there are no weird threading issues to take care of)

    // Here is how to chain a coroutine.
    Code (csharp):
    1.  
    2. yield StartCoroutine ("DoSomething");
    3. print(Time.time);// Time will be 10
    4.  
    5. function DoSomething ()
    6. {
    7.    yield StartCoroutine("DoSomethingElse");
    8.    yield WaitForSeconds(5);
    9. }
    10. function DoSomethingElse ()
    11. {
    12.   yield WaitForSeconds(5);
    13. }
    14.  

    Code (csharp):
    1.  
    2. yield StartCoroutine ("DoSomething");
    3. print(Time.time);// Time will be 5
    4.  
    5. function DoSomething ()
    6. {
    7.    StartCoroutine("DoSomethingElse");
    8.    // This line is executed immediately
    9.    // since we are not yielding StartCoroutine
    10.    yield WaitForSeconds(5);
    11. }
    12. function DoSomethingElse ()
    13. {
    14.   yield WaitForSeconds(5);
    15. }
    16.  
     
  9. marty

    marty

    Joined:
    Apr 27, 2005
    Posts:
    1,170
    Boy, these coroutines are intriguing stuff. Kind of like threading, but without the fuss and muss.

    So, to make sure I have this right - I can launch a coroutine (i.e. using StartCoroutine) and have it process in paralell. Or, I can launch it with yield (i.e. yield StartCoroutine) and have it process before program flow proceeds from the launch point. Is that right?
     
  10. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Exactly.

    And yes, coroutines are insanely cool.
    Say good bye to state machines.