Legacy GM For loop in the Destroy event [SOLVED]

O

Oz Locke

Guest
The issue was being caused by a part of the code that protected the player from new asteroids being spawned on top of them. There were a few fixes, including disabling that feature (not desirable!), disabling destruction of asteroids while invincible, or stopping the child asteroids from inheriting that particular code (as it's not really relevant for the children anyway)
------------------------------------------------------------------------------------------------------------------------------

I've been following the excellent intro videos from @ShaunJS and I've encountered an odd issue when trying to be a smart-ass on the destroy event for an object.

The object spawns two children when destroyed. For simplicity the course simply duplicates the spawn call:

Code:
instance_create(x, y, obj_asteroid_s);
instance_create(x, y, obj_asteroid_s);
Which works fine...

But I've been going over my code and converting to DRY and Object Oriented code where I can, for my own learning, so I tried replacing this with a for loop:

Code:
for(i = 0; i < 2; i++){
    instance_create(x, y, obj_asteroid_s);
};
The code works, in that it spawns two children, but it doesn't spawn them in the correct location.
It spawns them together, but they spawn at a random location in the room.

I can't see anything wrong with my loop, am I misunderstanding how the destroy event works? Do the position co-ordinates of the object get lost before the loop can run?
 
Last edited by a moderator:

Roderick

Member
I don't see any issues with your code, nor a reason why the objects would spawn at a random location. Double check your code in your project to make sure there are no typos, as what you have above should work.

A couple tips to clean up your code though:

You should use
Code:
for(var i = 0; i < 2, i++)
By using "var", you declare it a local variable, and the memory is freed as soon as the event ends. It's a general habit you should get into, because if you have a lot of objects calling for loops, and NOT setting the counter variable as local, it creates a memory leak that can eventually cause problems.
When you destroy an object, all of its variables are freed up, so it won't actually make a difference in this case, but it's still a good habit to get into.

Simply, if you're never going to use the contents of the variable again, regardless of what you're using it for, declare it with "var".

Adding onto that, you should only use a for loop if you actually need the counter variable for something. Since it makes no difference to the code in the loop whether you're on the first iteration or the second, there's no need to set, check, and increment the counter. Just use a repeat loop instead.

Code:
repeat (2) {instance_create(x, y, obj_asteroid_s);}
 
O

Oz Locke

Guest
@Roderick Awesome advice, thank you!

I was aware of the var declaration, but not of the implications of not using it... and I didn't even know repeat existed!

If I don't var i is there then also a risk that other loops that use it could interfere with it?
 
O

Oz Locke

Guest
I've updated the code with the repeat code provided here, but the issue is persisting :(
 
O

Oz Locke

Guest
After a bit more testing it looks like it's working OK if a bullet hits the asteroid, but not if the ship does. Both collisions are stored on their respective objects, but they both only pass a destroy_instance command to the asteroid.
 

Roderick

Member
Generally not. There are three basic ways of declaring variables.

Code:
var i = 0
This is a local variable. When the script or event that created it ends, the variable is deleted. Even if the same script or event that originally called it is run again, that variable will not be there until it is created again.
This type of variable cannot be referenced by other instances, or even different events in the same instance, because it will be gone by the time they have a chance to refer to it.

Code:
i = 0
This is an instanced variable. It belongs to the instance that created it. If it was created by a script, it belongs to the instance that called the script. This variable will persist until the instance that owns it is destroyed; at that point, all instanced variables owned by that instance are destroyed as well.
You can refer to an instanced variable owned by a different instance by using the instance identifier, followed by a dot, followed by the variable name. If you refer to an instanced variable in a different instance without the instance id, it will be a new, separate variable, even though they have the same name.

Code:
global.i = 0
This is a global variable. It can be called by any instance at any time after its creation. It will not be destroyed until your game is closed. It can have the same name as local or instanced variables, and each will be handled separately. global.i is different from obj_player.i and from i (the latter of which refers to the variable "i" owned by the current instance).

Regarding instance ids:

You can call an instanced variable by using an object id. For example, up above, I made reference to obj_player.i. You should NEVER refer to something by its object id unless you are absolutely certain that there will only ever be one instance of that object in existence at a time! If you use an object id to call an instance when there are multiple instances of the same object, you will get the id of the one with the first id number. Since you have no way to be certain if that's the one you want, you can never be sure you're pulling from the right instance.

If you are using a collision event, use other. "other" is a special keyword which means "the instance id of the instance that isn't me that is being used in this event".

If you are going to be referring to an instance repeatedly, you can store its id in a variable. The easiest way to do so is at the time of creation. For example, in your question above, you could do the following:

Code:
for (var i = 0; i < 2; i++)
{asteroid_s[i] = instance_create(x, y, obj_asteroid_s);}
That stores the instance ids of the two created asteroids in an array named asteroid_s, with the first in the [0] entry, and the second in the [1] entry.

You can also use the "id" variable that is built in to every instance. If one instance needs to remember who it last hit for some reason, you could use the code:

Code:
collider = other.id
This would store the id of the other instance, until the code was called again, and it was replaced by the new "other".

Finally, if you know the id number, you can reference it directly.

Code:
(10654).x = 100;
would set the x position of whatever instance has the id 10654 to 100. If 10654 doesn't exist, the line would result in an error. When directly referencing an id number, you must use parentheses, or else the system will try to interpret it as a number, rather than an id.
 

Roderick

Member
After a bit more testing it looks like it's working OK if a bullet hits the asteroid, but not if the ship does. Both collisions are stored on their respective objects, but they both only pass a destroy_instance command to the asteroid.
Could you upload your .gmz file somewhere (Dropbox is popular for such things), so that I can look over your code? It would be easier than me trying to guess which blocks of code I need to ask you to post.
 

Roderick

Member
Unrelated to your problem: If you need to comment out a block of code, instead of putting // in front of every line, enclose it in /* */ to quote out the whole thing.

Code:
/* This entire code block is commented out.
for (var i = 0; i < 3000000000000; i += 0.001)
{draw_point(irandom(room_width), irandom(rooom_height), choose(c_white, c_green, c_blue_ c_red));}

It looks like this should be drawing three trillion randomly placed colored dots around the screen, but it will actually do nothing, because it's all in the comment block. */
I don't know what to tell you, @Oz Locke. Try as I might, I cannot replicate your issue. The small asteroids are spawning at the location of the big one, regardless of whether it's my ship or shot that connects.

One thing that I noticed: Your shot sometimes passes through the asteroids. This is because, when you use Game Maker's built in movement (direction, speed, etc), the object doesn't actually move along a path; rather it teleports from where it should be from one step to the next. Is it possible that you're missing the asteroid that you're aiming at, and hitting one behind it?

One possible fix to the "teleporting" problem is to double the number of steps per second in your room's settings, and then halve the speed of your objects. They'll go the same speed overall, because they're moving more often, but the smaller jumps mean that they're less likely to completely bypass other objects.
 
Last edited:

FrostyCat

Redemption Seeker
The problem comes from when your ship collides with an asteroid while still invincible. The large asteroid gets destroyed and spawns small asteroids in-situ as instructed, you don't have a problem there. But since you programmed the asteroids to avoid the player upon creation, the small asteroids are forced to reposition elsewhere since the original position of the large asteroid is still occupied by the invincible ship.
 
O

Oz Locke

Guest
Thanks @Roderick, I do sometimes use milti-line, but for quickly disabling something single line is just quicker. On that note though, I don't suppose there's an auto comment button or something is there?
Also, is there no button for accepting a code completion suggestion? Most IDE's I've worked with auto-complete the suggestion on tab, so I keep doing that and having to go back to fix it.

@FrostyCat An amazing catch! I'd forgotten about that bit, it was a part of the course from many videos ago. I guess the simplest fix is to disable the destruction of the asteroid, or at least during the invincibility phase.

The code prevents new asteroids from spawning over the player, so it's not relevant to the children as the they spawn behind an existing asteroid, so I'll stop the children from inheriting that particular bit of code.
 
Last edited by a moderator:
Top