# Enemy Spawning randomly between set locations & Waves {Please help a Noob trying to learn!}

J

#### jhallphx

##### Guest
Hello,

I am creating a top-down space shooter, much like asteroids meets space invaders. I have all the movement mechanics, firing, collisions, sounds, menus, point system, etc nailed down and am moving on to the finals stages: Enemy Spawning, Wave Generation, and Enemy AI.

Between multiple threads, it has become pretty confusing for a noob like me to compile something because I think I am using pieces from multiple ways of doing it and they are not working together. Since there are more ways that one to skin a cat I think it has convoluted the best way to do it.

My arena looks like this:

What I am trying to accomplish in this phase is this:
• Spawn enemies at 4 set locations, randomly. (portals at the corners of the room)
• Delay spawn briefly so the enemies do not pile on top of each other
• After all enemies are destroyed, move onto next wave
• Have number of enemies spawned increase each wave, maxing out at maybe 25 so it's not ridiculous.
• Introduce new enemies (to replace weaker enemies) at more difficult waves
The code I have so far is:
(sorry, not sure how to make it go into the black boxes)

Create Event:

//define variables
repeater_value = 5
repeater = repeater_value

//initialize arrays
for(i=0; i<4; i =1){ x=0; y=0 }

//populate arrays (These are the coordinates of my portals)
x[0]=61;
x[1]=957;
x[2]=61;
x[3]=957;

y[0]=62;
y[1]=62;
y[2]=958;
y[3]=958;

Step Event:

if alarm[0] = -1 && repeater > 0 {alarm [0] = room_speed/2}

Alarm[0]

//if alarm[0] = -1 && repeater > 0 {alarm [0] = room_speed/2}
if instance_number(obj_enemy) = 0 && repeater = 0
{
var pos = round(random(3));//..get random, rounded integer from 0 - 3
var eny = instance_create_depth(x[pos], y[pos], 8000, obj_enemy);//..create instance at random position
repeater_value = 5
repeater = repeater_value
}

I have read through these three threads and more, as well as a ton of google searches and tutorial videos on spawning, and have had no luck figuring it out:

Can anyone please post a clear way of doing this so it is easy to learn the actual theory behind it, and not just mindlessly copy/paste someone elses code into my project? The tutorials I have watched don't seem to address this type of game as far as enemy spawning and waves. I am trying to teach myself to code from ground zero through these forums, and it is going well so far

Much Thanks!
JHallPHX

Last edited by a moderator:
C

#### Codec080

##### Guest
Here's how I would approach this problem.
From what I understand, you need something that will spawn enemies, and you need enemies that will attack the player.
Did you read about the 'with' statement? This lets you create instances and you can initialize them with other variables. That's something I would use for this kind of problem but don't worry about this for now.

I would create 3 objects: a game controller object, a spawn object, and a enemy object, each with their separate create events. Let's initialize the variables.
You also want to make stronger enemies later; this means you should assign them stats like hp.

Create event of obj_enemy:
hp = 0;

Now we need to spawn enemies. We need a variable for the delay, and we need to set our 4 positions.

Create event of obj_controller:
Code:
``````delay = 0;

xx[0] = 61;
xx[1] = 957;
yy[0] = 62;
yy[1] = 958;``````
Let's set our number of enemies for this level. We will also create our spawns and keep track of them with a array. One way of making stronger enemies is to multiply the level number with their hp.

Code:
``````enemies_count = 10;
level = 1;

spawn[0] =  instance_create(xx[0], yy[0], obj_spawn);
spawn[1] =  instance_create(xx[1], yy[0], obj_spawn);
spawn[2] =  instance_create(xx[0], yy[1], obj_spawn);
spawn[3] =  instance_create(xx[1], yy[1], obj_spawn);``````

Lets set our alarm.
Code:
``````delay = (room_speed * 10) (lets put our timer on 10 seconds.)

alarm[0] = delay * choose(1, 2, 0); // I added a multiplier so the delays are random.``````
Now, we run our timer for this object. This will cause a random spawn in the room to create a enemy while reducing the enemy_count in our alarm 0 event. Then, it sets the alarm back.

Code:
``````Alarm 0 event of obj_controller:
Var pick = choose(0, 1, 2, 3);

with spawn[pick] {
with instance_create(x, y, enemy) {
hp = 2*obj_controller.level
}
}
enemy_count -= 1;
alarm[0] = delay * choose(1,2,whatever );``````
Im going to assume you already have your collisions and movements for your enemies. In their step event you could do

Code:
``````If (hp <= 0) {
Instance_destroy()
}``````
Then, in your controller object step event:
Code:
``````If enemy_count <= 0 {
//change level: you can add hp, add enemies with your enemy_count variable, etc.
}``````
Feel free to ask for questions, I'll help you. im not on my pc right now.

Last edited by a moderator:
J

#### jhallphx

##### Guest
Thank you for the reply!

My vision is that I would like to have harder (different) enemies introduced at later levels, not just buff'd versions of one enemy. My enemies already have HP.

I did read about "with" it seemed to create line that executed as if it was written in the referenced object itself. Honestly, was kind of confusing because I kept getting errors... I dont think I was referencing the variables correctly perhaps.

With your code I was a little confused if everything went into the create event of obj_controller (besides what you obviously marked for the Alarm[0])? And is obj_spawn just an empty object for x/y place marking?

So... Since I posted...

I found this script and have been trying to work off of it to make it do what I want. Here is the script code... it's kind of massive, I found it on the marketplace. It's pretty well explained which I like, because that's easier to learn from.

The one difference from what I want is that this script is designed to spawn enemies at random locations within a single set radius, instead of at the 4 set x/y coordinates. Any thoughts on how to modify it to make that change? Otherwise it seems to work well for delaying spawns and spawning in new enemies at higher levels, etc. It's all code within a single object, with reference to a couple of scripts that I could post if you want, but they are just for coordinate location within the radius.

SPAWNER CODE
===========================
Code:
``````/////CREATE EVENT

enum SPAWN_SYSTEM_STATE                    //Basic state engine consisting of:
{
spawn,                        //Spawn       - creates enemies within the spawn zone.
monitor,                        //Monitor     - checks if all enemies are destroyed.
transition                        //Transition  - a short pause between waves. Also displays wave information.
}

//System
state       = SPAWN_SYSTEM_STATE.transition;        //Start in the transition state to display the first wave information.
wave        = 0;                    //Waves are incremented in the transition state, so set it to 0 here.
delay_spawn = 0.5;                    //The time (in seconds) between each enemy being spawned.
delay_wave  = 6;                    //The time (in seconds) between each wave.
timer       = delay_wave * room_speed;

//Spawn Zone Limits
origin_x    = x;                    //This is the coordinate of the centre of the zone.
origin_y    = y;
radius_min  = 128;                    //The minimum distance from the origin to spawn enemies.
radius_max  = 256;                    //The maximum distance from the origin to spawn enemies.
//By setting both radii to the same value, enemies will spawn the same distance away.
angle_min   = 0;                    //The spawn zone is clamped between these two angles. This allows you to specify a
angle_max   = 360;                    //certain portion of the arc in which to spawn enemies.

//Enemy Variables
enemy_type[0] = obEnemy1;                //You can add or take away from this 1D array in order to define each enemy type you
enemy_type[1] = obEnemy2;                //want to have spawned. You should declare all enemies here, and then use the variables
enemy_type[2] = obEnemy3;                //below to determine the number of each type to be spawned when increasing difficulty.

enemy_number    = 10;                    //This is how many enemies will be spawned in the current wave. (Initial value).
enemies_created = 0;                    //How many enemies have been created. (Don't change this).
allow_stacking  = false;                //If true enemies will be allowed to spawn on top of each other.

enemy_type_min  = 0;                    //This is effectively the range of enemies. The spawn script will randomly select an enemy from
enemy_type_max  = 0;                    //the enemy_type array at a position within these two values. If you want to slowly introduce new enemies
//throughout the game then you can start off with small values and increase them every nth wave.
//If they are the same value then only that enemy type will be spawned.

//Display
display_wave    = delay_wave;                //The time (in seconds) at which to start showing the next wave number when in the transition state.
display_count   = 3;                    //The time (in seconds) at which to start counting down to the next wave when in the transition state.``````
================================================
Code:
``````/////STEP EVENT

switch(state)
{
case SPAWN_SYSTEM_STATE.spawn:
if(timer <= 0)
{
var type, enemy, spawn_complete, dir, rad, spawn_x, spawn_y;

type            = irandom_range(enemy_type_min, enemy_type_max);
enemy           = instance_create(0, 0, enemy_type[type]);            //Create the enemy instance at an arbitrary location.
spawn_complete  = false;                            //The position will be calculated and tested if available later.

repeat(20)                                    // If the spawn position is occupied, the system will recalculate a new spawn position.
{                                        // However, there is a chance that the game could hang if there are no available positions.
dir     = random_range(angle_min, angle_max);                // The repeat number can be increased or decreased, however if it doesn't manage to successfully spawn an enemy
rad     = random_range(radius_min, radius_max);                // the system will skip it and retry in the following frame thus preventing the game from hanging or causing a
spawn_x = round(FindArcPoint_x(origin_x, rad, dir));            // significant delay between enemy spawns (only one or two frames depending on the number of enemies) by not
spawn_y = round(FindArcPoint_y(origin_y, rad, dir));                    // resetting the timer.

if(allow_stacking)                            //If enemies are allowed to spawn on top of each other then the spawn is complete.
{
with(enemy)
{
x = spawn_x;
y = spawn_y;
}
spawn_complete = true;
}
else
{
with(enemy)
{
x = spawn_x;
y = spawn_y;

if(place_empty(x, y))            //If the enemy is in a free position, then remember that the spawn was succesfully completed in order to
{                    //break out of the repeat() loop.
spawn_complete = true;
}
}
}
if(spawn_complete)
{
enemies_created++;
if(enemies_created < enemy_number)
{
timer = delay_spawn * room_speed;
}
else
{
state = SPAWN_SYSTEM_STATE.monitor;
}
break;
}
}

if(!spawn_complete)                    //Destroy the enemy to retry spawn in the next step.
{
with(enemy)
{
instance_destroy();
}
}
}
else                            //Simply count down the timer if it's greater than 0.
{
timer--;
}
break;

case SPAWN_SYSTEM_STATE.monitor:
var enemies_remaining = false;

for(var i = 0; i < array_length_1d(enemy_type); i++)
{
if(instance_exists(enemy_type))            //Determine if there are any enemy instances of any of the declared types remaining.
{
enemies_remaining = true;
break;                        //If there are, there is no point continuing through the the whole array so break out early.
}
}

if(!enemies_remaining)                    //If no enemy instances exist then the player has destroyed them all.
{
timer = delay_wave * room_speed;
state = SPAWN_SYSTEM_STATE.transition
}
break;

case SPAWN_SYSTEM_STATE.transition:
if(timer <= 0)                        //Update the system variables.
{
wave++;
enemies_created = 0;

if(wave > 1)
{
//delay_spawn       = 0.5;                    //Increase difficulty. These are all the variables you can adjust to increase the difficulty.
//enemy_type_min    = 0;
enemy_number       += 5;
enemy_type_max      = min(enemy_type_max + 1,            //This will add a new enemy type every wave until it reaches the limit of enemy types.
array_length_1d(enemy_type) - 1);
}
state = SPAWN_SYSTEM_STATE.spawn;                    //Recommence spawning.
}
else
{
timer--;
}
break;
}``````
================================================================
FindArcPoint_x and FindArcPoint_y refer to scripts that find a point within the radius to spawn on...

Also, since implementing the spawner code... I am running into a problem now where my death animation (an explosion) is not working when the enemies are destroyed, which is odd because the explosion sound continues to work fine. Any thoughts there?

Thank you for your help. I'll include anyone who helps me dial in code as a contributor in the credits! The game is called "Space Wasters".

#### Attachments

• 19.8 KB Views: 24
Last edited by a moderator:
C

#### Codec080

##### Guest
My vision is that I would like to have harder (different) enemies introduced at later levels, not just buff'd versions of one enemy. My enemies already have HP.
Do you mean different attacks? Can the enemy shoot the player?

With your code I was a little confused if everything went into the create event of obj_controller (besides what you obviously marked for the Alarm[0])? And is obj_spawn just an empty object for x/y place marking?
This part goes into the create event of the obj_controller.
Code:
``````delay = (room_speed * 10) (lets put our timer on 10 seconds.)

alarm[0] = delay * choose(1, 2, 0); // I added a multiplier so the delays are random.``````
You could get rid of obj_spawn and create your enemy instances assigning one of your 4 coordinates. That's even better. Initially I tried to approach your problem with a controller that would create 4 spawns, which would each create enemies, but it's easier to keep track of the current spawn instances via the controller object.

Now let's check out the code you sent me.

Like you said, with 'with' you can go right into a object and you can set its variables. You will get a error if you try to access a variable that doesn't exist in this object (not the calling one.)
In this part of the code:
Code:
``with(enemy) { x = spawn_x; y = spawn_y; }``
this is exactly what happens. The x and y position of the enemy spawn is set.
Now, if you want to assign your xy coordinates from whatever object creating the enemies, you could get rid of the findArcPoint stuff and access the coordinates from your calling instance using 'other':
Code:
``````with instance_create(x, y, enemy) {
x = other.xx[0];
y = other.yy[0];
}``````
Here, other refers to the obj_controller. You have 4 coordinates possible in 2 arrays and you want to spawn your enemies at random positions:

Code:
``````with instance_create(x, y, enemy) {
x = other.xx[choose(0,1)];
y = other.yy[choose(0,1)];
hp = // insert life for this enemy
}``````
Also, since implementing the spawner code... I am running into a problem now where my death animation (an explosion) is not working when the enemies are destroyed, which is odd because the explosion sound continues to work fine.
Send me the events code for your enemies.

J

#### jhallphx

##### Guest
There will be 3 enemy types. One where contact damages the player, one with a shooting ability, and one boss type which has much more hp, moves slower, and a rapid shooting ability.

Thanks for the clarification on your code. The coordinate array was a good idea for sure, so I added it to the creation code of the script I found on the marketplace. I like your ideas for the adjustment to the script I found. I tried to implement it and am getting an error message.

############################################################################################
ERROR in
action number 1
of Step Event0
for object ob_SpawnSystem:

Creating instance for non-existing object: 100038
at gml_Object_ob_SpawnSystem_Step_0 (line 36) - with instance_create_depth(x, y, 8000, enemy)
############################################################################################

To calrify, ob_SpawnSystem is the name of the object containing the spawner code I found on the marketplace. 8000 is the depth of my Instances layer, which is where enemies and the player exist.

This is what I changed the code to:

Code:
``````///// STEP EVENT
/// @description Process Spawn System

switch(state)
{
case SPAWN_SYSTEM_STATE.spawn:
if(timer <= 0)
{
var type, enemy, spawn_complete, dir, rad, spawn_x, spawn_y;

type            = irandom_range(enemy_type_min, enemy_type_max);
enemy           = instance_create_depth(x, y, 8000, enemy_type[type]);          //Create the enemy instance at an arbitrary location.
spawn_complete  = false;                                            //The position will be calculated and tested if available later.

repeat(20)                                                          //If the spawn position is occupied, the system will recalculate a new spawn position.
{                                                                   //However, there is a chance that the game could hang if there are no available positions.
dir     = random_range(angle_min, angle_max);                   //The repeat number can be increased or decreased, however if it doesn't manage to successfully spawn an enemy
rad     = random_range(radius_min, radius_max);                 //the system will skip it and retry in the following frame thus preventing the game from hanging or causing a
//spawn_x = round(FindArcPoint_x(origin_x, rad, dir));            //significant delay between enemy spawns (only one or two frames depending on the numebr of enemies) by not
//spawn_y = round(FindArcPoint_y(origin_y, rad, dir));            //resetting the timer.

if(allow_stacking)                                              //If enemies are allowed to spawn on top of each other then the spawn is complete.
{
with instance_create_depth(x, y, 8000, enemy)
{
x = other.xx[choose(0,1)];
y = other.yy[choose(0,1)];
}
spawn_complete = true;
}
else
{
with instance_create_depth(x, y, 8000, enemy)
{
x = other.xx[choose(0,1)];
y = other.yy[choose(0,1)];

if(place_empty(x, y))                                   //If the enemy is in a free position, then remember that the spawn was succesfully completed in order to
{                                                       //break out of the repeat() loop.
spawn_complete = true;
}
}
}``````
This is the original code, it refers to two scripts that were autogenerated by Game Maker Studio 2 for compatibility so I have no idea how to decipher them, or get around them. Certainly they are the cause of the error since I am no longer referencing the "instancecreate" script, which references the "__global_object_depths" script.

Code:
``````/////STEP EVENT
/// @description Process Spawn System

switch(state)
{
case SPAWN_SYSTEM_STATE.spawn:
if(timer <= 0)
{
var type, enemy, spawn_complete, dir, rad, spawn_x, spawn_y;

type            = irandom_range(enemy_type_min, enemy_type_max);
enemy           = instance_create(0, 0, enemy_type[type]);          //Create the enemy instance at an arbitrary location.
spawn_complete  = false;                                            //The position will be calculated and tested if available later.

repeat(20)                                                          //If the spawn position is occupied, the system will recalculate a new spawn position.
{                                                                   //However, there is a chance that the game could hang if there are no available positions.
dir     = random_range(angle_min, angle_max);                   //The repeat number can be increased or decreased, however if it doesn't manage to successfully spawn an enemy
rad     = random_range(radius_min, radius_max);                 //the system will skip it and retry in the following frame thus preventing the game from hanging or causing a
spawn_x = round(FindArcPoint_x(origin_x, rad, dir));            //significant delay between enemy spawns (only one or two frames depending on the numebr of enemies) by not
spawn_y = round(FindArcPoint_y(origin_y, rad, dir));            //resetting the timer.

if(allow_stacking)                                              //If enemies are allowed to spawn on top of each other then the spawn is complete.
{
with(enemy)
{
x = spawn_x;
y = spawn_y;
}
spawn_complete = true;
}
else
{
with(enemy)
{
x = spawn_x;
y = spawn_y;

if(place_empty(x, y))                                   //If the enemy is in a free position, then remember that the spawn was succesfully completed in order to
{                                                       //break out of the repeat() loop.
spawn_complete = true;
}
}
}``````
here are the two autogenerated scripts:
" instance_create "
Code:
``````/// @description Creates an instance of a given object at a given position.
/// @param x The x position the object will be created at.
/// @param y The y position the object will be created at.
/// @param obj The object to create an instance of.
var myDepth = __global_object_depths();
return instance_create_depth( argument0, argument1, myDepth, argument2 );``````
and " __global_object_depths "
Code:
``````// Initialise the global array that allows the lookup of the depth of a given object
// GM2.0 does not have a depth on objects so on import from 1.x a global array is created
// NOTE: MacroExpansion is used to insert the array initialisation at import time
gml_pragma( "global", "__global_object_depths()");

// insert the generated arrays here
global.__objectDepths[0] = 0; // obEnemy1
global.__objectDepths[1] = 0; // obSpawnSystem
global.__objectDepths[2] = 0; // obEnemy2
global.__objectDepths[3] = 0; // obEnemy3

global.__objectNames[0] = "obEnemy1";
global.__objectNames[1] = "obSpawnSystem";
global.__objectNames[2] = "obEnemy2";
global.__objectNames[3] = "obEnemy3";

// create another array that has the correct entries
var len = array_length_1d(global.__objectDepths);
global.__objectID2Depth = [];
for( var i=0; i<len; ++i ) {
var objID = asset_get_index( global.__objectNames[i] );
if (objID >= 0) {
global.__objectID2Depth[ objID ] = global.__objectDepths[i];
} // end if
} // end for``````

J

#### jhallphx

##### Guest
I'll see if you can address the animation issue once the spawn problem is figured out lol, I dont want to overload you... although you are clearly well versed in the Coding Arts!

C

#### Codec080

##### Guest
For the error: Check if your enemy object is named "enemy".

For the instance create thing, from what I understand they removed the instance_create function in gm2 but they give you a working script to use it if you dont want to use layers/depth.

Feel free to pm me sometime if you need help.

J

#### jhallphx

##### Guest
For the error: Check if your enemy object is named "enemy".
I think the "enemy" is referencing the var it created a few lines above...? my enemy is name "obj_enemy". Perhaps I cannot reference a variable in the instance_create_depth function? Who knows... lol

For the instance create thing, from what I understand they removed the instance_create function in gm2 but they give you a working script to use it if you dont want to use layers/depth.
Yeah, they did, I am trying to work around it by using the current function "instance_create_depth(x,y,depth,obj)" but it doesn't seem to like that.

Not sure how to PM on here either... Ive only been a member a couple days. Just added Kickle as my profile pic haha. Thanks for all the help so far!

C

#### Codec080

##### Guest
When creating a instance, you need the object name as a argument. If you named your object obj_enemy you need to pass it like so.

You are welcome. I havent been posting much either but Ive been doing a lot of gamemaker the few last months. Im glad I can help!

J

#### jhallphx

##### Guest
Ok... some good developments! I have tried to just emulate the original code by creating scripts to replace the "FindArcPoint" stuff. Now enemies are spawning, but only from the top left corner. Maybe you can see what I did wrong? I have to head out for a couple hours so I will check back when I get home again. Thanks again!

Updated code:
Code:
``````/// @description Process Spawn System

switch(state)
{
case SPAWN_SYSTEM_STATE.spawn:
if(timer <= 0)
{
var type, enemy, spawn_complete, dir, rad, spawn_x, spawn_y;

type            = irandom_range(enemy_type_min, enemy_type_max);
enemy           = instance_create(0, 0, enemy_type[type]);          //Create the enemy instance at an arbitrary location.
spawn_complete  = false;                                            //The position will be calculated and tested if available later.

repeat(20)                                                          //If the spawn position is occupied, the system will recalculate a new spawn position.
{                                                                   //However, there is a chance that the game could hang if there are no available positions.
dir     = random_range(angle_min, angle_max);                   //The repeat number can be increased or decreased, however if it doesn't manage to successfully spawn an enemy
rad     = random_range(radius_min, radius_max);                 //the system will skip it and retry in the following frame thus preventing the game from hanging or causing a
spawn_x = scr_spawn_x                                   //significant delay between enemy spawns (only one or two frames depending on the numebr of enemies) by not
spawn_y = scr_spawn_y                                   //resetting the timer.

if(allow_stacking)                                              //If enemies are allowed to spawn on top of each other then the spawn is complete.
{
with (enemy)
{
x = spawn_x;
y = spawn_y;
}
spawn_complete = true;
}
else
{
with (enemy)
{
x = spawn_x;
y = spawn_y;

if(place_empty(x, y))                                   //If the enemy is in a free position, then remember that the spawn was succesfully completed in order to
{                                                       //break out of the repeat() loop.
spawn_complete = true;
}
}
}``````
script I made for to generate x coordinate:
Code:
``````//Coordinate Array
var x1 = 61;
var x2 = 957;
var spawnchoicex = irandom(choose(0,1))

return xx[spawnchoicex]``````
script I made to generate y coordinate:
Code:
``````//Coordinate Array
var y1 = 62;
var y2 = 958;
var spawnchoicey = irandom(choose(0,1))

return yy[spawnchoicey]``````
I just left the compatibility-created codes and scripts alone to avoid errors there... so not sure if there is any way to bypass that stuff and eliminate it. Just trying to get it to work! Included a screenshot of what it looks like with the updated code.

Thanks!

J

#### jhallphx

##### Guest
OK, problem is SOLVED!! Special thanks to Codec080 for putting me on the right track! Turns out it was the "autostacking" feature being false... turned that to true and it works! My theory is that it was preventing it from spawning because when it checked the spawn point to ensure it was empty, it got caught in a loop because it was trying to place it and make sure it was empty at the same time... if that makes sense. Maybe I'm wrong. At any rate, I reworked the code for this specific situation - 4 fixed spawn points, where enemies spawn in waves, randomly between the 4 points. This is a code that I originally got off the marketplace, so credit to Jordan Robinson for the original code.

Since I hate when I read a thread and there is no clear cut blueprint of what to put where, here you go:

Some stuff up front:
• my enemies are called "obj_enemy" and "obj_boss", you may need to change this to fit your names
• my Instance depth is set to 8000
• you will need two fonts set up, one for wave and one for the counter. I named them "fnt_wave" and "fnt_count"
• the coordinates of my spawn points are: (61,62), (957,62), (61, 958), (958, 957)
• the wave announcement fits over my HUD in game which is why I have it set to those coordinates, you might want to change it, like center it or whatever

Create a spawn object, mine is named "obj_enemy_spawner" and drag it into your room. There is no sprite attached to it so you can place it anywhere. Put the follwing code in the dictated events:

Create Event:
Code:
``````/////INITIALIZE SPAWNER

enum SPAWN_SYSTEM_STATE                             //Basic state engine consisting of:
{                                                   //      Spawn       - creates enemies within the spawn zone.
spawn,                                          //      Monitor     - checks if all enemies are destroyed.
monitor,                                        //      Transition  - a short pause between waves. Also displays wave information.
transition
}

//System
state       = SPAWN_SYSTEM_STATE.transition;        //Start in the transition state to display the first wave information.
wave        = 0;                                    //Waves are incremented in the transition state, so set it to 0 here.
delay_spawn = 0.5;                                  //The time (in seconds) between each enemy being spawned.
delay_wave  = 6;                                    //The time (in seconds) between each wave.
timer       = delay_wave * room_speed;

//Enemy Variables
enemy_type[0] = obj_enemy;                           //You can add or take away from this 1D array in order to define each enemy type you
enemy_type[1] = obj_boss;                           //want to have spawned. You should declare all enemies here, and then use the variables
//enemy_type[2] = obEnemy3;                           //below to determine the number of each type to be spawned when increasing difficulty.

enemy_number    = 10;                               //This is how many enemies will be spawned in the current wave. (Initial value).
enemies_created = 0;                                //How many enemies have been created. (Don't change this).
spawnenabled  = true;                                //LET IT SPAWN!!

enemy_type_min  = 0;                                //This is effectively the range of enemies. The spawn script will randomly select an enemy from
enemy_type_max  = 0;                                //the enemy_type array at a position within these two values. If you want to slowly introduce new enemies
//throughout the game then you can start off with small values and increase them every nth wave.
//If they are the same value then only that enemy type will be spawned.

//Display
display_wave    = delay_wave;                       //The time (in seconds) at which to start showing the next wave number when in the transition state.
display_count   = 3;                                //The time (in seconds) at which to start counting down to the next wave when in the transition state.``````
Step Event:
Code:
``````///// SPAWNER PROCESS

switch(state)
{
case SPAWN_SYSTEM_STATE.spawn:
if(timer <= 0)
{
var type, enemy, spawn_complete;

type            = irandom_range(enemy_type_min, enemy_type_max);
enemy           = instance_create_depth(x, y, 8000, enemy_type[type]);          //Create the enemy instance at an arbitrary location.
spawn_complete  = false;            //The position will be calculated and tested if available later.

repeat(20)                            //If the spawn position is occupied, the system will recalculate a new spawn position.
{                                    //However, there is a chance that the game could hang if there are no available positions.
if(spawnenabled)                //The repeat number can be increased or decreased, however if it doesn't manage to successfully spawn an enemy
{                                //the system will skip it and retry in the following frame thus preventing the game from hanging or causing a
with (enemy)                //significant delay between enemy spawns (only one or two frames depending on the numebr of enemies) by not
{                            //resetting the timer.
x = choose(61, 957);
y = choose(62, 958);
}
spawn_complete = true;
}
if(spawn_complete)
{
enemies_created++;
if(enemies_created < enemy_number)
{
timer = delay_spawn * room_speed;
}
else
{
state = SPAWN_SYSTEM_STATE.monitor;
}
break;
}
}

if(!spawn_complete)                                                 //Destroy the enemy to retry spawn in the next step.
{
with(enemy)
{
instance_destroy();
}
}
}
else                                                                    //Simply count down the timer if it's greater than 0.
{
timer--;
}
break;

case SPAWN_SYSTEM_STATE.monitor:
var enemies_remaining = false;

for(var i = 0; i < array_length_1d(enemy_type); i++)
{
if(instance_exists(enemy_type[i]))                                  //Determine if there are any enemy instances of any of the declared types remaining.
{
enemies_remaining = true;
break;                                                          //If there are, there is no point continuing through the the whole array so break out early.
}
}

if(!enemies_remaining)                                                  //If no enemy instances exist then the player has destroyed them all.
{
timer = delay_wave * room_speed;
state = SPAWN_SYSTEM_STATE.transition
}
break;

case SPAWN_SYSTEM_STATE.transition:
if(timer <= 0)                                                          //Update the system variables.
{
wave++;
enemies_created = 0;

if(wave > 1)
{
//delay_spawn       = 0.5;                                      //Increase difficulty. These are all the variables you can adjust to increase the difficulty.
//enemy_type_min    = 0;
enemy_number       += 2;
enemy_type_max      = min(enemy_type_max + .1,                     //This will add a new enemy type every 10th wave until it reaches the limit of enemy types. Change to 1 to add a new enemy every wave.
array_length_1d(enemy_type) - 1);
}
state = SPAWN_SYSTEM_STATE.spawn;                                   //Recommence spawning.
}
else
{
timer--;
}
break;
}``````
Draw Event:
Code:
``````///// DRAW WAVE ANNOUNCEMENT

if(state == SPAWN_SYSTEM_STATE.transition)
{
draw_set_halign(fa_center);
draw_set_valign(fa_middle);
depth = -2000

if(timer <= display_wave * room_speed)
{
draw_set_font(fnt_wave);
draw_text(325, 993, string_hash_to_newline("W-" + string(wave + 1)));
}

if(timer <= display_count * room_speed)
{
draw_set_font(fnt_count);
draw_text(325, 1005, string_hash_to_newline("..." + string(ceil(timer / room_speed))));
}

draw_set_halign(fa_left);   //Reset draw variables
draw_set_valign(fa_top);
draw_set_font(-1);
}``````
That's it!
Should work... it does for me!

//JHallPHX