Cloaked Games
Member
FLEXIBLE INVENTORY SYSTEM
GM Version: (GMS2) v2.1.0.136 (Will work for GMS1.4 and 2.3+)
Target Platform: ALL
Download: GMS2 Example Project
Difficulty: Intermediate
Last Edited: March 3rd, 2020
This tutorial will explain a way to program a highly flexible inventory system in GML. It will include adding new items into the game, modifying inventory slots to contain items, stacking multiple items in one slot, and basics for viewing and interacting with inventory slots, including using items.
My focus with this inventory code is ease of use. It is not the lowest memory solution, or the most efficient. However, using these methods, you can make an inventory that is flexible enough to make it easy to add new items, new types of items, and things such as crafting, storage chests, and saving, that does not require you to remember specific details of how it was programmed. I have broken the tutorial into several spoiler tabs, to organize the information.
Finally, it is important to note that this tutorial is not designed for beginners. If you are brand new to Gamemaker you might be able to get it to work by copying my code, but if you start trying to modify it, you will probably end up lost with all sorts of errors. This tutorial will not work for you if you are copying and pasting the code. It is designed to give a complete explanation of one, (good) solution to this programming problem, but it assumes you know how to use variables, understand inter-connected systems, and general programming practices.
Tutorial:
This is a long in-depth tutorial. I hope that's because I put lots of detail in, not because it's convoluted and confusing. I tried my best to cover everything you might want to do with a fully complete flexible inventory system. If you have any questions however, please feel free to ask and I will hopefully be able to help you. I am also looking for feedback on the tutorial itself, so if anything feels confusing, unnecessary, under or over explained, please tell me. I will improve the tutorial over time as I notice those kinds of things.
This is the same system (actually a little bit better) that I use in my main project The Last Librarian. It has served me well so far, and hopefully it will work for you too.
GM Version: (GMS2) v2.1.0.136 (Will work for GMS1.4 and 2.3+)
Target Platform: ALL
Download: GMS2 Example Project
Difficulty: Intermediate
Last Edited: March 3rd, 2020
Summary:This tutorial will explain a way to program a highly flexible inventory system in GML. It will include adding new items into the game, modifying inventory slots to contain items, stacking multiple items in one slot, and basics for viewing and interacting with inventory slots, including using items.
My focus with this inventory code is ease of use. It is not the lowest memory solution, or the most efficient. However, using these methods, you can make an inventory that is flexible enough to make it easy to add new items, new types of items, and things such as crafting, storage chests, and saving, that does not require you to remember specific details of how it was programmed. I have broken the tutorial into several spoiler tabs, to organize the information.
Finally, it is important to note that this tutorial is not designed for beginners. If you are brand new to Gamemaker you might be able to get it to work by copying my code, but if you start trying to modify it, you will probably end up lost with all sorts of errors. This tutorial will not work for you if you are copying and pasting the code. It is designed to give a complete explanation of one, (good) solution to this programming problem, but it assumes you know how to use variables, understand inter-connected systems, and general programming practices.
Tutorial:
All of the code example in here would be executed once at the start of the game.
We want to add some items into our game. To do this, we need a few things that will make it possible to access these items.
First, we need item ids that are specific to each item we add. These will be a number that is used to reference an item in the code, when assigning a slot to hold an item, when giving players items, when testing to see if the player has items, even when displaying items in the inventory. We can't just type "apple" or "sword" for the item ids. This needs to be a number.
We could assign each item to a number, for example, if we decide apple is 0, and sword is 1, after that, we could just write "0" to reference the apple and "1" to reference the sword. However, this is annoying because it's so hard to remember a whole bunch of arbitrary numbers, especially with a large number of items. How will you remember if machine gun is 127 or 315?
Fortunately, there is a way to automate this, and provide us with IDs that are easy to remember, using an enumerator. Enumerators (referenced as "enum" in the code), are a list of constants (a variable that never changes), accessible through an enum name. It works like this:
In this example (which we will be using for the rest of the tutorial), each variable in the enumerator is automatically assigned to an incrementing value, in order. We can access those constants through the enum name, which is "item". item.none = 0, item.apple = 1, item.sword = 2, etc. This way, we have item ids that are easy to remember: just "item." then the item you want to reference.
First, we need item ids that are specific to each item we add. These will be a number that is used to reference an item in the code, when assigning a slot to hold an item, when giving players items, when testing to see if the player has items, even when displaying items in the inventory. We can't just type "apple" or "sword" for the item ids. This needs to be a number.
We could assign each item to a number, for example, if we decide apple is 0, and sword is 1, after that, we could just write "0" to reference the apple and "1" to reference the sword. However, this is annoying because it's so hard to remember a whole bunch of arbitrary numbers, especially with a large number of items. How will you remember if machine gun is 127 or 315?
Fortunately, there is a way to automate this, and provide us with IDs that are easy to remember, using an enumerator. Enumerators (referenced as "enum" in the code), are a list of constants (a variable that never changes), accessible through an enum name. It works like this:
GML:
enum item
{
none,
apple,
sword,
health_potion,
staff,
total
}
Next we want each item to have a sprite. Now that we have a list of Item IDs, this is actually quite simple. We will create a strip of sprites, called "spr_item", where each sub-image is the sprite for one of the items. Here is an example for the items we added to the enumerator above:
You can see that the first sub-image is empty. This lines up with the first entry in our enum: item.none. Each sub-image for the items is in the same order as the item IDs in the enumerator. First, nothing, then the apple, then the sword, then the potion, then the staff. We want to keep this consistent for every item we add.
Why? Because sub-images are assigned numbers in the same way as enumerators. The first slot is 0, then 1, then 2, etc, incrementing by ones. This is useful because it means we have IDs for these sprites too! To find the right sub-image, we just use the item ID. item.sword is the correct sub-image for the sword, item.apple is the correct sub-image for the apple.
To draw an item is very simple now:
In the same way, later we can display the items in an inventory slot just by setting the sub-image equal to the value that stores which item is in the slot. If there is nothing (item.none), it will draw the first sub-image, which is nothing.
You can see that the first sub-image is empty. This lines up with the first entry in our enum: item.none. Each sub-image for the items is in the same order as the item IDs in the enumerator. First, nothing, then the apple, then the sword, then the potion, then the staff. We want to keep this consistent for every item we add.
Why? Because sub-images are assigned numbers in the same way as enumerators. The first slot is 0, then 1, then 2, etc, incrementing by ones. This is useful because it means we have IDs for these sprites too! To find the right sub-image, we just use the item ID. item.sword is the correct sub-image for the sword, item.apple is the correct sub-image for the apple.
To draw an item is very simple now:
GML:
draw_sprite(spr_item, item.staff, x, y); //Draws the staff item
draw_sprite(spr_item, item.sword, x, y); //Draws the sword item
So now we can see what a particular item looks like. But we need to be able to do more than that. We want to know things like what the item is named, how much damage it does (in the case of weapons), how much health it recovers (in the case of food).
I will explain how we will use these later, but for now, we're going to create two more enumerators to help tell us this information about the items.
First, item_stat. This one will list all of the different stats we might want to look up about an item, like it's name, damage, type, etc. These are the stats we're using in the example, but there are lots more you might want to add, like cost, range, aoe, etc.
Second, item_type. This one is for each of the different types of items. We'll have 3 types in this example, but once again, you can add whatever different types of items you could possibly want.
Using these values, we will be able to reference everything about an item, what type it is, how much health it heals, what it's name is, etc. We will set up an item index to allow us to do this in the next section.
I will explain how we will use these later, but for now, we're going to create two more enumerators to help tell us this information about the items.
First, item_stat. This one will list all of the different stats we might want to look up about an item, like it's name, damage, type, etc. These are the stats we're using in the example, but there are lots more you might want to add, like cost, range, aoe, etc.
GML:
enum item_stat
{
name,
description,
type,
damage,
health_gain,
total
}
GML:
enum item_type
{
none,
weapon,
food,
total
}
Now, using all this information we set up, we are going to create a single item index of sorts to store it all in an easy to access way, and actually start defining those values for the items.
First, we are going to setup a ds_grid. A grid stores values in a rows and columns, and we can find values inside the grid by specifying which row and column we want to read or change. It is like a 2D array, but it is a little more flexible, and has additional built in functionality.
Using this code we will create a ds_grid large enough to fit all the information we want, and assign it to a global variable "global.item_index" so we can reference it later.
If we drew a picture of our ds_grid called global.item_index, it would look sort of like this:
You'll notice that each column is its own item, and each row below it contains the item stats. Now we can find information about items using coordinates: the damage of a sword would be located: [2, 3], or if you remember our enumerators: [item.sword, item_stat.damage].
But hold on! There's nothing in that slot! (Well, actually there's a 0 there, because we cleared it earlier...) Either way, that's not what we want. The sword does more than 0 damage. Let's try setting that spot to a value that's more useful, like 3. We can use the accessor "#" to access a position in a ds_grid, and then set it equal to a value, like this.
Now that slot tells us the damage a sword does, and we could use that sort of code to fill in every value but... that's 25 lines of code just for this grid, and we might add more items and item stats later! How about we find a better way to do it?
Using a function we create, we can make ourselves an easy way to set all the values for any weapon in the game.
Using this function, we can add the sword, and the staff, to the item index:
But let's back up a second. You might have noticed that in our scr_add_weapon function, we completely ignored item_stat.health_gain. That's because we don't need to set that value for weapons, they don't recover health. However, we do need a new function for food:
And now we can add the food into the item index:
In both functions, we automatically set the item type value within the function, we don't need to get that value as an argument, saving us a little bit of typing also. Alternatively, you could use just one function that accepts arguments for every single item_stat. However, this would often require typing in 0 for slots you don't need for a particular type of item, so in the example I have opted to have multiple functions.
Now that we've finished adding our items, here is the new ds_grid global.item_index:
Now, for the rest of our project, we can access any information about any item using this item index. Here are some examples of what we can do:
As you add more types of items, or more item stats, just make sure to remember to update the item add functions to accommodate the things you added.
Next I will go over how to store these items in an inventory, how to stack multiple items in a slot, and how to have them move around in the inventory.
First, we are going to setup a ds_grid. A grid stores values in a rows and columns, and we can find values inside the grid by specifying which row and column we want to read or change. It is like a 2D array, but it is a little more flexible, and has additional built in functionality.
Using this code we will create a ds_grid large enough to fit all the information we want, and assign it to a global variable "global.item_index" so we can reference it later.
GML:
//Create a ds grid item.total wide, and item_stat.total tall
global.item_index = ds_grid_create(item.total, item_stat.total);
ds_grid_clear(global.item_index, 0); //Set every position to 0
You'll notice that each column is its own item, and each row below it contains the item stats. Now we can find information about items using coordinates: the damage of a sword would be located: [2, 3], or if you remember our enumerators: [item.sword, item_stat.damage].
But hold on! There's nothing in that slot! (Well, actually there's a 0 there, because we cleared it earlier...) Either way, that's not what we want. The sword does more than 0 damage. Let's try setting that spot to a value that's more useful, like 3. We can use the accessor "#" to access a position in a ds_grid, and then set it equal to a value, like this.
GML:
global.item_index[# item.sword, item_stat.damage] = 3;
Using a function we create, we can make ourselves an easy way to set all the values for any weapon in the game.
GML:
/// @description scr_add_weapon(item_ID, name, description, damage);
/// @function scr_add_weapon
/// @param item_ID
/// @param name
/// @param description
/// @param damage
var iid = argument0;
global.item_index[# iid, item_stat.name] = argument1;
global.item_index[# iid, item_stat.description] = argument2;
global.item_index[# iid, item_stat.damage] = argument3;
global.item_index[# iid, item_stat.type] = item_type.weapon;
GML:
scr_add_weapon(item.sword, "Sword", "A sword to banish evil!", 3);
scr_add_weapon(item.staff, "Staff", "A magic staff, pulsing with power.", 5);
GML:
/// @description scr_add_food(item_ID, name, description, health_gain);
/// @function scr_add_food
/// @param item_ID
/// @param name
/// @param description
/// @param health_gain
var iid = argument0;
global.item_index[# iid, item_stat.name] = argument1;
global.item_index[# iid, item_stat.description] = argument2;
global.item_index[# iid, item_stat.health_gain] = argument3;
global.item_index[# iid, item_stat.type] = item_type.food;
GML:
scr_add_food(item.apple, "Apple", "Pretty basic. In every RPG.", 10);
scr_add_food(item.health_potion, "Health Potion", "It's red. Like normal.", 25);
Now that we've finished adding our items, here is the new ds_grid global.item_index:
Now, for the rest of our project, we can access any information about any item using this item index. Here are some examples of what we can do:
GML:
player_health += global.item_index[# item.apple, item_stat.health_gain]; //Gain health from eating an apple.
if (global.item_index[# equipped_item, item_stat.type] == item_type.weapon)
{
enemy_health -= global.item_index[# equipped_item, item_stat.damage]; //Attack an enemy with any weapon
}
Next I will go over how to store these items in an inventory, how to stack multiple items in a slot, and how to have them move around in the inventory.
Now we have items, and an item index that allows us to store and look up information about those items. We want to create an inventory that defines what items the player currently has, and how many of them are in each slot.
We're going to use another ds_grid, this time named global.inventory: (Initialized at the start of the game).
This grid is 10 wide, and 2 tall. We will use the x position in the grid (the first number), to indicate the slot we are looking at, 0-9. This means we have 10 slots total in the inventory. The y position (the second number) is either 0 or 1. We will use the column to store two pieces of information about that slot, first, the item in the slot, and also how many are in that slot.
So, if global.inventory[# 0, 0] returns item.apple, that tells us the first slot has an apple in it. Then, if global.inventory[# 0, 1] returns 4, we know there are 4 apples in the first slot.
This is all we need to code for this section for it to work. However, let's add just a few functions to help us easily access and modify these slots in the inventory. First, this function will allow us to increase or decrease the amount of items in a slot.
This function will increase and decrease the number of items in a slot (0-9). This is done by modifying the value of global.inventory[# slot, 1], which is the amount of items in that slot. If the argument override is set to true, the number of items in a slot is set to the input amount, instead of incremented. An important note is that at the end of the function, we completely clear the slot if the number of items is less than or equal to 0, this ensures we don't end up with negative items in a slot, and clears it to store a different item later.
This second function will be used to add a certain number of items into the inventory automatically, finding an available slot to put the items in.
This function loops through the inventory looking for a valid slot to put some items. It returns true of false to indicate whether it was successful or not. We will use these functions later to set and modify slots to move items around in the inventory. There is no function for setting a slot to hold an item, because that would just be: global.inventory[# slot, 0] = item.apple;
We're going to use another ds_grid, this time named global.inventory: (Initialized at the start of the game).
GML:
global.inventory = ds_grid_create(10, 2);
ds_grid_clear(global.inventory, 0);
So, if global.inventory[# 0, 0] returns item.apple, that tells us the first slot has an apple in it. Then, if global.inventory[# 0, 1] returns 4, we know there are 4 apples in the first slot.
This is all we need to code for this section for it to work. However, let's add just a few functions to help us easily access and modify these slots in the inventory. First, this function will allow us to increase or decrease the amount of items in a slot.
GML:
/// @description Modifies a slot in the inventory. Can add and remove items, and set the item.
/// @function scr_slot_modify_amount(slot, amount, override);
/// @param slot
/// @param amount
/// @param override
//Assign local variables
var slot = argument0;
var amount = argument1;
var override = argument2;
if (override == false) //If it is not overriding current values
{
global.inventory[# slot, 1] += amount; //Increase amount by input amount
}
else //If it is overriding current values
{
global.inventory[# slot, 1] = amount; //Set amount to input amount
}
//Clear slot if the amount is less than or equal to 0
if (global.inventory[# slot, 1] <= 0)
{
global.inventory[# slot, 0] = item.none;
global.inventory[# slot, 1] = 0;
}
This second function will be used to add a certain number of items into the inventory automatically, finding an available slot to put the items in.
GML:
/// @description Adds an item and a quantity into the inventory in a valid slot.
/// @function scr_gain_item(item_ID, amount);
/// @param item_ID
/// @param amount
var iid = argument0;
var amount = argument1;
var slot = 0; //A temporary variable to loop through the slots
var inventory_width = ds_grid_width(global.inventory);
while (slot < inventory_width)
{
//If the tested slot in the inventory is either empty, or contains a matching item id
if (global.inventory[# slot, 0] == iid || global.inventory[# slot, 0] == item.none)
{
global.inventory[# slot, 0] = iid;
global.inventory[# slot, 1] += amount;
return true; //Did set the slot (return/exit)
}
slot ++; //Incriment slot to test next position
}
return false; //Did not set slot
Our scr_item_gain function above will look for the first available spot to put an item, either an empty slot, or a matching slot. This works perfectly fine, but it's often better if you instead prioritize slots with a matching type, before filling a new empty slot with items. (If you have an apple in slot 10, and pick up another apple, it should probably go to slot 10 before filling empty slot 1 so you have two slots with apples). The way to do this is pretty simple: just loop through twice, first looking for matching slots, and then looking for empty slots.
GML:
/// @description Adds an item and a quantity into the inventory in a valid slot.
/// @function scr_gain_item(item_ID, amount);
/// @param item_ID
/// @param amount
var iid = argument0;
var amount = argument1;
var slot = 0; //A temporary variable to loop through the slots
var inventory_width = ds_grid_width(global.inventory);
while (slot < inventory_width)
{
//Searching for a matching inventory slot
if (global.inventory[# slot, 0] == iid)
{
global.inventory[# slot, 1] += amount;
return true; //Did set the slot
exit; //Exit function, because it has set the slot
}
slot ++; //Increment slot to test next position
}
slot = 0;
while (slot < inventory_width)
{
//Searching for an empty inventory slot
if (global.inventory[# slot, 0] == item.none)
{
global.inventory[# slot, 0] = iid;
global.inventory[# slot, 1] += amount;
return true; //Did set the slot
exit; //Exit function, because it has set the slot
}
slot ++; //Increment slot to test next position
}
return false; //Did not set slot
Finally we're going to get some tangible results. Now that we can set and modify slots in our inventory, and read information about the items in our inventory, let's get around to actually displaying it so the player can interact with it.
First, we're going to create a series of instances that will be responsible for managing our inventory slots. We'll create 10 total, one for each slot, and these slots will draw the items inside of them, and how many there. We can later use these for detecting when the player has clicked on a slot.
Let's first create an object called obj_slot, and then add 10 of them into the room. These will be positioned in the top left corner. As we create them, we will assign them a slot, so all 10 slots are assigned. Put the code in the create/room start event of a controller object, or the player.
Now, in the draw GUI event of these slot objects, we can display what we want about the items in that slot.
Let's add some items into the inventory, and then see what it looks like!
That is exactly what should happen based on our functions. Next we'll move on to how to interact with the inventory, and move items between slots.
First, we're going to create a series of instances that will be responsible for managing our inventory slots. We'll create 10 total, one for each slot, and these slots will draw the items inside of them, and how many there. We can later use these for detecting when the player has clicked on a slot.
Let's first create an object called obj_slot, and then add 10 of them into the room. These will be positioned in the top left corner. As we create them, we will assign them a slot, so all 10 slots are assigned. Put the code in the create/room start event of a controller object, or the player.
GML:
var slot = 0;
while (slot < ds_grid_width(global.inventory))
{
var inst = instance_create_layer(x+8+(64*slot), y+8, "Instances", obj_slot);
inst.var_slot = slot;
slot ++;
}
GML:
//Get values
var iid = global.inventory[# var_slot, 0];
var amount = global.inventory[# var_slot, 1];
var name = global.item_index[# iid, item_stat.name];
var description = global.item_index[# iid, item_stat.description];
//Draw stuff
if (iid != item.none)
{
draw_sprite(spr_item, iid, x, y); //Draw item sprite
draw_text(x+4, y+4, string(amount)); //Draw item quantity
}
GML:
scr_gain_item(item.apple, 3);
scr_gain_item(item.staff, 1);
scr_gain_item(item.sword, 2);
global.inventory[# 5, 0] = item.health_potion;
scr_slot_modify_amount(5, 2, true);
That is exactly what should happen based on our functions. Next we'll move on to how to interact with the inventory, and move items between slots.
Since this code works entirely based on absolute coordinates, we need to make some modifications in order to make it work with a view. Try having the slot objects store their position as a gox and goy position. Then set their x and y position to the gox and goy position plus the view/camera position. Then draw everything from the gox and goy coordinate instead of the x and y. This should keep everything lined up.
Now we want to make it so that we can pick up items in the mouse and move them between slots. To start, we need another small grid that will store the item that is in the mouse slot. You can see it is only big enough for one item:
We can draw the mouse slot using almost the same code as the obj_slot code, in the draw event of a controller object:
Then we can use this code in the left pressed event of the slot object to move the items with the mouse:
Now we can move items around between slots, and stack two items matching together into the same slot. From this point onward, everything we can do with this inventory involves using the item_index we set up and the enumerators to our advantage. With the system we have now built as a groundwork, everything else we might want to do with this inventory is simple to implement. I will go over a few examples in part 3, however at this point, it would take forever to give complete code examples for every situation.
Code:
global.mouse_slot = ds_grid_create(1, 2);
GML:
/// @description Draw the mouse items.
//Get values
var iid = global.mouse_slot[# 0, 0];
var amount = global.mouse_slot[# 0, 1];
//Draw stuff
if (iid != item.none)
{
draw_sprite(spr_item, iid, mouse_x-32, mouse_y-32); //Draw item sprite
draw_text(mouse_x+4-32, mouse_y+4-32, string(amount)); //Draw item quantity
}
GML:
/// @description Move items with the mouse
var iid = global.inventory[# var_slot, 0];
var amount = global.inventory[# var_slot, 1];
var mouse_iid = global.mouse_slot[# 0, 0];
var mouse_amount = global.mouse_slot[# 0, 1];
if (iid == 0 || mouse_iid == 0 || iid != mouse_iid) //If either slot is empty, or the two slots don't match
{
//Switch the slots
global.inventory[# var_slot, 0] = mouse_iid;
global.inventory[# var_slot, 1] = mouse_amount;
global.mouse_slot[# 0, 0] = iid;
global.mouse_slot[# 0, 1] = amount;
}
else if (iid == mouse_iid) //If both slots are the same
{
//Take all mouse items and put them in inventory
global.inventory[# var_slot, 1] += global.mouse_slot[# 0, 1];
global.mouse_slot[# 0, 0] = item.none;
global.mouse_slot[# 0, 1] = 0;
}
Our current system has an unlimited stack amount. You can put as many items as you want of the same type into the same slot. In most cases we don't want this. With this system, a good way to limit the number of items in each slot is to just add a check for the various inventory moving function that ensures the value of global.inventroy[# var_slot, 1] stays below a certain amount.
However, sometimes we want different numbers for each type of item. (For example, you can use the number of items to represent durability instead for weapon type items). The best way to do this is to add another item_stat that is something like "item_stat.max_stack", and test for that instead. It may look something like this (in the case of the left pressed event for inventory slots):
In addition, we would need to update our item gain function to account for stack limits. (I here am modifying the advanced gain function under Part Two: Storing Items and Quantities). We cannot add all of the items at once, because if the number exceeds the space left in the slot until the stack, adding the items all at once will pass the stack limit. The method we use here instead is to add one item at a time, until the slot is full (or we're out of items), then we move onto another slot.
However, sometimes we want different numbers for each type of item. (For example, you can use the number of items to represent durability instead for weapon type items). The best way to do this is to add another item_stat that is something like "item_stat.max_stack", and test for that instead. It may look something like this (in the case of the left pressed event for inventory slots):
GML:
if (iid == mouse_iid) //If both slots are the same
{
//Take all mouse items and put them in inventory
while (global.inventory[# var_slot, 1] < global.item_index[# iid, item_stat.max_stack])
{
global.inventory[# var_slot, 1] += 1;
global.mouse_slot[# 0, 1] -= 1;
if (global.mouse_slot[# 0, 1] <= 0)
{
global.mouse_slot[# 0, 0] = item.none;
global.mouse_slot[# 0, 1] = 0;
exit;
}
}
}
GML:
/// @description scr_gain_item(item ID, quantity);
/// @function scr_gain_item
/// @param item_ID
/// @param quantity
var iid = argument[0];
var quantity = argument[1];
var slot = 0;
var inventory_width = ds_grid_width(global.inventory);
var stack_limit = global.item_index[# test_item, item_stat.max_stack];
while (slot < inventory_width) //Checking for matching slots
{
//If testing slot has matching item
if (global.inventory[# slot, 0] == iid)
{
global.inventory[# slot, 0] = iid; //Make that slot hold test item
while (global.inventory[# slot, 1] < stack_limit && quantity > 0)
{
global.inventory[# slot, 1] ++;
quantity --;
}
if (quantity == 0)
{
return true;
exit;
}
}
slot += 1;
}
slot = 0;
//Checking for empty slots
while (slot < inventory_width)
{
if (global.inventory[# slot, 0] == item.none) //If testing slot is empty
{
global.inventory[# slot, 0] = iid; //Make that slot hold test item
global.inventory[# slot, 1] += quantity; //Add amount to match
return true;
exit;
}
slot += 1;
}
To add a crafting system, you just need to check if the necessary values in a number of designated crafting slots match a set of values that are stored to serve as the crafting recipe. Personally, I store crafting recipes in their own grid, which has the following values:
craft_itemA, craft_itemB, craft_amountA, craft_amountB, output_item, output_amount
With these values I have two slots that can be filled with certain items to craft a third new item. craft_itemA has to match the item in the first slot, craft_itemB matches the item in the second slot, the amount in the first and second slot must be greater than or equal to the matching craft_amountA and craft_amountB. A lot of code goes into this, but the system of inventory we have here means it's not too challenging.
craft_itemA, craft_itemB, craft_amountA, craft_amountB, output_item, output_amount
With these values I have two slots that can be filled with certain items to craft a third new item. craft_itemA has to match the item in the first slot, craft_itemB matches the item in the second slot, the amount in the first and second slot must be greater than or equal to the matching craft_amountA and craft_amountB. A lot of code goes into this, but the system of inventory we have here means it's not too challenging.
To have chests to store items in, like in Minecraft, you can create another inventory in exactly the same way we created the player inventory, but instead set it up as a local variable for a chest object of some sort. You can create slot instances for the chest upon interacting with it, and trade items among the slots the same way we do with the player inventory.
Make sure however, that if the chest instance is destroyed, the local inventory grid is also destroyed, with ds_grid_destroy. Otherwise it will hang out in memory forever, a memory leak. (Grids are dynamic resources).
Make sure however, that if the chest instance is destroyed, the local inventory grid is also destroyed, with ds_grid_destroy. Otherwise it will hang out in memory forever, a memory leak. (Grids are dynamic resources).
If you have any experience saving and loading, then saving and loading these inventories is simple. GM has built in functions for this.
Use ds_grid_write and ds_grid_read for easy saving and loading to a text file.
Use ds_grid_write and ds_grid_read for easy saving and loading to a text file.
I can't cover everything you need to make items do what you want in your game, but I can give an example so you can hopefully understand how to do it the way you want.
Let's say we want to be able to click on a slot and have that item do something. In our example, the food items would be consumed and increase the player health. To do that, on the click event, we check first to see what type of item it is, and if it is food, increase the player health by the correct amount:
Using this sort of code, you can define what happens no matter what type of item it is. Bows might be programmed to create arrows with a certain damage, swords might be programmed to damage enemies, armor might be programmed to increase defense if it is in a certain slot.
Let's say we want to be able to click on a slot and have that item do something. In our example, the food items would be consumed and increase the player health. To do that, on the click event, we check first to see what type of item it is, and if it is food, increase the player health by the correct amount:
GML:
var iid = global.inventory[# var_slot, 0];
if (global.item_index[# iid, item_stat.type] == item_type.food) //If the item is food
{
player_health += global.item_index[# iid, item_stat.health_gain]; //Gain the right amount of health
global.inventory[# var_slot, 1] -= 1; //Remove one food item
if (global.inventory[# var_slot, 1] <= 0) //Clear the slot if it is empty
{
global.inventory[# var_slot, 0] = 0;
global.inventory[# var_slot, 1] = 0;
}
}
If you want a weapon or tool to have durability, use the amount information in the inventory as durability instead of the number of items in a slot.You may wish to display this as a durability bar instead of a number, so add a check into the display code to test which type of item it is and run the correct display code for the items.
global.inventory[# 0, 1] would return the durability of that slot if it was a weapon instead of the stack count. They're just treated the same way.
global.inventory[# 0, 1] would return the durability of that slot if it was a weapon instead of the stack count. They're just treated the same way.
This is one major disadvantage of the system used here for displaying item sprites. You can only use one sub-image per sprite. You could modify the system instead to have a separate sprite per item, and store the correct item sprite in the item_index grid instead. It's slower and more annoying, but it's the only way to add animated item sprites to this system.
This was suggested by @IngoLingo, thanks!
Implementing inventory sorting is fairly straightforward, but only by item ID and quantity. Past that it gets a little bit complicated. I will try to explain here.
With the system we have right now, we can actually sort the inventory based on two things: the value of the item ID of the items in the inventory (the order of which the IDs appear in the initializing enumerator), and the quantity of items. These are the two columns of the inventory grid, and we can sort them with ds_grid_sort:
With only a little bit more work, we could also sort the items based on item data from the item_index, such as item_stat.name (alphabetically), item_stat.health_gain, item_stat.damage, etc. However, these values are not actually stored in global.inventory, so we can't sort by those values without some additional code.
One way to do it, would be to resize the inventory grid to be one column wider (either at the creation, or temporarily). Then we can iterate through the inventory grid, plugging the item ID into item_index to store the value of whatever item_stat we want to sort with in the third column.
Implementing inventory sorting is fairly straightforward, but only by item ID and quantity. Past that it gets a little bit complicated. I will try to explain here.
With the system we have right now, we can actually sort the inventory based on two things: the value of the item ID of the items in the inventory (the order of which the IDs appear in the initializing enumerator), and the quantity of items. These are the two columns of the inventory grid, and we can sort them with ds_grid_sort:
GML:
ds_grid_sort(global.inventory, 0, true); //Sort based on item ID
ds_grid_sort(global.inventory, 1, true); //Sort based on quantity of items
One way to do it, would be to resize the inventory grid to be one column wider (either at the creation, or temporarily). Then we can iterate through the inventory grid, plugging the item ID into item_index to store the value of whatever item_stat we want to sort with in the third column.
GML:
ds_grid_sort(global.inventory, 2, true); //Sort based on whatever you put in this column
This is a long in-depth tutorial. I hope that's because I put lots of detail in, not because it's convoluted and confusing. I tried my best to cover everything you might want to do with a fully complete flexible inventory system. If you have any questions however, please feel free to ask and I will hopefully be able to help you. I am also looking for feedback on the tutorial itself, so if anything feels confusing, unnecessary, under or over explained, please tell me. I will improve the tutorial over time as I notice those kinds of things.
This is the same system (actually a little bit better) that I use in my main project The Last Librarian. It has served me well so far, and hopefully it will work for you too.
Last edited: