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?
Answer by Legend_Bacon · Feb 13, 2018 at 06:24 PM
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
Both of you, thanks a lot. I had to update the question above because when simplifying it for the forum I made 2 mistakes. 1 Fight() is called in the heroScript, not in update. 2. In the ballScript I am comparing the balls.position to targetPos not to target.
With this Updated version do you know why the trouble with my performance is occurring? I am terribly sorry for the mess-up.
No worries, these things happen. Thanks for taking the time to simplify the code before posting it. I assumed that's what you meant anyways, so it doesn't matter!
Other than that, I can't see much that would cause a performance spike here, except perhaps the instantiate call. You could use object pooling if you aren't already.
Did you take a look at the profiler (Window -> Profiler)? It can be very useful to find out what is costing you so much perf at runtime.
Then, maybe throw a Debug.Log inside your coroutines. This will help you find out if they are getting called more than they should.
I hope that helps!
Cheers,
~LegendBacon
Dear Legend_Bacon (I love your name :-))
Thanks a million for taken time. I am really grateful.
This Profiler is amazing. I didn't think I'd be ready to use it yet. But its not too complicated. I have found many interesting things. Apparently my coroutines don't seem to be the problem. Ins$$anonymous$$d its a mixture of (1. Instantiate, like you said. Especially for my particleSystems with Children Objects. I will have to look into object pooling. 2. Debug.Log which is surprisingly costy. 3. A big 'Editor Overhead" which I understand will not be present in the actual build. 4. Some weird thing occasionally happening called: "Font.CacheFontForText" I believe it has to do with "BestFit" being enabled on some UI Text Elements. 5. An occasional EventSystem.Update. Something to do with my mouse and the UI.)
After all maybe its just my macs cooling system. The game runs at 100 FPS and yet he is making noises.
So in other words. Thanks a lot. And cheers to you too :-)
Thank you for the tip of using distance within while. I will definitely do that.
yielding null does not stop a coroutine - it makes it wait for 1 frame
Answer by Lilius · Feb 13, 2018 at 06:03 PM
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;
}
Definitely agree. Starting coroutines in update, even when you are very careful with your Ifs and conditions, is a big no-no in my book.
Both of you, thanks a lot. I had to update the question above because when simplifying it for the forum I made 2 mistakes. 1 Fight() is called in the heroScript, not in update. 2. In the ballScript I am comparing the balls.position to targetPos not to target (as pointed out by you Legend_Bacon in your comment below.
With this Updated version do you know why the trouble with my performance is occurring? I am terribly sorry for the mess-up.
Your answer
Follow this Question
Related Questions
Coroutine WaitForSeconds ignoring StopAllCoroutines... How can I do it? 0 Answers
Show image then wait for x seconds and hide it again. 0 Answers
Wait time after coroutine's wait seconds is complete 0 Answers
Coroutine not working 1 Answer
Coroutine getting player input before timer runs out help 1 Answer