I use co-routines in Unity where it’s applicable and find them good when it comes to escaping over engineering. Often you can write cleaner code using co-routine instead of a bunch of logic inside your Update() function.

Imagine we need to program a turret which shoots bullet’s automatically during it’s lifetime. Here is a simple co-routine which will launch each bullet along the directional vector.

private IEnumerator LaunchBullet()    {      
    var bullet =  GameObject.Instantiate(bulletPrefab, transform.position, Quaternion.identity);
    while (Vector3.Distance(transform.position, bullet.transform.position) <= distanceToDestroy)
    {           
        bullet.transform.position += directionalVector * (speed * Time.deltaTime);
        yield return null;        
    }

    Destroy(bullet);
    yield break;    
} 

This piece of code will change bullet's position on each frame. On yield return null statement - co-routine yields function execution and transfers control to the Unity Engine, so it initiates the next frame update. When bullet reaches distanceToDestroy, while loop is ended, bullet destroyed and yield break exits the co-routine.

So, Сo-routine in Unity is a function which returns IEnumerator object, which makes your function iterable by adding yield statements. It iterates through your method until yield keyword is found, pauses execution on current frame, gives control back to Unity and continues where it left off on the next frame.

Now, we write Shoot() method and start our LaunchBullet() Co-Routine inside, so we can launch one bullet whenever Shoot method is called:

void Shoot()
{
    StartCoroutine(LaunchBullet());
}

Let’s say you have this method on your TurretBehaviour.cs script, and you have to shoot each bullet with fireRate delay (of course if you don’t want to shoot the bullet each frame which is probably insane). Then you going to write some piece of code to delay shooting:

void Update()
{
    shooting = false;
    if (Time.time >= lastTimeBulletShot + fireRate) 
   {
        shooting = true;
        lastTimeBulletShot = Time.time;
   }

   if (shooting) Shoot();
}

But I personally strive to add as little logic as it's possible in my update functions as it can affect performance in long run. Let's do better staff instead.

Combining Co-Routines

Since co-routine might have more than one yield statements inside it’s body and dev team at Unity implemented WaitForSeconds object, we can get rid of this unnecessary code inside update function by writing new co-routine which will initiate bullet shoot after each fireRate delay in time:

IEnumerator StartShooting()
{
    while(true)
    {
        StartCoroutine(LaunchBullet());
        yield return new WaitForSeconds(fireRate);
    }

    yield break;
}

Here we just launch new bullet with fireRate delay forever (until object is destroyed). So at the point where we launch StartShooting() co-routine, we actually start our shooting and have not even a single line in our Update() function.

That's it. Think of how you can simplify your game code and even improve performance using co-routines instead of overflowing your update methods with logic.

The full TurretBehaviour.cs class for Unity is available here. Don't forget to put your bullet prefab into Resources/Prefabs/bullet if you're going to test this script.