Performance Issue with Coroutines

Dear Community,

In my game I come to a point where I want several tasks to be performed before the player can interact with the game again. The tasks being: My 5 heroes attacking all 3 enemies one after another by shooting balls at them. Then the enemies attacking the heroes one after another and finally instantiating a new bunch of objects into a grid. I am using coroutines to do that. The problem is that at this point my computer is going wild. (loud and hot - its a macbook 2015) And I don’t know why. Since I’d like to eventually play this game on my iPad, it really needs to be simple.

Here is a picture:

Essentially the code goes like this:

/*
using ...
public class InputManager : MonoBehaviour {
     public Hero[] heroes;
     public Monster[] monsters;
     public GridManager grid;
     public int timesToClick = 4;

     void Update () {
          // When you click on the grid, deactivate the tiles beneath.
          // timesToClick--;
          // Instantiate a gameObject "Ammo".
          // start Ammo's coroutine that lets it fly to the hero.
          // in Ammo's script: when "Ammo"'s.position == hero.position call hero.ReceivedAmmo
          // that starts the coroutine Fight() in this script
     }

     IEnumerator Fight() {
          foreach (Hero hero in heroes) {
               hero.Attack();
               yield return new WaitForSeconds (timeBetweenAttacks);
          }
          foreach (Monster monster in monsters) {
               monster.Attack();
               yield return new WaitForSeconds (timeBetweenAttacks);
          }
          grid.UpdateGrid();
          yield return new WaitForSeconds (timeToUpgradeGrid);
          timesToClick = 4;
     }
}
*/

hero.Attack and monster.Attack are functions in my hero and monster class. When my heroes attack they each instantiate 3 “Ball” objects (another custom class), and start “Ball’s” coroutine moving their transform from the heroes position to each of the monsters position. Essentially like this:

/*
public class Ball
     public static int ballCount;
     public float speed;
     private Vector3 targetPos;

     void Awake {
           ballCount++;
     }

     IEnumerator Attack(Monster target, int damage)
     {
          targetPos = target.transform.position;
          while (this) {
               speed *= Time.DeltaTime;
               transform.position = Vector3.MoveTowards (transform.position, targetPos, speed);
               if (transform.position == targetPos) {
                    target.HasBeenDamaged(damage);
                    ballCount--;
                    Destroy (this.gameObject);
               }
               yield return new WaitForFixedUpdate ();
          } 
     }
*/

This essentially is the Hero Script:

/*
public class Hero : MonoBehaviour {
     public int damage;
     public InputManager InputManager;

     void ReceivedAmmo () {
          damage++;
          //do some UI stuff 
          if (inputManager.timesToClick == 0) {
               StartCoroutine (inputManager.Fight());
          }
     }
}
*/

Can anyone explain why this is so computationally demanding?

Is the performance the only issue here? I think you are starting the coroutines each frame when timesToClick is 0. You couldadd bool isFighting, set it to true when coroutine Fight is started, and in the end of the coroutine set it to false. Then in your update start coroutine when timesToClick ==0 AND isFighting == false

      bool isFighting
      
      void Update () {
           if (timesToClick == 0 && !isFighting) {
                StartCoroutine(Fight());
           }
      }
      
      IEnumerator Fight() {
           isFighting = true;
           foreach (Hero hero in heroes) {
                hero.Attack();
                yield return new WaitForSeconds (timeBetweenAttacks);
           }
           foreach (Monster monster in monsters) {
                monster.Attack();
                yield return new WaitForSeconds (timeBetweenAttacks);
           }
           grid.UpdateGrid();
           yield return new WaitForSeconds (timeToUpgradeGrid);
           timesToClick = 4;
           isFighting = false;
      }

Hello there,

As mentioned above, you really shouldn’t be starting coroutines in Update(). There’s a risk that you’ll be calling StartCoroutine() multiple times a second, which would end up multiplying the performance costs as you’d be running the same coroutine in parallel many, many times.

One thing you could do is declare a coroutine variable at the top: private Coroutine cor_fightCoroutine = null;.

Then, instead of starting a loose coroutine, you can keep a reference to it by doing this:

if(cor_fightCoroutine == null)    
{    
     cor_fightCoroutine = StartCoroutine(Fight());    
}

Then, make sure to end your coroutine with yield return null;. That way, you can only start it when it’s not running already.


Another thing about your coroutine in Ball. In the code you posted, you seem to be comparing transform.position to target, which is a Monster. Did you mean targetPos? You also use target in your MoveTowards() function…

instead of having your while loop rely on its own script’s existence to keep running, I recommend you directly use the distance. In that case, that would look something like:

public class Ball
{
      public float speed;
      private Vector3 targetPos;
 
      IEnumerator Attack(Monster target, int damage)
      {
           targetPos = target.transform.position;
           // replace 0.25f with a variable that would serve as the "I've reached my target" 
           // distance
           while (Vector3.Distance(transform.position, targetPos) > 0.25f)
            {
                speed *= Time.DeltaTime;
                transform.position = Vector3.MoveTowards (transform.position, targetPos, speed);
                yield return null;
           } 
            target.HasBeenDamaged(damage);
            Destroy(gameObject);
            yield return null;
      }
}

Other than that, I can’t see anything else that would be wrong here.


I hope that helps!

Cheers,

~LegendBacon