GMS 2.3+ Advice on Reusing Code for an Item Shop

NTiger7

Member
IDE v. 2.3.0.529, runtime v. 2.3.0.401

So I successfully have made an shop where the player can purchase items from and it will be added to their inventory, but I would like to use the same code for all the other shops so it doesn't use up more memory. The only thing I would have to change is what items are being displayed, how many items are available to purchase at the particular store, and converting the items in the store to the items in the inventory. I have used the create event, draw gui event, step event, and clean up event in my object where the item shop code is located. How would I go to reuse this? In order to get the shop to display, the player has to run into the trigger area and press 'w' and then the shop will display. Should I use this trigger area to rename variables? Is it even possible? Should I change this to a script? I don't know what is the best course of action for this. Here is the code I would like to change:

In the create event (Note: I just am showing one item as an example, but I have several different items here).
GML:
shop_items = 6;
/................... Several lines down
enum burgershopItems {
    burger                = 0,  
}

//Shop Inventory
ds_shop[# 0, 0] = burgershopItems.burger;


//Price per item
ds_shop[# 1, 0] = 5;

#region Create Items Info Grid
ds_shop_items_info = ds_grid_create(4, burgershopItems.height);

//---Item Names
//example
var z = 0, i = 0;
ds_shop_items_info[# z, i++] = "Burger";

//---Item Descriptions
var z = 1, i = 0;
ds_shop_items_info[# z, i++] = "This is the item description.";

//----Mass in kg
var z = 2, i = 0;
ds_shop_items_info[# z, i++] = 0.200;

//---Item Volume in cm^3
var z = 3, i = 0;
ds_shop_items_info[# z, i++] = 194;
In the draw gui event:
GML:
var shop_grid = ds_shop;
shop_Info_grid = ds_shop_items_info;
//Anywhere in the draw GUI where these variables are found would equal the new grid
In the step event:
GML:
if (mouse_check_button_pressed(mb_left)) && (shop_hovered_slot >= 0) {
    global.totalMoney = global.totalMoney -    ss_item_price;
    with (oInventory) {
        var inv_grid = ds_inventory;
        var ii = 0;
       
        repeat (inv_slots) {
            var iItem = inv_grid[# 0, ii];
            if (iItem > 0) ii++;
            else {
                if (ss_item = 0) {
                    for (var j = 0; j < inv_slots; j++) { //if that item exists, stack on top of it
                        if (inv_grid[# 0, j] = 1) {
                            inv_grid[# 1, j] += 1;
                            totalMass += ds_items_info[# 2, inv_grid[# 0, j]];
                            totalVolume += (ds_items_info[# 3, inv_grid[# 0, j]] / 1000);
                            break;
                        }
                    }
                    if (j == inv_slots) { //if item does not exist, create new item
                        inv_grid[# 0, ii] = 1;
                        inv_grid[# 1, ii] = 1;
                        totalMass += (inv_grid[# 1, ii] * ds_items_info[# 2, inv_grid[# 0, ii]]);
                        totalVolume += (inv_grid[# 1, ii] * (ds_items_info[# 3, inv_grid[# 0, ii]] / 1000));
                    }
                }
There are multiple else ifs with each item. There probably is a more efficient way to do this but I'm not sure.

And the clean up event:
GML:
ds_grid_destroy(ds_shop);
ds_grid_destroy(ds_shop_items_info);
Or do I have to duplicate the object and change all the instance variable names for each shop (which there will be several so it will just take some time)? If the code I have known is not enough, just let me know, but all the other code would be the same except if I need to rename the instance variables. Thank you for any advice.
 

Nidoking

Member
Depending on whether the shops are instances you place in the Room Editor or created through code, you could use Variable Definitions/Creation Code or a function, accordingly.
 

NTiger7

Member
Depending on whether the shops are instances you place in the Room Editor or created through code, you could use Variable Definitions/Creation Code or a function, accordingly.
The shops are instances that are added to the room when the player presses the 'W' button on the trigger. Do grids work in the creation code?

I did try to move the code I had in the create to the creation code but it didn't work and the game crashed stating the variable names were not defined before entering the room. I tried doing it like this in the creation code, with b_shop_items being defined in the creation code:
GML:
shop_items = other.b_shop_items;
I guess I could make a function, but I would have to make a different function for each shop since I would need it to change what items are being shown depending on which store the player is in and I don't know/think one function is possible. I'm not sure if it would take more/less memory than multiple objects.
 

Nidoking

Member
I would have to make a different function for each shop since I would need it to change what items are being shown depending on which store the player is in and I don't know/think one function is possible.
I don't think you understand what functions are. Functions are bits of code that take arguments, also known as parameters, to tell them how to operate in different ways. This is literally how you tell one block of code to do multiple things, like populate a shop with different items for sale. You pass a data structure of some kind with the items you want as an argument, and write the function to read the values from the data structure and populate the store.

I did try to move the code I had in the create to the creation code but it didn't work and the game crashed stating the variable names were not defined before entering the room.
I also don't think you understand what creation code does. The point here is to use variables in the shop object to tell it what items to create, and to use the creation code to change what those variables are before its Create event runs. But since you're creating the instances, you'll need to do that either with a function, as discussed, or by setting the variables before the shop populates. I highly recommend the function approach.
 

NTiger7

Member
I don't think you understand what functions are. Functions are bits of code that take arguments, also known as parameters, to tell them how to operate in different ways. This is literally how you tell one block of code to do multiple things, like populate a shop with different items for sale. You pass a data structure of some kind with the items you want as an argument, and write the function to read the values from the data structure and populate the store.



I also don't think you understand what creation code does. The point here is to use variables in the shop object to tell it what items to create, and to use the creation code to change what those variables are before its Create event runs. But since you're creating the instances, you'll need to do that either with a function, as discussed, or by setting the variables before the shop populates. I highly recommend the function approach.
Sorry, I wasn't fully thinking yesterday. I understand how functions work.

In the create event of oShop, I put this code in:
GML:
shop_items = other.shop_items;
shop_name = other.shop_name;
In terms of creation code, if I put this in the create event of the object oShopTrigger, which is used to trigger the shop:
GML:
with (oShop) {
    shop_items = 12;
    shop_name = "CSshop";
}
I got an error code stating that shop_items was not defined. The error says this:
ERROR in
action number 1
of Create Event
for object oShop:

Variable oShop.<unknown variable>(100019, -2147483648) not set before reading it.
at gml_Object_oShop_Create_0 (line 4) - shop_items = other.shop_items;
In my mind, I was thinking that I could define what shop_items equals in the trigger, but that may not be possible because it is not oShop.

I made two different functions, one called ChangeShopItems and the other called ChangeShopItemDesc and the functions work out fine. I tested it out by defining what shop_items and shop_name was in the create event of oShop and the shop loaded no problem. If someone wants to use the same idea, this is what I did in my scripts (Note: I am only showing an example of one item since I don't want people to see all the items I made for my store):

ChangeShopItems:
GML:
function ChangeShopItems(_numShopItems, _ShopName){
    with (oShop) {
        shop_items = _numShopItems;
        shop_name = _ShopName;
    }
    
    if (_ShopName == "CSshop") {
        var ds_shop = ds_grid_create(2, _numShopItems);

        enum CSshopItems {
            hamsandwich            = 0,
            //...
            height                = 12
        }

        //Shop Inventory
        ds_shop[# 0, 0] = CSshopItems.hamsandwich;

        //Price per item
        ds_shop[# 1, 0] = 5;
    }
    
    return ds_shop;
}
ChangeShopItemDesc:
GML:
function ChangeShopItemDesc(_ShopName){
    with (oShop) {
        shop_name = _ShopName;
    }   
    
    if (_ShopName == "CSshop") {
        #region Create Items Info Grid
        var ds_shop_items_info = ds_grid_create(4, CSshopItems.height);

        //---Item Names
        var z = 0, i = 0;
        ds_shop_items_info[# z, i++] = "Ham Sandwich";

        //---Item Descriptions
        var z = 1, i = 0;
        ds_shop_items_info[# z, i++] = "This is the item description.";

        //----Mass in kg
        var z = 2, i = 0;
        ds_shop_items_info[# z, i++] = 0.250;

        //---Item Volume in cm^3
        var z = 3, i = 0;
        ds_shop_items_info[# z, i++] = 194;
        #endregion
    }
        
    return ds_shop_items_info;
}
Where I can define what shop_items and shop_name are equal to so that I can make this object work with a variety of shops?
 

Nidoking

Member
In the create event of oShop, I put this code in:
GML:
shop_items = other.shop_items;
shop_name = other.shop_name;
Ah, so you just don't know what "other" is. Create events are neither collision events nor with blocks, so there is no "other". What you want to put in the Create event is either a default value for these variables or nothing. If it's nothing, then you have to be sure that everything the oShop does accounts for the fact that the variables may not have been set yet.

What you're doing with those functions is... well, bad. I suppose you can always find the definition for each shop within the function, but that's a lot of hardcoding. Why bother passing in the name of the shop if you're just going to hardcode everything else? You could make a big data structure to hold all of that information and pass in an index into that data structure. And what's with redefining variables every time, incrementing i, and then throwing away that value when you redefine the same variables again? I take it you don't know what i++ means either. Heck, this is GML 2.3. You can write a constructor function to build a struct that has the information about each shop, then either make an array of those or, ideally, throw them in a map. Now you just grab the map entry for the name of the shop, and bingo, it has all of the information you need to make that shop. Or, since it looks like you're using a grid to hold the information, you could build grids and store them in the map. That way, you load all of the information for all of the stores once, when the game starts, and populating a store is as easy as looking up the name in the map. I would switch to structs, though. Using numbered indices for information is inferior to giving it names. Why would you ds_shop_items_info[# item_num, 3] when you could struct_shop_items_info[item_num].volume?
 

NTiger7

Member
Ah, so you just don't know what "other" is. Create events are neither collision events nor with blocks, so there is no "other". What you want to put in the Create event is either a default value for these variables or nothing. If it's nothing, then you have to be sure that everything the oShop does accounts for the fact that the variables may not have been set yet.

What you're doing with those functions is... well, bad. I suppose you can always find the definition for each shop within the function, but that's a lot of hardcoding. Why bother passing in the name of the shop if you're just going to hardcode everything else? You could make a big data structure to hold all of that information and pass in an index into that data structure. And what's with redefining variables every time, incrementing i, and then throwing away that value when you redefine the same variables again? I take it you don't know what i++ means either. Heck, this is GML 2.3. You can write a constructor function to build a struct that has the information about each shop, then either make an array of those or, ideally, throw them in a map. Now you just grab the map entry for the name of the shop, and bingo, it has all of the information you need to make that shop. Or, since it looks like you're using a grid to hold the information, you could build grids and store them in the map. That way, you load all of the information for all of the stores once, when the game starts, and populating a store is as easy as looking up the name in the map. I would switch to structs, though. Using numbered indices for information is inferior to giving it names. Why would you ds_shop_items_info[# item_num, 3] when you could struct_shop_items_info[item_num].volume?
I'm sorry, I am really new to Gamemaker in general (started less than a month ago). I have been following along with different tutorials on YouTube, and to make the shop, I based it off a tutorial on how to make an inventory, and the tutorial was with an older version of Gamemaker 2, which is why I didn't do it any of the ways you recommend since I didn't know they existed in the first place. I have no idea what a structure, map, or constructor are. I am looking at what they all mean from the documents posted by yoyogames as well as the the post about the updates of 2.3 to try and understand what they all do. I probably need to update how my inventory works as well.

I understand what i++ does, I was just following along a tutorial and that was what they did in it.

I have only have made basic programs (generally less than 100 lines of code) for assignments in school (with C, C++, and python) so I haven't really needed to worry about efficiency before.

Thank you for the help.
 

NTiger7

Member
Ah, so you just don't know what "other" is. Create events are neither collision events nor with blocks, so there is no "other". What you want to put in the Create event is either a default value for these variables or nothing. If it's nothing, then you have to be sure that everything the oShop does accounts for the fact that the variables may not have been set yet.

What you're doing with those functions is... well, bad. I suppose you can always find the definition for each shop within the function, but that's a lot of hardcoding. Why bother passing in the name of the shop if you're just going to hardcode everything else? You could make a big data structure to hold all of that information and pass in an index into that data structure. And what's with redefining variables every time, incrementing i, and then throwing away that value when you redefine the same variables again? I take it you don't know what i++ means either. Heck, this is GML 2.3. You can write a constructor function to build a struct that has the information about each shop, then either make an array of those or, ideally, throw them in a map. Now you just grab the map entry for the name of the shop, and bingo, it has all of the information you need to make that shop. Or, since it looks like you're using a grid to hold the information, you could build grids and store them in the map. That way, you load all of the information for all of the stores once, when the game starts, and populating a store is as easy as looking up the name in the map. I would switch to structs, though. Using numbered indices for information is inferior to giving it names. Why would you ds_shop_items_info[# item_num, 3] when you could struct_shop_items_info[item_num].volume?
Ok, so I have finally had time to work on my game again and looked into what you suggested. So if I make an item class as a constructor, which looks like this:
GML:
function Item () constructor {
    name = "";
    price = 0;
    description = "";
    weight = 0;
    volume = 0;
}
And then I inherit the item constructor for each item such as this:
GML:
function Burger () : Item () constructor {
    name = "Burger";
    price = 5;
    description = "Description here";
    weight = 0.200;
    volume = 194;   
}
And I do this for all my items, how would I use it instead of a grid? Like, can a store constructor inherit from a constructor? Or do I just make a struct for each shop? Like, I want to have the information of each item available to that particular store. You mentioned something about maps, but if I did it this way, I wouldn't need to use a map correct? Or did you mean using array structs?

With a grid, you can cycle through each value with a for loop, can you do the same with constructors? I believe you have to use new Burger() to reference it, but if I only want the price, how would I do that? With struct, you would just use Burger.price as an example if I had a struct called Burger.

Thank you for your help.
 

Nidoking

Member
You'd create the struct instances by calling the constructor function, then put those into an array, for example. You can loop through an array the same way you'd loop through a grid, but instead of using numbers to reference the various pieces of information, you'd use names. Your store information could be an array of functions that you'd loop through, and one way to organize that is to put the arrays in a map, referenced to the store.

So, when you enter a store, you get the information to make the items, then make the items, then do whatever it is you do with the items in the store.
 
Top