• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

[SOLVED] Collect items only once

[Game Maker 8]

Hey guys!

I'm making a platformer-adventure thingy game and I have a room with a lot of coins. Now If I would like the player to be able to collect them only once, what would be the best way to do this? They respawn everytime I come back into this room.
Thing is: first I tried to create a variable, that checks, whether the coin was collected before or not. But i actually didn't know how to initialise it because it's no global variable.

My create event looked like this:

variableabc = 0;

And that was the problem (i know, it's kinda stupid). Everytime I enter the room it's telling the coin: nope, you haven't been picked already.

I also tried to initialise the variable in my 'start game' button event with:

with(obj_coin) {variableabc =0;}

Didn't work either, as you might already know while you read this. I'm still learning and I would like to know how to solve this one.

Thanks for any help!

#YOCO (you only collect once) ;-)
 
V

VansiusProductions

Guest
Best way to do it is store the variable in a file, and load it every time you start the game.
 

NightFrost

Member
Saving to file is necessary only if you want to be able to collect the coins only once, ever, or you do game saves (in which case you're saving lot of other information besides just coin collect states). Otherwise, a global data structure or array that tracks coin collection states is enough, you just must remember to reset it after a game over. Once you have collection state tracking, you check the state in the coin's create code, and immediately destroy any coin that finds itself already collected. That way it no longer exists when draw events come around and will not flash on screen for one frame. If your coins are completely static, you can use their room and coordinate information to uniquely identify each - since you probably have lots of coins so giving each am otheriwse unique identifier would be slower.
 
Saving to file is necessary only if you want to be able to collect the coins only once, ever, or you do game saves (in which case you're saving lot of other information besides just coin collect states). Otherwise, a global data structure or array that tracks coin collection states is enough, you just must remember to reset it after a game over. Once you have collection state tracking, you check the state in the coin's create code, and immediately destroy any coin that finds itself already collected. That way it no longer exists when draw events come around and will not flash on screen for one frame. If your coins are completely static, you can use their room and coordinate information to uniquely identify each - since you probably have lots of coins so giving each am otheriwse unique identifier would be slower.
This might help me!
Can you give me a short example, if how such a file might look? Thanks a ton!!
 

NightFrost

Member
At the start of your game, create a ds map to hold information on collected coins, make sure you create this only once:
Code:
global.Collected_Coins = ds_map_create();
When you pick up a coin add that information to the map:
Code:
var Identifier = string(room) + "-" + string(x) + "-" + string(y); // Create unique identifier from room id and x/y position
ds_map_add(Identifier, true);
Coin's create event checks if it has already been collected when you enter a room:
Code:
var Identifier = string(room) + "-" + string(x) + "-" + string(y); // The unique identifier again
if(!is_undefined(ds_map_find_value(global.Collected_Coins, Identifier))){ // Try finding the identifier in the ds map; if not found, it returns undefined
    instance_destroy(); // Destroy the coin as it was in the ds map
}
On game over, clear the map so all the coins are present when player starts a new game:
Code:
ds_map_clear(global.Collected_Coins);
 
At the start of your game, create a ds map to hold information on collected coins, make sure you create this only once:
Code:
global.Collected_Coins = ds_map_create();
When you pick up a coin add that information to the map:
Code:
var Identifier = string(room) + "-" + string(x) + "-" + string(y); // Create unique identifier from room id and x/y position
ds_map_add(Identifier, true);
Coin's create event checks if it has already been collected when you enter a room:
Code:
var Identifier = string(room) + "-" + string(x) + "-" + string(y); // The unique identifier again
if(!is_undefined(ds_map_find_value(global.Collected_Coins, Identifier))){ // Try finding the identifier in the ds map; if not found, it returns undefined
    instance_destroy(); // Destroy the coin as it was in the ds map
}
On game over, clear the map so all the coins are present when player starts a new game:
Code:
ds_map_clear(global.Collected_Coins);
Thanks a lot!! I'm going to try this out! For the game over: would it also be okay to clear the map whenever I press 'new game'? Because it's an adventure game with different save slots. I'd consider it a platformer RPG... Something like this.
 
Last edited:

NightFrost

Member
Yes, it doesn't matter when you clear the ds map as long as you do it before a new game is started. If you're doing save slots, record the information along with the rest of your stuff.
 
Yes, it doesn't matter when you clear the ds map as long as you do it before a new game is started. If you're doing save slots, record the information along with the rest of your stuff.
Well.. To be honest: I use the built-in save functions and save additional information in ini files.
 

NightFrost

Member
I just realized, if you're saving the coin info, there is a case where the system can blow up. Theoretically, room ids may change at every execution, but in practice GM assigns same id numbers for them... except when you add new rooms in between. To safeguard agains this, for the coin's unique identifier you should use room name instead of id, changing the code into:
Code:
var Identifier = room_get_name(room) + "-" + string(x) + "-" + string(y);
This ensures saved data remains valid even after you've added rooms, at least until you start changing room names in the resource tree. Then I would say it turns into a matter of coding the game to support save files of older versions... (though even larger companies do go, nope, old saves are not supported from version X onwards.)
 
Uhm the is_undefined function doesn't exist in gm8.. How would I do it then? ds_map_exists(id, key) does exist. Is that kinda the same thing?

@NightFrost: I tried to figure it out on my own, but I failed, since I never used data structures before :/
I don't want you to solve this one for me, I want to understand ds maps. Thanks for your help so far! Really helped me to understand what ds maps are. Now I need to learn how they work. I wanted to check a value, but since it's a unique identifier, it seemed impossible to me.
 
Last edited:

trg601

Member
You could loop through the ds_map and delete every instance in the map.
Here is an example of how you can iterate (loop) through a map:
Code:
var key = ds_map_find_first(map);

for(i=0;i<ds_map_size(map)
{
    with(ds_map_find_value(map,key)
    {
        instance_destroy();
    }
    key=ds_map_find_next(map,key);
}
 
You could loop through the ds_map and delete every instance in the map.
Here is an example of how you can iterate (loop) through a map:
Code:
var key = ds_map_find_first(map);

for(i=0;i<ds_map_size(map)
{
    with(ds_map_find_value(map,key)
    {
        instance_destroy();
    }
    key=ds_map_find_next(map,key);
}
Cool! I'm gonna try this one out as soon as I'm back from work =_=
:D thanks!
 
Yeah, I used ini files to save several values on game, but I think about entirely switching to ds, because the ini file can be manipulated too easily.
 
T

Ting_Thing

Guest
I use a totally different method for this kind of issue. When a coin is picked up, I create an invisible, persistent instance over it before removing the coin. I then save the room ID to the invisible instance as a holding variable.

In the create event of a coin, I check to see if the persistent instance is already on top of it. If so, I check that the persistant instance is from the correct room. If both are true, the coin is removed.

When I save the game, the x,y, and ID variable are saved. When I load the game, it is recreated using those variables.

The downside is that there may be a lot of instances in the room at any given time, but I'm careful to keep them disabled unless I need them.
 

RangerX

Member
Another way you can do this is to create an array in the global at the beginning of your game. Each of your coin could "register" themselves when the room starts, and become "collected" later or whatever stats you'd like to track.
That's how I do with my game actually. I track prettty much everything in arrays and then when a room starts, the objects adjust themselves "as it was before".
 
G

gamer_essence

Guest
yeah like NightFrost said make some global event like he had and then try that.
 
Another way you can do this is to create an array in the global at the beginning of your game. Each of your coin could "register" themselves when the room starts, and become "collected" later or whatever stats you'd like to track.
That's how I do with my game actually. I track prettty much everything in arrays and then when a room starts, the objects adjust themselves "as it was before".
Cool, could you please post a short code example of how this would look?
 
You could loop through the ds_map and delete every instance in the map.
Here is an example of how you can iterate (loop) through a map:
Code:
var key = ds_map_find_first(map);

for(i=0;i<ds_map_size(map)
{
    with(ds_map_find_value(map,key)
    {
        instance_destroy();
    }
    key=ds_map_find_next(map,key);
}
Uhm isn't the for statement incomplete?
 

RangerX

Member
Cool, could you please post a short code example of how this would look?
I'll try to make it clear and understandable.


Code:
// first of all, at the opening of your game you need to declare the array that will contain your collectibles
// for loop to create an array of 100 items
//  the 2 positions in the array are: [Item id, Collected or not]

for(i=0; i<100; i+=1) 
{
global.CollectablesArray[i,0]=0;
}


// Now when your put a collectable in your room, you need to give it a simple ID.
// This is for the "item ID" position in the array.
// Now when your character touches a collectable, you need to change track its collected now...

global.CollectablesArray[itemID,0]=1;


// Now in the step event of the collectible, make it check if needs to be there or not...

if(global.CollectablesArray[MyID,1])               
then
{
with (self) instance_destroy();                        // byebye!
}

}
 
I'll try to make it clear and understandable.


Code:
// first of all, at the opening of your game you need to declare the array that will contain your collectibles
// for loop to create an array of 100 items
//  the 2 positions in the array are: [Item id, Collected or not]

for(i=0; i<100; i+=1)
{
global.CollectablesArray[i,0]=0;
}


// Now when your put a collectable in your room, you need to give it a simple ID.
// This is for the "item ID" position in the array.
// Now when your character touches a collectable, you need to change track its collected now...

global.CollectablesArray[itemID,0]=1;


// Now in the step event of the collectible, make it check if needs to be there or not...

if(global.CollectablesArray[MyID,1])              
then
{
with (self) instance_destroy();                        // byebye!
}

}
Yep, that makes sense! Thanks! The item id is for example obj_coin? Or an integer? (I'm just learning gml and want to improve my skills and gain knowledge, so sorry I'm making this a big deal)
Thanks!
 

RangerX

Member
Yep, that makes sense! Thanks! The item id is for example obj_coin? Or an integer? (I'm just learning gml and want to improve my skills and gain knowledge, so sorry I'm making this a big deal)
Thanks!
Its an id you give to the object yourself. Just put an integer from 0 to X number you have in the game.
 

RangerX

Member
I don't think you get it. Its a simple variable.
Per example, in the creation code of the first collectable you put in your room, you write " MyID=0; " (because with code we start counting at 0)
This is just a simple variable containing a number that will help you track the state of that collectable.

Now imagine we have an array variable with 10 positions instead of 100 from my previous example. A 2D array also tracking if a collectable is collected or not. Let's say we declare it as: global.CollectablesArray[id of the collectible, Collected or not];
Do you know what an array is? You need to master that very basic stuff if you want to progress.
It would look like this at first:

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 --- this row is the ID of the collectable (their "MyID" variable in creation code)
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 --- this row is the fact they are collected or not (at first its 0 for "not collected")

Now when the player actually collect of one them, you update the array consequently:
global.CollectablesArray[MyID, 1];

This will go at the position of the ID you gave in the array and change the second row to "1" which would mean its collected. Since in the example, "MyID" is 0, the array would then look like this:

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 --- basically meaning that you're tracking the fact that the collectable of the ID "0" is now collected (1).


You can follow so far or... ?





Now imagine you come back later into that room and you want to make so that collectable appear as collected (therefore not appear lol).
In that collectible (create or step, doesn't much matter in this case), you go:

if (global.CollectablesArray[MyID,0]==1)
then
{
instance_destroy;
}

So in the case here, since the "MyID" of the collectable was 0 and the second row of the array at that position was 1 (collected), we destroy immediately the collectible therefore the player won't see it. The room will appear as if it was never there.
 
I don't think you get it. Its a simple variable.
Per example, in the creation code of the first collectable you put in your room, you write " MyID=0; " (because with code we start counting at 0)
This is just a simple variable containing a number that will help you track the state of that collectable.

Now imagine we have an array variable with 10 positions instead of 100 from my previous example. A 2D array also tracking if a collectable is collected or not. Let's say we declare it as: global.CollectablesArray[id of the collectible, Collected or not];
Do you know what an array is? You need to master that very basic stuff if you want to progress.
It would look like this at first:

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 --- this row is the ID of the collectable (their "MyID" variable in creation code)
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 --- this row is the fact they are collected or not (at first its 0 for "not collected")

Now when the player actually collect of one them, you update the array consequently:
global.CollectablesArray[MyID, 1];

This will go at the position of the ID you gave in the array and change the second row to "1" which would mean its collected. Since in the example, "MyID" is 0, the array would then look like this:

0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 --- basically meaning that you're tracking the fact that the collectable of the ID "0" is now collected (1).


You can follow so far or... ?





Now imagine you come back later into that room and you want to make so that collectable appear as collected (therefore not appear lol).
In that collectible (create or step, doesn't much matter in this case), you go:

if (global.CollectablesArray[MyID,0]==1)
then
{
instance_destroy;
}

So in the case here, since the "MyID" of the collectable was 0 and the second row of the array at that position was 1 (collected), we destroy immediately the collectible therefore the player won't see it. The room will appear as if it was never there.
Yes I knew that,, thanks. I simply wasn't sure about that myID thing, because I was a little confused. Sorry. I sometimes even can't explain what's "inside my head", because English is my foreign language. I know how 2d arrays work and it sounds all logical to me. I was just unsettled. Thanks for your help I guess this will work. I'll update this thread as soon as it's solved. Thanks to you all for your patience and your replies!
 
Last edited:
Top