Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Queues and Coroutines

Discussion in 'Scripting' started by deLord, Jan 17, 2015.

  1. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    Hey

    I tried to implement a short message queue where I can add messages via a button (1 click = 1 msg to add). These messages are stored in smsQueue of type Queue<ShortMessage>.

    In Start() I will call my forever looping coroutine displayShortMessageLoop.

    Code (CSharp):
    1. /** add sms to the list of sms to be displayed */
    2. public void addShortMessage(ShortMessage sms){
    3.     smsQueue.Enqueue(sms);
    4. }
    5.  
    6. /** while true loop that displays a short message from smsQueue that disappears after a few seconds */
    7. private IEnumerator displayShortMessageLoop(){
    8.     ShortMessage sms;
    9.     while(true){
    10.         if(smsQueue.Count == 0){
    11.             yield return new WaitForSeconds(0.1f)
    12.             continue; // check again in 0.1s if no message present
    13.         } else {
    14.             if(lastSMSSpawnTime + 2.9f >= Time.time ) yield return new WaitForSeconds((lastSMSSpawnTime + 2.9f) - Time.time);
    15.             Debug.Log ("smsQueue " + smsQueue.Count);
    16.             sms = smsQueue.Dequeue();
    17.             // do a lot of stuff, set text and image
    18.             smsGO.GetComponent<MoveOverTime>().StartCoroutine("begin");
    19.         }
    20.     }
    21. }
    MoveOverTime looks like this
    Code (CSharp):
    1. public override void Start () {
    2.     base.Start();
    3.     originalPos = gameObject.transform.position;
    4. }
    5.  
    6. public IEnumerator begin() {
    7.     foreach(MoveOverTimeContainer cont in moveDirections){
    8.         yield return new WaitForSeconds(cont.waitAtStart);
    9.         StartCoroutine(moveWithinTime(cont.target, cont.timeToTarget));
    10.         yield return new WaitForSeconds(cont.timeToTarget); // wait the time until processing the next cont
    11.     }
    12.     yield return null;
    13. }
    14.  
    15. /** move to target position within timetoMove seconds */
    16. public IEnumerator moveWithinTime(Vector3 target, float timeToMove){
    17.     Vector3 currentPos = gameObject.transform.position;
    18.     target += originalPos;
    19.     float t = 0f;
    20.     while(t < 1) {
    21.         t += Time.deltaTime / timeToMove;
    22.         gameObject.transform.position = Vector3.Lerp(currentPos, target, t);
    23.         yield return null;
    24.     }
    25. }
    MoveOverTimeContainer:
    Code (CSharp):
    1. /** initial waiting time */
    2. public float waitAtStart = 0f;
    3. /** move to this point */
    4. public Vector3 target;
    5. /** within this timeframe */
    6. public float timeToTarget = 2f;
    Now, what I want to achieve is that no matter when the message is added, the following should happen:

    If the message has not fully reached its 1st target position (because that is what happens during begin()), wait until that happens. If the message is fully on the screen, make sure it is displayed for at least the waiting delay of the second cont object (2s). From that point on, it is allowed to override the movement so that a message that has being added between second 0 and second timeToMove+2s will stay at the first target position so that the message field stays where it is while the text is being changed. I know this is confusing to I tried to visualize this:
    IRQSMS.png

    I have problems understanding the scope of a coroutine I think. The moving UI object with his MoveOverTime will always be the same, only the text and image are changed in a message. I can't really make this work because of that and need some clarification

    The MoveOverTimeContainers:
    1: wait 0s, move to left within 0.9s
    2: wait 2s, move to 0/0 (original) within 0.6s

    When I click the button 4 times rapidly, I want to scroll it to the left within 0.9s, show msg1 for 2s, then msg2 for 2s, then msg3 for 2s, then msg4 for 2s, then go back to original pos. If I add the message while it is already moving back, interrupt that and move to position1 and continue normally.

    Any help appreciated :)
     
  2. xxBarginsxx

    xxBarginsxx

    Joined:
    Mar 7, 2014
    Posts:
    68
  3. xxBarginsxx

    xxBarginsxx

    Joined:
    Mar 7, 2014
    Posts:
    68
    "
    1. public IEnumerator begin() {
    2. foreach(MoveOverTimeContainer cont in moveDirections){
    3. yield return new WaitForSeconds(cont.waitAtStart);
    4. StartCoroutine(moveWithinTime(cont.target, cont.timeToTarget));
    5. yield return new WaitForSeconds(cont.timeToTarget); // wait the time until processing the next cont
    6. }
    7. yield return null;
    8. }
    "

    you need this line

    yield return null;
    inside the curly brakets, because its not returning a null IEnumator :)
     
    deLord likes this.
  4. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    hmmmm why do I need a yield return null when I have a
    yield return new WaitForSeconds(cont.timeToTarget) ?
     
  5. xxBarginsxx

    xxBarginsxx

    Joined:
    Mar 7, 2014
    Posts:
    68
    ,Because the 'Yield' keyword means Wait. so you Say 'Yield' (wait) For Seconds. WaitForSeconds(time);

    well thats a good but you need to yield return null do make a 'null' IEnumeratorso It will resume the code execution at the foreach loop once the update starts again. :) Thats the best i can tell ya :D

    I.E :

    public IEnumerator Begin()
    {
    foreach(MoveOverTimeContainer cont in MoveOverTimeContainer)
    {
    yield return new WaitForSeconds(cont.timeToTarget);

    StartCoroutine(moveWithinTime(cont.target, cont.timeToTarget));

    yield return new WaitForSeconds(cont.timeToTarget); // wait the time until processing the next cont

    yield return null; //Returns a null IEnumerator so that the code execution can resume a the givin spot
    }
    }
     
  6. Lightning-Zordon

    Lightning-Zordon

    Joined:
    May 13, 2014
    Posts:
    47
    Nope any yield will do that, a null just yields until the next frame, a waitforseconds wont resume execution of the coroutine until the time has passed.
     
  7. xxBarginsxx

    xxBarginsxx

    Joined:
    Mar 7, 2014
    Posts:
    68
    It's returning the IEnumator null :p So it will execute the code again :p at the next frame :p

    By the way... Watch the video... You'll know what I meant
     
  8. deLord

    deLord

    Joined:
    Oct 11, 2014
    Posts:
    306
    actually my problem is more of the nature that I don't know how to "stop" the current movement if there is a new one, since everything I tried with local and global variables I tried failed. That's what I meant with "scope". The code itselfs runs fine concerning the movement in a standard, non-interrupting situation, no yield return null is needed imho.
     
  9. xxBarginsxx

    xxBarginsxx

    Joined:
    Mar 7, 2014
    Posts:
    68
    If it's working then I'm not sure. Good job and Good luck with you'r project! :)