• Hey! Guest! The 39th GMC Jam will take place between November 26th, 12:00 UTC and November 30th, 12:00 UTC. Why not join in! Click here to find out more!

GML [SOLVED] Spawner Code While Loop Crashes - Platformer

F

Feast For Worms

Guest
Hello everyone,

I have a coding error that I cannot seem to solve by myself so I hope you can help.

I have a spawn controller object that runs on an alarm event. There is a step event in the object besides the alarm.

While in open areas, the spawn controller works well. However, when entering tighter areas of the levels or the edges of the level, I run into a hard crash.

When running debug, I've diagnosed that my while loop is causing an infinite loop that crashes the game.

I've tried several ways to get around this - disabling/enabling solid objects, trying a do/until loop, if statements, etc - but I'm still having issues...

Here's a snippet of code, highlighted where it's getting caught up.


SPAWNER CODE

Code:
if roll <= 1
    {
    if (spawn) && instance_exists(par_player)
    {
        with (instance_create(par_player.x + irandom_range(spawnrangemin, spawnrangemax), par_player.y-2, enemy1)) {
        while (place_meeting(x,y,par_wall))
        {
            x = par_player.x + irandom_range(spawnrangemin, spawnrangemax);
            y = par_player.y
        }
    }
...and just a quick glimpse at the collision code for safe measure...

COLLISION CODE

Code:
if (place_meeting(x+hspd,y,par_wall))
{
    if(!place_meeting(x+sign(hspd),y,par_wall))
    {
        x += sign(hspd);
    }
    hspd = 0;
    dir *= -1;
}
x += hspd;
 
//Vertical Collision
if (place_meeting(x,y+vspd,par_wall))
{
    if(!place_meeting(x,y+sign(vspd),par_wall))
    {
        y += sign(vspd);
    }
    vspd = 0;
   
    if (fearofheights) && !position_meeting(x+(sprite_width/2)*dir, y+(sprite_height/2)+8, par_wall)
    {
        dir *= -1;
    }
}
y += vspd;

Sometimes it'll get caught at the beginning of the while loop. Other times it'll stall during the coordinates.

I've tried replacing it with an if statement, but this causes enemies to spawn and get stuck inside the wall objects, which is less than ideal. Replacing it with a do/until statement worked (mostly) when the objects were made solid, but I'd much rather not deal with solids.

Any help? Am I missing a simple syntax error? Are there any adjustments you would make to the code?

Is there a simpler, easier, better way to code enemy spawning for a platformer?

Any and all help is greatly appreciated!
 
Last edited by a moderator:

obscene

Member
Instead of creating the enemy and then trying to move it to a safe position, find a safe position (using collision_circle with a radius a little bigger than your enemy would be easiest) and then create the enemy. With this system you could use IF, and if the circle hits a wall nothing will be spawned. You might even want to repeat it a few times with each alarm (do until) or just set your alarm to check more often.
 
F

Feast For Worms

Guest
Instead of creating the enemy and then trying to move it to a safe position, find a safe position (using collision_circle with a radius a little bigger than your enemy would be easiest) and then create the enemy. With this system you could use IF, and if the circle hits a wall nothing will be spawned. You might even want to repeat it a few times with each alarm (do until) or just set your alarm to check more often.
Thanks for the quick response.

Hmm. Haven't used collision_circle much yet. So I'd replace the while statement with a collision check? So... something along the lines of...

Code:
with instance_create(x,y,enemy1)
{
     if collision_circle(x,y,64,par_wall,0,0)
     {
          instance_destroy();
     }
}
Is that what you're suggesting?

I would really like to avoid spawning enemies and having them destroyed immediately... That's why I was using a while loop to place them randomly within the room. But... It's obviously not working lol
 

TheouAegis

Member
What are the values of spawnrangemin and spawnrangemax? I noticed you're only spawning randomly along the horizontal axis, so there is no need to concern the code with the y-coordinate at all. Now, what you could do is something like

Code:
var n = x;
while place_meeting(x,y,par_wall)
{
    x++;           //Move forward 1 pixel (change this to x+=? to speed up the process)
    if x-par_player.x > spawnrangemax { x = par_player.x + spawnrangemin; }        //Loop back to the min range if you've moved too far
    if x == n instance_destroy();        //If you made a full loop, there's no free spot, so remove the enemy
}

You don't need to use a collision_circle because you're working in 1D space, whereas a circle collision would be used in 2D space. But I think obscene's idea still deviated from what you're looking for. My method isn't the fastest, because it does a collision check over and over and over then a destroy, but it was how I learned to do similar spawning (it was how Konami handled spawning zombies out of the ground in Castlevania III).
 

obscene

Member
Thanks for the quick response.

Hmm. Haven't used collision_circle much yet. So I'd replace the while statement with a collision check? So... something along the lines of...

Code:
with instance_create(x,y,enemy1)
{
     if collision_circle(x,y,64,par_wall,0,0)
     {
          instance_destroy();
     }
}
Is that what you're suggesting?

I would really like to avoid spawning enemies and having them destroyed immediately... That's why I was using a while loop to place them randomly within the room. But... It's obviously not working lol
No, you missed the point. Check for a free place BEFORE creating the instance. If you can find a free space, create the instance. If not, don't. The reason I used a circle is because it's the easiest to code, but you could also use a rectangle the same size as your enemy, or change your sprite index to the same as your enemy, check place meeting and then set it back when you're done. But the key to all of this is to find a free space THEN create the enemy.
 
F

Feast For Worms

Guest
What are the values of spawnrangemin and spawnrangemax? I noticed you're only spawning randomly along the horizontal axis, so there is no need to concern the code with the y-coordinate at all. Now, what you could do is something like

Code:
var n = x;
while place_meeting(x,y,par_wall)
{
    x++;           //Move forward 1 pixel (change this to x+=? to speed up the process)
    if x-par_player.x > spawnrangemax { x = par_player.x + spawnrangemin; }        //Loop back to the min range if you've moved too far
    if x == n instance_destroy();        //If you made a full loop, there's no free spot, so remove the enemy
}

You don't need to use a collision_circle because you're working in 1D space, whereas a circle collision would be used in 2D space. But I think obscene's idea still deviated from what you're looking for. My method isn't the fastest, because it does a collision check over and over and over then a destroy, but it was how I learned to do similar spawning (it was how Konami handled spawning zombies out of the ground in Castlevania III).
The spawnrangemin and spawnrangemax are enemy specific. There's a switch function that changes their ranges. It's usually about (-400,400).

I left the y-coordinate in there because I originally had it trying to find an open y-coord position but I wanted to diagnose the problem without concerning myself with both coordinates.

I'll test out your suggestions and report back. Hadn't considered this technique. I had no idea Castlevania used a similar approach. Seems promising.

I'm wondering, would it be possible to run the x-coord check, then a y-coord check? Or... maybe set the x-coord range as a set distance then do a similar function for the y-coord?

Just spitballing. Gonna give this a shot.
 
F

Feast For Worms

Guest
No, you missed the point. Check for a free place BEFORE creating the instance. If you can find a free space, create the instance. If not, don't. The reason I used a circle is because it's the easiest to code, but you could also use a rectangle the same size as your enemy, or change your sprite index to the same as your enemy, check place meeting and then set it back when you're done. But the key to all of this is to find a free space THEN create the enemy.
Ahhh. Apologies. Late night and long day of coding and I misunderstood.

I'll try this technique as well. If both your technique and the one above work, I'll compare and see which one's more beneficial.
 
F

Feast For Worms

Guest
No, you missed the point. Check for a free place BEFORE creating the instance. If you can find a free space, create the instance. If not, don't. The reason I used a circle is because it's the easiest to code, but you could also use a rectangle the same size as your enemy, or change your sprite index to the same as your enemy, check place meeting and then set it back when you're done. But the key to all of this is to find a free space THEN create the enemy.
I tried writing some quick code to figure this out, but I can't seem to get it to work.

Since I haven't used collision_circle before, I might be doing it wrong.

Any quick psuedocode you could provide to get the ball rolling?

I tried doing something along the lines of this... I deleted the code from my game since it wasn't working, but I think I tried something like.....

Code:
var circle;
circle = collision_circle(x,y,64,par_wall,0,0)

if !place_meeting(circle.x,circle.y,par_wall)
{
    instance_create(x,y,enemy1);
}
...But yeah, unsuccessful.
 
F

Feast For Worms

Guest
What are the values of spawnrangemin and spawnrangemax? I noticed you're only spawning randomly along the horizontal axis, so there is no need to concern the code with the y-coordinate at all. Now, what you could do is something like

Code:
var n = x;
while place_meeting(x,y,par_wall)
{
    x++;           //Move forward 1 pixel (change this to x+=? to speed up the process)
    if x-par_player.x > spawnrangemax { x = par_player.x + spawnrangemin; }        //Loop back to the min range if you've moved too far
    if x == n instance_destroy();        //If you made a full loop, there's no free spot, so remove the enemy
}

You don't need to use a collision_circle because you're working in 1D space, whereas a circle collision would be used in 2D space. But I think obscene's idea still deviated from what you're looking for. My method isn't the fastest, because it does a collision check over and over and over then a destroy, but it was how I learned to do similar spawning (it was how Konami handled spawning zombies out of the ground in Castlevania III).
Alright, I tried this and it seemed to be working for a while. Now suddenly I'm getting the same freeze/crash... I don't know what I'm doing wrong, because it seems like your code should work.


I guess I'm just confused by the spawning I'm trying to do. I've spent several hours today trying to get them to work, and when it seems to be working, suddenly I'll get a crash. *sigh*

My goal is to emulate the spawning from Risk of Rain. Enemies spawn near the player, above floors, but never on top of the player.

It's late, I'm tired and frustrated, and I'll try to tackle this again tomorrow.
 
F

Feast For Worms

Guest
I should also note, it seems that sometimes the crash is caused by my spawning code, and other times is caused by my collision code. Mainly it's the spawning code, but there are a few times it's been due to the collision while loops. :confused:
 

TheouAegis

Member
When you say crash, do you mean the game freezes or the game closes automatically on its own or do you mean an error message pops up telling you the game needs to abort?

The only change to my code I'd consider making en lieu of your issues is:
Code:
var r = irandom_range(spawnrangemin,spawnrangemax);
var n = r;
with instance_create(par_player.x + r, par_player.y-2,  enemy1)
while place_meeting(x,y,par_wall)
{
   r++;           //Move forward 1 pixel (change this to x+=? to speed up the process)
   if r > spawnrangemax { r = spawnrangemin; }        //Loop back to the min range if you've moved too far
   if r == n instance_destroy();        //If you made a full loop, there's no free spot, so remove the enemy
   x = par_player.x + r;
}
The only reason I made this change is to avoid possible rounding errors.

Also your collision code is not right.
Code:
//Vertical Collision
if (place_meeting(x,y+vspd,par_wall))
{
   if(!place_meeting(x,y+sign(vspd),par_wall))  <<<<<<<<<<<<<<<<
   {
       y += sign(vspd);
   }
   vspd = 0;
   
...
}
y += vspd;
In this case, you do want a while loop.

Code:
if place_meeting(x,y+vspd,par_wall)
{
    while !place_meeting(x,y+sign(vspd),par_wall)
        y += sign(vspd);
    vspd = 0;

...
}
 
F

Feast For Worms

Guest
When you say crash, do you mean the game freezes or the game closes automatically on its own or do you mean an error message pops up telling you the game needs to abort?

The only change to my code I'd consider making en lieu of your issues is:
Code:
var r = irandom_range(spawnrangemin,spawnrangemax);
var n = r;
with instance_create(par_player.x + r, par_player.y-2,  enemy1)
while place_meeting(x,y,par_wall)
{
   r++;           //Move forward 1 pixel (change this to x+=? to speed up the process)
   if r > spawnrangemax { r = spawnrangemin; }        //Loop back to the min range if you've moved too far
   if r == n instance_destroy();        //If you made a full loop, there's no free spot, so remove the enemy
   x = par_player.x + r;
}
The only reason I made this change is to avoid possible rounding errors.

Also your collision code is not right.
Code:
//Vertical Collision
if (place_meeting(x,y+vspd,par_wall))
{
   if(!place_meeting(x,y+sign(vspd),par_wall))  <<<<<<<<<<<<<<<<
   {
       y += sign(vspd);
   }
   vspd = 0;
  
...
}
y += vspd;
In this case, you do want a while loop.

Code:
if place_meeting(x,y+vspd,par_wall)
{
    while !place_meeting(x,y+sign(vspd),par_wall)
        y += sign(vspd);
    vspd = 0;

...
}
Sorry, I should have clarified. It freezes. The debugger shows it's in the while loop of the spawning code. There is no error message.

And yeah, I have the while loops back in the collision scripts. I removed them at first because I thought they might be the culprit, but it was my original spawn script.

The majority of my enemies do spawn correctly now, but a certain enemy type (taller sprite, different origin & collision box) is still causing it to freeze.

I'll try your additions and report back.

While I have you here, would you recommend doing the x-coordinate or y-coordinate check for the spawning code? Did Castlevania do the y-coordinate check at a constant x-distance? I know it's probably personal preference and game specific for spawning code. I'm just trying to figure out which might cause the least issues for me.

Thanks for all of your help :)
 

TheouAegis

Member
The zombies were a constant x. It spawned at the edge of the screen, looked for valid terrain closest to the player's y or the bottom of the screen.

If you can, try to take a screenshot of the situation (i know it'll be hard to do ), at least of the layout of the room when the freeze typically happens and post the enemy's sprite (in case no enemies have spawned at the time of your screenshot).

But if the revised code I posted (which if you didn't notice handles the spawning too, so make sure you don't run the instance_create() twice) still has the freeze, then maybe it's something else that happens when the spawn code is supposed to run.
 
F

Feast For Worms

Guest
When you say crash, do you mean the game freezes or the game closes automatically on its own or do you mean an error message pops up telling you the game needs to abort?

The only change to my code I'd consider making en lieu of your issues is:
Code:
var r = irandom_range(spawnrangemin,spawnrangemax);
var n = r;
with instance_create(par_player.x + r, par_player.y-2,  enemy1)
while place_meeting(x,y,par_wall)
{
   r++;           //Move forward 1 pixel (change this to x+=? to speed up the process)
   if r > spawnrangemax { r = spawnrangemin; }        //Loop back to the min range if you've moved too far
   if r == n instance_destroy();        //If you made a full loop, there's no free spot, so remove the enemy
   x = par_player.x + r;
}
The only reason I made this change is to avoid possible rounding errors.

Also your collision code is not right.
Code:
//Vertical Collision
if (place_meeting(x,y+vspd,par_wall))
{
   if(!place_meeting(x,y+sign(vspd),par_wall))  <<<<<<<<<<<<<<<<
   {
       y += sign(vspd);
   }
   vspd = 0;
  
...
}
y += vspd;
In this case, you do want a while loop.

Code:
if place_meeting(x,y+vspd,par_wall)
{
    while !place_meeting(x,y+sign(vspd),par_wall)
        y += sign(vspd);
    vspd = 0;

...
}
So, good news! This prevented any while infinite loops! Some enemies are still spawning incorrectly (stuck in the walls and such) but that's due to their origins being off and such. This almost works perfectly now.

The only thing I've noticed is that my game is lagging a bit when the spawn code is run. If I put all of this stuff into a script and have it called instead of having it run through the alarm, would this speed up the process?

Thank you for all of your help so far :D
 

TheouAegis

Member
If it's in an alarm, it shouldn't lag that much. Like I said, you can speed things up by changing it from r++ to r+=n where n is larger than 1. If you put it in a script, it wouldn't matter because you'd still have to call the script through the alarm. If anything, that would be slower. Post the full object code for whatever is spawning your enemies. Also check the instance count and make sure you don't have a whole bunch of instances accidentally being created in the room.
 
F

Feast For Worms

Guest
If it's in an alarm, it shouldn't lag that much. Like I said, you can speed things up by changing it from r++ to r+=n where n is larger than 1. If you put it in a script, it wouldn't matter because you'd still have to call the script through the alarm. If anything, that would be slower. Post the full object code for whatever is spawning your enemies. Also check the instance count and make sure you don't have a whole bunch of instances accidentally being created in the room.
Yeah, I think the lag might be because I hadn't cleaned the cache in a while and I was running the debugger at the same time. So it seems to be better now.

Alright, here's the full object code for the spawner. I'm sure it'll need some adjusting and optimiziing (since I'm still pretty new to GML and coding in general and I can over-complicate stuff pretty easily). The step event is especially weird since I wrote that before I knew much about switch functions and haven't changed it since.

I also created some new variables, r1, r2, r3, n1, n2, & n3 so the enemies will spawn in different locations and not stack on each other.

The alarm event has a variable "roll" which rolls a 1/3 chance to spawn either 1, 2, or 3 enemies based on the player's level.

So... Here it goes. It's a long one in the alarm, but most of it is copy/paste with different variations of r & n.

CREATE EVENT

Code:
spawnrate1 = 10;
spawnrate2 = 14;

alarm[0] = room_speed*10;
spawn = true;

ALARM [1]

Code:
randomize();
var spawnrangemin, spawnrangemax, posneg, r1, r2, r3, n1, n2, n3;

posneg = choose(-1,1);
spawnrangemin = -500;
spawnrangemax = 500;
r1 = irandom_range(spawnrangemin,spawnrangemax);
r2 = irandom_range(spawnrangemin,spawnrangemax);
r3 = irandom_range(spawnrangemin,spawnrangemax);
n1 = r1;
n2 = r2;
n3 = r3;

var enemy1 = choose(obj_skeleton_2_spawn,obj_bat_spawn,obj_slug_spawn);
var enemy2 = choose(obj_skeleton_2_spawn,obj_bat_spawn,obj_slug_spawn,obj_spider_spawn);
var enemy3 = choose(obj_skeleton_2_spawn,obj_bat_spawn,obj_slug_spawn,obj_wraith_spawn,obj_spider_spawn);

spawn = true;
alarm[0] = room_speed*irandom_range(spawnrate1,spawnrate2);
roll = irandom(2)

if instance_exists(par_player) && global.level <=2
{
    if roll <= 1
    {
    if (spawn) && instance_exists(par_player)
    {
        with (instance_create(par_player.x + r1, par_player.y-2, enemy1))
        while place_meeting(x,y,par_wall)
        {
            r1++;
            if r1 > spawnrangemax
            {
                r1 = spawnrangemin;
            }
            if r1 == n1
            {
                instance_destroy();
            }
            x = par_player.x + r1;
        }
    }
    }else{
    if !instance_exists(par_player)
    {
        instance_destroy();
    }
    }
    }
  
    if roll == 2
    {
    if (spawn) && instance_exists(par_player)
    {
        with (instance_create(par_player.x + r1, par_player.y-2, enemy1)) {
      
        while place_meeting(x,y,par_wall)
        {
            r1++;
            if r1 > spawnrangemax
            {
                r1 = spawnrangemin;
            }
            if r1 == n1
            {
                instance_destroy();
            }
            x = par_player.x + r1;
        }
        with (instance_create(par_player.x + r2, par_player.y-2, enemy1)) {
      
        while place_meeting(x,y,par_wall)
        {
            r2++;
            if r2 > spawnrangemax
            {
                r2 = spawnrangemin;
            }
            if r2 == n2
            {
                instance_destroy();
            }
            x = par_player.x + r2;
        }
    }
    }
    }else{
    if !instance_exists(par_player)
    {
        instance_destroy();
    }
    }

}else{

if instance_exists(par_player) && global.level <=3
{
    if roll <= 0
    {
    if (spawn) && instance_exists(par_player)
    {
        with (instance_create(par_player.x + r1, par_player.y-2, enemy2)) {

        while place_meeting(x,y,par_wall)
        {
            r1++;
            if r1 > spawnrangemax
            {
                r1 = spawnrangemin;
            }
            if r1 == n1
            {
                instance_destroy();
            }
            x = par_player.x + r1;
        }
    }
    }else{
    if !instance_exists(par_player)
    {
        instance_destroy();
    }
    }
    }
  
    if roll >= 1
    {
    if (spawn) && instance_exists(par_player)
    {
        with (instance_create(par_player.x + r1, par_player.y-2, enemy2)) {

        while place_meeting(x,y,par_wall)
        {
            r1++;
            if r1 > spawnrangemax
            {
                r1 = spawnrangemin;
            }
            if r1 == n1
            {
                instance_destroy();
            }
            x = par_player.x + r1;
        }
        with (instance_create(par_player.x + r2, par_player.y-2, enemy2)) {

        while place_meeting(x,y,par_wall)
        {
            r1++;
            if r2 > spawnrangemax
            {
                r2 = spawnrangemin;
            }
            if r2 == n2
            {
                instance_destroy();
            }
            x = par_player.x + r2;
        }
    }
    }
    }else{
    if !instance_exists(par_player)
    {
        instance_destroy();
    }
    }
    }
}else{

if instance_exists(par_player) && global.level >= 4
{

    if roll == 0
    {
    if (spawn) && instance_exists(par_player)
    {
        with (instance_create(par_player.x + r1, par_player.y-2, enemy3)) {

        while place_meeting(x,y,par_wall)
        {
            r1++;
            if r1 > spawnrangemax
            {
                r1 = spawnrangemin;
            }
            if r1 == n1
            {
                instance_destroy();
            }
            x = par_player.x + r1;
        }
    }
    }else{
    if !instance_exists(par_player)
    {
        instance_destroy();
    }
    }
    }
  
    if roll == 1
    {
    if (spawn) && instance_exists(par_player)
    {
        with (instance_create(par_player.x + r1, par_player.y-2, enemy3)) {

        while place_meeting(x,y,par_wall)
        {
            r1++;
            if r1 > spawnrangemax
            {
                r1 = spawnrangemin;
            }
            if r1 == n1
            {
                instance_destroy();
            }
            x = par_player.x + r1;
        }
        with (instance_create(par_player.x + r2, par_player.y-2, enemy3)) {

        while place_meeting(x,y,par_wall)
        {
            r2++;
            if r2 > spawnrangemax
            {
                r2 = spawnrangemin;
            }
            if r2 == n2
            {
                instance_destroy();
            }
            x = par_player.x + r2;
        }
    }
    }
    }else{
    if !instance_exists(par_player)
    {
        instance_destroy();
    }
    }
    }
  
    if roll == 2
    {
    if (spawn) && instance_exists(par_player)
    {
        with (instance_create(par_player.x + r1, par_player.y-2, enemy3)) {

        while place_meeting(x,y,par_wall)
        {
            r1++;
            if r1 > spawnrangemax
            {
                r1 = spawnrangemin;
            }
            if r1 == n1
            {
                instance_destroy();
            }
            x = par_player.x + r1;
        }
        with (instance_create(par_player.x + r2, par_player.y-2, enemy3)) {

        while place_meeting(x,y,par_wall)
        {
            r2++;
            if r2 > spawnrangemax
            {
                r2 = spawnrangemin;
            }
            if r2 == n2
            {
                instance_destroy();
            }
            x = par_player.x + r2;
        }
        with (instance_create(par_player.x + r3, par_player.y-2, enemy3)) {

        while place_meeting(x,y,par_wall)
        {
            r3++;
            if r3 > spawnrangemax
            {
                r3 = spawnrangemin;
            }
            if r3 == n3
            {
                instance_destroy();
            }
            x = par_player.x + r3;
        }
        }
    }
    }
    }else{
    if !instance_exists(par_player)
    {
        instance_destroy();
    }
    }
    }

}
}
}


STEP EVENT


Code:
if instance_exists(obj_player)
{
    if global.level <= 2
    {
        spawnrate1 = 10;
        spawnrate2 = 14;
    }else{
        if global.level <= 4
        {
            spawnrate1 = 8;
            spawnrate2 = 12;
        }else{
            if global.level <= 6
            {
                spawnrate1 = 6;
                spawnrate2 = 9;
            }else{
                if global.level <= 8
                {
                    spawnrate1 = 5;
                    spawnrate2 = 8;
                }else{
                    if global.level <= 10
                    {
                        spawnrate1 = 5;
                        spawnrate2 = 7;
                    }
                }
            }
        }
    }
}
 
F

Feast For Worms

Guest
Everything seems to be working pretty well by now. I did add a small addition to my collision code, just to make sure enemies that happen to get stuck in walls and such are deactivated.

I don't use solids for collisions, but just to deactivate objects that might happen to get stuck in walls or spawn incorrectly, I set my par_wall object as solid and do a if !place_free(x,y) {instance_deactivate_object(self)} check. I run it during the collision code so they'll instantly be deactivated.

Everything works really well so far! Occasionally I'll have some framerate issues, but this is most likely related to background applications and such rather than GMS2 itself, because I can't replicate it within GMS2.

Marking this thread as solved and hopefully it will help someone in the future. And if you have any ideas on how I can optimize or improve this further, please let me know :)
 
Top