Tower Defence creep-spawning problems

Hi all, I have been working on a small TD game for a while now and am struggling with a good way to spawn in my creeps after a delay (it is worth mentioning and probably obvious, that I am new to Unity.)

The CreepManager class below uses a switch statement to determine which creep gameObject (from an array) is due to be spawned in. I believe the invoke delay is working but am unsure, the main problem lies in that instead of the switch statement starting the movement of each individual creep, one after they other, they all go at once and as a result clip through one another.

I am aware that much of the code (particularly in the switch statement) and is optimized and untidy, but will be sorted out later after my core functionality is down.

Help anyone could provide would be great and should you have any questions do not hesitate to ask! Thanks

using UnityEngine;
using System.Collections;

public class CreepManager : MonoBehaviour {

    public static bool beginNextRound;

    // Use this for initialization
    void Start ()
    {
        beginNextRound = false;
    }

    // Update is called once per frame
    void Update ()
    {
        if(beginNextRound == true)
        {
            if(GameController.nextCreep <= 9)
            {
                //spawn next creep
                Invoke("sendNextCreep", 4);
            }
        }
        else
        {
            GameController.nextCreep = 0;
        }
    }

    void sendNextCreep()
    {
        switch(GameController.nextCreep)
        {
        case 0:
            GameController.creepsOnMap[0].renderer.enabled = true;
            GameController.creepsOnMap[0].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 1;
            break;
        case 1:
            GameController.creepsOnMap[1].renderer.enabled = true;
            GameController.creepsOnMap[1].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 2;
            break;
        case 2:
            GameController.creepsOnMap[2].renderer.enabled = true;
            GameController.creepsOnMap[2].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 3;
            break;
        case 3:
            GameController.creepsOnMap[3].renderer.enabled = true;
            GameController.creepsOnMap[3].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 4;
            break;
        case 4:
            GameController.creepsOnMap[4].renderer.enabled = true;
            GameController.creepsOnMap[4].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 5;
            break;
        case 5:
            GameController.creepsOnMap[5].renderer.enabled = true;
            GameController.creepsOnMap[5].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 6;
            break;
        case 6:
            GameController.creepsOnMap[6].renderer.enabled = true;
            GameController.creepsOnMap[6].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 7;
            break;
        case 7:
            GameController.creepsOnMap[7].renderer.enabled = true;
            GameController.creepsOnMap[7].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 8;
            break;
        case 8:
            GameController.creepsOnMap[8].renderer.enabled = true;
            GameController.creepsOnMap[8].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            GameController.nextCreep = 9;
            break;
        case 9:
            GameController.creepsOnMap[9].renderer.enabled = true;
            GameController.creepsOnMap[9].GetComponent<Creep>().startMovement();
            print ("creep" + GameController.nextCreep + " has started moving");
            //closes the loop until next wave
            GameController.nextCreep = 0;
            beginNextRound = false;
            break;
        default:
            print ("problem within CreepManager's switch statement!");
            beginNextRound = false;
            break;
        }


     
    }

}

What I understand from your code, is that you already have all the creeps placed on the map and activate them when needed. Why not use Instantiate instead?

1 Like

Yes, @Fluzing is right, you should Instantiate a prefab for each creep rather than hiding and then activating them.

But moreover, I will try to explain why they’re all spawning at once… or nearly so. That Update() method fires 60 times per second or so. And on each call, you Invoke the sendNextCreep method. So on frame 0, you schedule sendNextCreep for 4 seconds later. Then on frame 1 (1/60th of a second later), you schedule sendNextCreep for 4 seconds later than that. Then again on frame 2, frame 3, etc… by the time 4 seconds have passed, you’ve scheduled 4*60=240 sendNextCreep calls!

So. Beginners (and sometimes non-beginners too) often reach for Invoke or coroutines when wanting to delay or do something periodically in Unity. I think that’s making life more complicated than it really should be. Here’s what I recommend instead:

Watch the clock. (And by “clock,” I mean Time.time.)

Just add a float property to your script called “nextThingTime” (name this according to whatever it does, like “nextSpawnTime” in your case). Set this to Time.time + 4 (or whatever) in your Start() method. Then in your Update method, you simply check whether that time has passed yet:

void Update() {
   if (Time.time > nextSpawnTime) {
      SendNextCreep();
      nextSpawnTime = Time.time + 4;
   }
}

Simple, right? And very hard to mess up. You just have to be sure you reset your nextThingTime (whatever that is) whenever you do your next thing.

1 Like

Hi Fluzing thanks for your reply, indeed all of the creeps have been spawned in the same location on the map, this is the second solution I came up with for the spawning system because the first did involve instantiating each creep on the fly.

The reason I abandoned this (it partially worked) was mainly due to frustration and the fact that my deadline is creeping closer and closer.

This new one uses the same ten creeps for each wave, when they get to the base (or are destroyed), their position is reset to spawn again and meshRenderer switched off until next wave.

Also sorry if difficult to understand :smile:

void Update ()
    {
        if(beginNextRound == true)
        {
            if(GameController.nextCreep <= 9)
            {
                //spawn next creep
                Invoke("sendNextCreep", 4);
                beginNextRound = false; //Do set it to "true" when you need to spawn an other round.
            }
        }
        else
        {
            GameController.nextCreep = 0;
        }
    }

The main problem I see in that code is that beginNextRound is true untill you spawned the last wave, meaning that it will keep spawning waves.

You might also put the beginNextRound variable as a public static one in GameController, just like the “nextCreep” one, because I suspect that the logic for deciding when to send the wave is in GameController.

1 Like