Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Resolved Start multiple coroutines and wait for them to end

Discussion in 'Scripting' started by stefan3398, Apr 26, 2021.

  1. stefan3398

    stefan3398

    Joined:
    Aug 25, 2019
    Posts:
    16
    hello,

    I have this code, where I call a Coroutine from scripts on multiple children, each of these Coroutines in turn start other ones, the process goes like follows

    I click on a tile -> turrets get ordered to aim at the target -> turrets aim the barrels up -> projectile is fired -> yield return null

    this is the "turrets get ordered to aim at the target part"

    Code (CSharp):
    1.  // this function aims all possible turrets at the TARGETTILE
    2.     IEnumerator aimGunsAtTarget(Maptile _targetTile)
    3.     {
    4.         int _angle = GetAngle(_targetTile);
    5.  
    6.         foreach (turretController v in turrets)
    7.         {
    8.             // tell turrets shooting forward
    9.             if (125 >= _angle && _angle >= -125)                    // angles should actually be hard 60 and hard 120, but I've added/subtrackted a tolerance of 5°,
    10.             {                                                       // since this much is irrelevant for the game and helps with getting the right tile for shooting at the target
    11.                 if (v.orientation == MainBatteryAngles.FORWARD)
    12.                 {
    13.                     v.StartCoroutine("AimAt", _targetTile);
    14.                 }
    15.             }
    16.             // tell shooting backwards
    17.             if (-55 >= _angle || _angle >= 55)
    18.             {
    19.                 if (v.orientation == MainBatteryAngles.BACKWARD)
    20.                 {
    21.                     v.StartCoroutine("AimAt", _targetTile);
    22.                 }
    23.             }
    24.             // tell shooting (only) left
    25.             if (-55 >= _angle && _angle >= -125)
    26.             {
    27.                 if (v.orientation == MainBatteryAngles.LEFTWING || v.orientation == MainBatteryAngles.BOTHBROADSIDES)
    28.                 {
    29.                     v.StartCoroutine("AimAt", _targetTile);
    30.                     Debug.Log("wingturret " + v.name);
    31.                 }
    32.             }
    33.             // tell shooting (only) right
    34.             if (55 <= _angle && _angle <= 125)
    35.             {
    36.                 if (v.orientation == MainBatteryAngles.RIGHTWING || v.orientation == MainBatteryAngles.BOTHBROADSIDES)
    37.                 {
    38.                     v.StartCoroutine("AimAt", _targetTile);
    39.                     Debug.Log("wingturret " + v.name);
    40.                 }
    41.             }
    42.         }
    43.         yield return null;
    44.     }
    So here I would want all turrets to aime the target at the same time, but I don't want to "continue" the coroutine before all turrets have finished.
    I have looked through a few threads where solutions like counting up the running coroutines, etc. are proposed, what I wanted to ask, if there is mayb e an easier way, than that.
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,777
    I'd add a public bool isDoneAiming to each turret. Set it to false when its coroutine starts and the to true at the end of the gun's coroutine. Now in your main coroutine:
    Code (csharp):
    1. bool isAnyGunStillAiming = true;
    2. while (isAnyGunStillAiming) {
    3.     isAnyGunStillAiming = false;
    4.     foreach (var turret in turrets) {
    5.         if (!turret.isDoneAiming) isAnyGunStillAiming = true;
    6.     }
    7.     yield return null;
    8. }
    9. //now they're all done
     
    stain2319 likes this.
  3. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    Coroutines are so powerfull but you should avoid to start them with a string. It cost more and the only usefull thing youve is you can cancel them from another instance... but this is pretty useless: you can do it storing the coroutine return value from StartCoroutine() and is much efficient. Take an eye to the IEnumerator version of StartCoroutine:
    Code (CSharp):
    1. v.StartCoroutine("AimAt", _targetTile);
    use:
    Code (CSharp):
    1. v.StartCoroutine(AimAt(_targetTile));
    Not a good idea to iterate each frame just checking if all coroutines ended. I think this cases are for what CustomYieldInstruction is intended... You can create one and use it everywhere when you need some similar. Since Unity allows to yield return an IEnumerator as a Coroutine, you cant use a CustomYieldInstruction as manager for all the coroutines and just yield return it with your desired return.

    Heres my idea:
    Code (CSharp):
    1.  
    2. public class YieldCollection : CustomYieldInstruction
    3. {
    4.     int _count;
    5.  
    6.     //Each time you call this, you call the coroutine and count is increased until the end.
    7.     public IEnumerator CountCoroutine(IEnumerator coroutine)
    8.     {
    9.         _count++;
    10.         yield return coroutine;
    11.         _count--;
    12.     }
    13.  
    14.     //If count is 0,no one coroutine is running.
    15.     public override bool keepWaiting => _count != 0;
    16. }
    17.  
    Code (CSharp):
    1.  
    2. IEnumerator AimGunsAtTarget(Maptile _targetTile)
    3. {
    4.     YieldCollection manager = new YieldCollection();
    5.  
    6.     Debug.Log("Aim Started");
    7.  
    8.     foreach (TurretController v in turrets)
    9.     {
    10.         // tell turrets shooting forward
    11.         if (125 >= _angle && _angle >= -125)                
    12.         {                                                  
    13.             if (v.orientation == MainBatteryAngles.FORWARD)
    14.             {
    15.                 //For each coroutine we start the Coroutine counter, which starts our desired method itself, instead of starting the method directly.
    16.                 v.StartCoroutine(manager.CountCoroutine(AimAt(_targetTile)));
    17.             }
    18.         }
    19.  
    20.  
    21.         .........
    22.  
    23.  
    24.     }
    25.  
    26.     Debug.Log("All turrets turning...");
    27.  
    28.     //it will return true when all coroutines have finished.
    29.     yield return manager;
    30.  
    31.     Debug.Log("All turrets ready!");
    32. }
    33.  
     
    Last edited: Apr 27, 2021
    james580 likes this.
  4. stefan3398

    stefan3398

    Joined:
    Aug 25, 2019
    Posts:
    16
    thanks, I could see, that is theoreticaly works perfectly, trough Debug-Notifications, there is just one small problem.

    I can see, that is properly counts all the finishing Coroutines, but

    Code (CSharp):
    1.     //it will return true when all coroutines have finished.
    2.     yield return manager;
    doesn't make my code stop, is this what this is for?

    Code (CSharp):
    1.     //If count is 0,no one coroutine is running.
    2.     public override bool keepWaiting => _count == 0;
    because I could see in Visual Studio, that this is actually never called.

    One thing, that works is this while loop:

    Code (CSharp):
    1. while(manager.keepWaiting == false)
    2.         {
    3.             yield return null;
    4.         }
     
  5. rubcc95

    rubcc95

    Joined:
    Dec 27, 2019
    Posts:
    222
    Nah, it just doesn't work because im tard and it should be different of 0 not equal to xD. Now yield is stopping inmediatly when the 1st nested coroutine is started, it's making exactly the wrong one.

    Just change:
    Code (CSharp):
    1.    public override bool keepWaiting => _count != 0;
     
  6. stefan3398

    stefan3398

    Joined:
    Aug 25, 2019
    Posts:
    16
    perfect, works perfectly now, thank you very much for your help
     
  7. dnorambu

    dnorambu

    Joined:
    Sep 14, 2021
    Posts:
    11
    This video from Tarodev may be useful, although it implies changing the code to async calls, it resolves the stated problem in the OP in a very clean way