Search Unity

CoRoutines and Waiting

Discussion in 'Getting Started' started by 97CraigSanto, May 20, 2016.

  1. 97CraigSanto

    97CraigSanto

    Joined:
    Feb 22, 2016
    Posts:
    24
    So, I thought I knew what I was doing with Coroutines and WaitForSeconds, but I guess I was wrong.

    My intent was to do staggered displaying , which I thought to accomplish through yields and coroutines. But, and I guess I should've seen it coming, coroutines run alongside the rest of the code.

    My initial idea was to then string the Coroutines together. Have the yield do the pausing, then move on to the next. The problem then, is, that even if I've staggered the displaying properly, the rest of the code will continue moving along.

    So then, I think "I can make the method that called the readout method a coroutine," but then that logic quickly turns everything else into a coroutine calling another coroutine. There's gotta be a better way...

    Thanks in advance for any help on this one!

    Oh, and a bit of a bonus question: A part of my code likes to run before the new scene can fully load, causing null errors. How can I tell my code to sit tight in this situation?
     
  2. jhocking

    jhocking

    Joined:
    Nov 21, 2009
    Posts:
    814
    Literally all you said as a description of what you're trying to do is:

    What does "staggered displaying" mean? Before we can help you need to tell us what you're trying to do. Also you should probably post the code you wrote so we can see what's wrong with it.
     
    Kiwasi likes this.
  3. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    I agree with @jhocking; haven't a clue what you're talking about. However, I can maybe try for the bonus points:

    You just need to more carefully think about how to divide up your startup code. Things that grab references to other things (calls to GetComponent, GameObject.Find, etc.) should go in Awake. You shouldn't attempt to actually do anything with those references at that point; just tuck them away. Then, actually doing stuff that needs to happen at startup should go in Start. All your Awake methods run before any of your Start methods, so by the time you get to Start, you shouldn't have any null references.

    (You'll probably want to bookmark this important page of the manual.)
     
  4. MikeTeavee

    MikeTeavee

    Joined:
    May 22, 2015
    Posts:
    194
    "Staggered display" sounds like you're trying to trying to make some code to delay the scene load?

    That can be done with random range and an if-statement checking if a certain amount of time has gone by. All that can be coded in Update() without the use of coroutines.
     
    Last edited: May 20, 2016
    JoeStrout likes this.
  5. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Word!
     
  6. 97CraigSanto

    97CraigSanto

    Joined:
    Feb 22, 2016
    Posts:
    24
    Thanks for all the responses!

    I can see how my original post was unclear. Let me clarify: I simply meant seconds of delay between displaying information to players. And that info comes in the form of simple UI elements and text. Nothing nearly as fancy as you guys were cooking up!
     
  7. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    So what's the problem with:

    One routine or many, yeah, the rest of the code continues — which is the whole point of Coroutines (or, with simple if-blocks in the Update loop as @MikeTeavee suggested). This is a Good Thing. I doubt you'd want your game to freeze while information is displayed.

    So, what is the real problem you're trying to solve here?
     
  8. 97CraigSanto

    97CraigSanto

    Joined:
    Feb 22, 2016
    Posts:
    24
    I do want everything to freeze while the information is being displayed - allow me to explain:

    The game I'm trying to make is a fully UI based game. I guess it could be called a card game. At this point, everyone's in and the cards have been dealt out. Before the game starts, I want to tell everyone that the game has started, show them what the host has selected, and then show them their current card.

    And beyond this issue, everyone's turn is timed - and having everyone go together, or during the initial readout, it a no-go.

    Now that I think about it, I was going to use a timer for the actual turns... could that work here, too? Something I can add seconds to, let tick down to zero while a message displays, rinse and repeat? Or would that crash unity with an infinite while loop? I've had that happen before.

    Thanks again for the responses. Help is greatly appreciated.
     
  9. 97CraigSanto

    97CraigSanto

    Joined:
    Feb 22, 2016
    Posts:
    24
    Update: Tried making a simple timer object. Made Unity blow up. Can somebody tell me what I'm doing wrong?

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class Timer : MonoBehaviour {
    5.     private static float timeLeft;
    6.  
    7.     // Use this for initialization
    8.     void Start () {
    9.         DontDestroyOnLoad(gameObject);
    10.         timeLeft = 0.0f;
    11.         InvokeRepeating("countdown", 0, 0.2f);
    12.     }
    13.  
    14.     public static void addTime(int time)
    15.     {
    16.         timeLeft += (float)time;
    17.     }
    18.  
    19.     public static void addTime(float time)
    20.     {
    21.         timeLeft += time;
    22.     }
    23.  
    24.     public static float getTime()
    25.     {
    26.         return timeLeft;
    27.     }
    28.    
    29.     private void countdown()
    30.     {
    31.         if(timeLeft > 0)
    32.         {
    33.             timeLeft -= .2f;
    34.             if (timeLeft < 0) timeLeft = 0;
    35.         }
    36.         if(GameUI.instance != null)
    37.         {
    38.             GameUI.instance.updateTimer(timeLeft);
    39.         }
    40.     }
    41. }
    Basically, my intent is to have a clock object that ticks down to zero and stays there. Time can be inserted into it as needed.

    An example of where I call it:
    Code (CSharp):
    1. private void safetyDelay(int delay)
    2.     {
    3.         Timer.addTime(delay);
    4.         while(Timer.getTime() > 0)
    5.         {
    6.             //do nothing
    7.         }
    8.     }
    And when I use said clock, my intent is to throw time into it, wait until it time runs out, and then move on.

    EDIT: Started using InvokeRepeating instead of update. I thought that would do it, but it seems I was wrong. I'm now at a loss.
     
    Last edited: May 21, 2016
  10. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    21,141
    Unity is a single-threaded engine. You created a delay function that ties up execution inside a while loop until a specific amount of time has passed, but because the thread that calls the Update() function is the main thread you're effectively pausing the entire engine along with the script.
     
    Last edited: May 21, 2016
  11. 97CraigSanto

    97CraigSanto

    Joined:
    Feb 22, 2016
    Posts:
    24
    Thank you! I see (well enough, at least) that this isn't the right way to handle the situation.

    But there has to be a way to say "let me display this information for x seconds before we move this thing along," and "you have x seconds to make a choice" - I feel like I'm not doing anything too out of the ordinary there.

    Is the answer really just having the code jump into a coroutine and then continuing execution at the coroutine's end? That feels wrong to me...

    EDIT: Wait a minute, how do coroutines fit in with threading, then? What would happen if I somehow invoke a coroutine into the timer? Like, wrap the invokerepeating call in a coroutine? Or put a coroutine in the repeatedly-invoked method? Hmm... I imagine that wouldn't do it. Something would have to be called, but I'd probably still be stuck in the loop.
     
    Last edited: May 21, 2016
  12. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    OK, here's my advice. I'm only going to throw this out once; you can take it or leave it (one of which leads to suffering, but hey, that's your choice ;).)

    Don't use coroutines. Don't use Invoke or InvokeRepeating. Keep it simple: use the Update method, and a variable or two to keep track of what state your game is in, and how long you've been in that state. Here's a quick example to get you started; adapt it to your own purposes.

    Code (CSharp):
    1. public class StateDemo : MonoBehaviour
    2. {
    3.     public UI.Text display;
    4.  
    5.     enum State {
    6.         Idle,
    7.         ShowingFoo,
    8.         ShowingBar,
    9.         ShowingBaz
    10.     }
    11.    
    12.     State state = State.Idle;    // what state we're in
    13.     float stateStart;        // Time.time at which we entered the current state
    14.     float nextStateTime;    // Time.time at which we should go to the next state
    15.     float timeInState { get { return Time.time - stateStart; } }
    16.    
    17.     void Update() {
    18.         if (Time.time > nextStateTime) NextState();
    19.     }
    20.  
    21.     void NextState() {
    22.         // Advance to the next state, depending on what state we're in.
    23.         switch (state) {
    24.         case State.Idle:
    25.             Enter(State.ShowingFoo);
    26.             break;
    27.         case State.ShowingFoo:
    28.             Enter(State.ShowingBar);
    29.             break;
    30.         case State.ShowingBar:
    31.             Enter(State.ShowingBaz, 5.0f);
    32.             break;
    33.         case State.ShowingBaz:
    34.             Enter(State.Idle, 0.5f);
    35.             break;
    36.         }
    37.     }
    38.    
    39.     void Enter(State newState, float duration=3) {
    40.         state = newState;
    41.         stateStart = Time.time;
    42.         nextStateTime = Time.time + duration;
    43.         switch (state) {
    44.         case State.ShowingFoo:
    45.             display.text = "Foo!";
    46.             break;
    47.         case State.ShowingBar:
    48.             display.text = "Bar!";
    49.             break;
    50.         case State.ShowingBaz:
    51.             display.text = "Baz!";
    52.             break;
    53.     }
    54. }
    That's untested code, but should give you the idea at least. Time.time and if statements are all you need; just check the clock on each frame (which is what the Update method is for), and do whatever's appropriate for that time.
     
    Ryiah likes this.
  13. jhocking

    jhocking

    Joined:
    Nov 21, 2009
    Posts:
    814
    I would tend to agree; coroutines are an ideal solution if you wanted the rest of the game to keep running while the display changes (eg. a background animation) but for this situation it could just make it harder to reason about your code.

    Although, thinking about it more, I would probably approach this with both a coroutine AND a gamestate that Update() responds to. Basically, I'd put a state conditional in just like @JoeStrout did, but I wouldn't have a state for every single message, but rather a general "messages displaying" state. Then the first line of the coroutine would set to the messages state, while the last line changes back to another state. That way the rest of the game wouldn't do anything while the coroutine is running, and I'd get the simple to write (and, importantly, simple to adjust later!) sequence using a series of Wait commands.
     
  14. 97CraigSanto

    97CraigSanto

    Joined:
    Feb 22, 2016
    Posts:
    24
    I'll give this a shot, thanks guys!

    I like the idea of using a coroutine to go through the initial display process as a single state. But @jhocking , I'm not sure how exactly this delays the rest of the game until the display is taken care of. I feel like in the main part of my code, I'd still be stuck in a while loop waiting for the green light to move on, which crashes the engine. Can you further explain how this method prevents that crash?

    EDIT: Unless, in my end-of-display call, make a call to a method that continues on with the code?
     
  15. jhocking

    jhocking

    Joined:
    Nov 21, 2009
    Posts:
    814
    Everything in a coroutine runs in sequence over time. So put state=State.Display; as the first line of the function, and put state=State.Idle; as the last line in the function.
     
  16. 97CraigSanto

    97CraigSanto

    Joined:
    Feb 22, 2016
    Posts:
    24
    Did it slightly differently, but it's working!

    Thank you all so much for the help! Watching those messages pop up was oddly suspenseful, but totally satisfying.