How to manage waves of enemies

I want to have enemies spawn in waves, with a variable time gap between each one being spawned. If I was doing this in a programming language I’d just use an array with the number of enemies in the wave in position 0, then pairs of values for enemy type and time gap duration. Each row in the array would contain all the enemy data for one wave.

Has anyone worked out a way to do this sort of thing in GDevelop? Can the entire wave be stored somehow in GDevelop or would you have to do it by loading in each pair of values (enemy type, time gap) from a file?

This is the current event which just spawns an enemy every few seconds according to the value of timegap. The type of enemy to spawn is stored in btype is and is changed in other events.

The array can be made in structure variables, any complex thing can be made:

Waves ....-1 ........Timegap 5 ....0 ........Enemies ............0 ................Name "Cacodemon" ................Amount 3 ............1 ................Name "Pinky" ................Amount 1 ........Timegap 45 ....1 ........Enemies ............0 ................Name "Cacodemon" ................Amount 1 ............1 ................Name "Pinky" ................Amount 1 ............2 ................Name "Skull" ................Amount 5 ........Timegap 40 ....2 ........Enemies ............(...) ........Timegap 50 (...)
The first wave is fake, I’ve put it there to wait for the real first wave without extra events.
The “hard” thing is accessing the values, if you have a “current_wave” variable for example, to track the current wave, you should use it in this way:

[code]Conditions: At the beginning of the scene
Actions: Do = -1 to variable “current_wave”
Reset timer “wave_timer”

Conditions: Time of timer “wave_timer” is greater than Variable(Waves[ToString(Variable(current_wave))].Timegap)
Actions: Reset timer “wave_timer”
Do + 1 to variable “current_wave”
Do = 0 to variable “enemy_index”
Repeat VariableChildCount(Waves[ToString(Variable(current_wave))].Enemies times
Repeat times Variable(Waves[ToString(Variable(current_wave))].Enemies[ToString(Variable(enemy_index))].Amount) times
Conditions: No conditions (always)
Actions: Create object from name VariableString(Waves[ToString(Variable(current_wave))].Enemies[ToString(Variable(enemy_index))].Name) at position Random(x) ; Random(y)
Do + 1 to variable “enemy_index”[/code]

And you can make even more complex things, as save multiple positions for each enemy tipe, like:

Enemies: ....0 ........Name "Cacodemon" ........Amount 2 ........X0 100 ........Y0 150 ........X1 50 ........Y1 100
And create one like:

Create object from name VariableString(Enemies[ToString(Variable(enemy_index))].Name) at position VariableString(Enemies[ToString(Variable(enemy_index))]["X" + ToString(Variable(instance_index))]) ; VariableString(Enemies[ToString(Variable(enemy_index))]["Y" + ToString(Variable(instance_index))])

But if the waves needs special positions maybe the best way is to create each wave in an external layout, and save the external layout name in the structure:

Waves ....0 ........Layouts ............0 "Cacodemon2" ............1 "Pinky3" ........Timegap 55 ....1 ........Layouts ............(...) ........Timegap 40 ....2 (...)
And of course use the layouts names in the same way :slight_smile:

If all this seems like a mess is because it’s a big mess, I would recommend to use the last one, layouts are easy to set-up :wink:

Nice Doom reference :wink:

That’s a lot to work through, so I haven’t tried it yet, but if I understand your example correctly it would create the entire wave of enemy objects each “tick” of the wave_timer, whereas I want the timer to control the time between spawnings of enemies in the same wave. If a wave is ten enemies, then each one of those ten would be created with a new tick of the timer. That way they could appear slowly to begin with, but more rapidly in later waves.

The loop would be something like:

[code]At start of scene | Put first two values into scene variables enemytype and timegap.

Check if timer>timegap | Create object from name using enemytype
| Reset timer
| Put next two values into scene variables enemytype and timegap.
[/code]

Fortunately, they all spawn at the same point offscreen, so no need to add in location variables too. Having said that, now that I think about it, maybe it would be simpler to just create the whole wave offscreen at once in an external event, so they are spaced out in X co-ordinates rather than in time of creation. It should have the same result as far as the player is concerned.

I’m not familiar enough with either structures or external events to know which would be best, but thanks for the ideas. :smiley:

My main loop is actually:

[code]At the beginning of the scene | Set current_wave variable = -1

Check if timer > Waves[current_wave].Timegap | Do + 1 to variable current_wave
| Reset timer
| Create objects from external layout Waves[current_wave].ExternalLayout[/code]

Each wave has a timegap, so you can put custom times for wave, and has an external layout name to create objects.

So for only one external layout per wave it’s pretty easy, the structure would be:

Waves ....0 ........ExternalLayout "Wave0" ........Timegap 45 ....1 ........ExternalLayout "Wave1" ........Timegap 30 (...)
To access the Timegap:

Variable(Waves[ToString(Variable(current_wave))].Timegap)

To access the ExternalLayout:

VariableString(Waves[ToString(Variable(current_wave))].ExternalLayout)

But if you name the external layout as I’ve done here, Wave0, Wave1, …, WaveN, then you can use the Waves structure without the ExternalLayout entries and instead use it:

VariableString(Waves[ToString(Variable(current_wave))]["Wave"+ToString(Variable(current_wave))])

I’ve really overlooked external layouts until now, even though them seem to be designed to do exactly the sort of thing I need.

There is perhaps something I’m missing about them, because this event didn’t work properly:

The objects are all created in the correct place, but the action to add a force is ignored. They stay motionless. Damping is set to 1, so it’s not that they move once and then stop.

When I moved the apply force action to a separate event it started to work. :confused:

Anyway, many thanks for pointing me in the direction of external layouts. :smiley: