Start multiple coroutines and wait for them to end

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”

 // this function aims all possible turrets at the TARGETTILE
    IEnumerator aimGunsAtTarget(Maptile _targetTile)
    {
        int _angle = GetAngle(_targetTile);

        foreach (turretController v in turrets)
        {
            // tell turrets shooting forward
            if (125 >= _angle && _angle >= -125)                    // angles should actually be hard 60 and hard 120, but I've added/subtrackted a tolerance of 5°,
            {                                                       // since this much is irrelevant for the game and helps with getting the right tile for shooting at the target
                if (v.orientation == MainBatteryAngles.FORWARD)
                {
                    v.StartCoroutine("AimAt", _targetTile);
                }
            }
            // tell shooting backwards
            if (-55 >= _angle || _angle >= 55)
            {
                if (v.orientation == MainBatteryAngles.BACKWARD)
                {
                    v.StartCoroutine("AimAt", _targetTile);
                }
            }
            // tell shooting (only) left
            if (-55 >= _angle && _angle >= -125)
            {
                if (v.orientation == MainBatteryAngles.LEFTWING || v.orientation == MainBatteryAngles.BOTHBROADSIDES)
                {
                    v.StartCoroutine("AimAt", _targetTile);
                    Debug.Log("wingturret " + v.name);
                }
            }
            // tell shooting (only) right
            if (55 <= _angle && _angle <= 125)
            {
                if (v.orientation == MainBatteryAngles.RIGHTWING || v.orientation == MainBatteryAngles.BOTHBROADSIDES)
                {
                    v.StartCoroutine("AimAt", _targetTile);
                    Debug.Log("wingturret " + v.name);
                }
            }
        }
        yield return null;
    }

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.

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:

bool isAnyGunStillAiming = true;
while (isAnyGunStillAiming) {
    isAnyGunStillAiming = false;
    foreach (var turret in turrets) {
        if (!turret.isDoneAiming) isAnyGunStillAiming = true;
    }
    yield return null;
}
//now they're all done
1 Like

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:

v.StartCoroutine("AimAt", _targetTile);

use:

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:

public class YieldCollection : CustomYieldInstruction
{
    int _count;

    //Each time you call this, you call the coroutine and count is increased until the end.
    public IEnumerator CountCoroutine(IEnumerator coroutine)
    {
        _count++;
        yield return coroutine;
        _count--;
    }

    //If count is 0,no one coroutine is running.
    public override bool keepWaiting => _count != 0;
}
IEnumerator AimGunsAtTarget(Maptile _targetTile)
{
    YieldCollection manager = new YieldCollection();

    Debug.Log("Aim Started");

    foreach (TurretController v in turrets)
    {
        // tell turrets shooting forward
        if (125 >= _angle && _angle >= -125)                
        {                                                  
            if (v.orientation == MainBatteryAngles.FORWARD)
            {
                //For each coroutine we start the Coroutine counter, which starts our desired method itself, instead of starting the method directly.
                v.StartCoroutine(manager.CountCoroutine(AimAt(_targetTile)));
            }
        }


        .........


    }

    Debug.Log("All turrets turning...");

    //it will return true when all coroutines have finished.
    yield return manager;

    Debug.Log("All turrets ready!");
}
1 Like

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

    //it will return true when all coroutines have finished.
    yield return manager;

doesn’t make my code stop, is this what this is for?

    //If count is 0,no one coroutine is running.
    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:

while(manager.keepWaiting == false)
        {
            yield return null;
        }

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:

   public override bool keepWaiting => _count != 0;

perfect, works perfectly now, thank you very much for your help

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