A guide to creating an inventory system [2d arrays]

PlayerOne

Member
GM Version: Game Maker Studio 1.4 and 2
Target Platform
: ALL
Download [GM2]: https://drive.google.com/open?id=1F75KZuS9Lnl_W5m-Ml-mthe5OD_jIAVi
Links: N/A
Version: 1.0.2

Change Log:
V1.0.0: Initial release.
V1.0.1: Removed some conflicting code in the draw event.
V1.0.2: Removed some redundancies in the code.
Summary:
This tutorial will create a 2d array based inventory system. This is a very flexible inventory system that can be used by anyone who needs an inventory system akin to what you see in the original Dead Space, Resident Evil / Biohazard 0 - 3, or the atelier series.

The tutorial is compatible with game maker 2. I know it's compatible with 1.4. however, I do not use that particular version and some functions would need to be changed/adjusted.

A downloadable version is provided and fully commented to those who wish to skip this long winded tutorial.


Special Thanks:
A Very Special Thank you to:
@IndianaBones and @TheouAegis
For without their help this demo project wouldn’t be possible.



NOTICE: If you don’t want the read the entire tutorial - and I certainly wouldn’t blame you – the download link will provide a simple setup with a few variable you can manipulate in the create event of obj_controller. You can easily plop the project into your game in no time flat. Just be sure to read the bits on how to add objects to arrays and usage of items.





SETUP:

To start, create the following objects:


OBJECTS:

Code:
obj_controller <<< This will store our 2d arrays, item information, and more.
obj_Inv <<< This is our interactive inventory box object.
obj_player <<< We need a player object for this little demo.
par_item <<< This will contain the pickup code when the player object collides into an item.
Obj_item_A <<< This will be our interactive item in the room that the player can pick up.
Obj_item_B <<< This will be our interactive item in the room that the player can pick up.

Next, you will need to import the item box and item sprites. Ensure the sprites are 64x64 and centered for this demonstration. If you want a smaller or larger size inventory refer to the obj_controller step event for the necessary adjustments adjustments.

SPRITES:
Code:
spr_inventory_box <<< Can be of any size you choose, but needs to be a box with a dark outline.
spr_inv_noone <<< create a blank sprite of your size. This will sprite represent “nothing” in the inventory box when empty.
spr_inv_itemA <<< A random item image of your choice. This will represent the first item for this project.
spr_inv_itemB  <<< A random item image of your choice. This will represent the second item for this project.
spr_player <<< A player sprite that can be a purple box with the word player written in it.
Once the sprites are imported apply spr_inv to obj_inv, spr_inv_itemA to obj_inv_itemA, and spr_inv_itemB to obj_inv_itemB.

Next import just two random sounds and name them:

SOUNDS:
Code:
snd_use_itemA
snd_use_itemB

ROOM:
Create a 2000x4000 room and the enable camera[0] in the room properties. DO NOT ASSIGN AN OBJECT TO BE FOLLOWED. The reason for this is explained in the step event of the player object.


CREATE EVENT:

Code:
room_speed=60; // <<< Sets room speed to 60 for all rooms, 30 is the default amount.
global.selected=0; // <<< This holds the index from the inventory box
global.item_id=0; // <<< This holds the item id from the 2D array
Code:
global.upgrade=24 //<<< The rest of the Inventory
global.item_box=12 + global.upgrade; //<<< Base inventory
These two variables will set the total amount of inventory boxes for the entire game. Take this example: Say you want 36 boxes in your entire fully upgrades inventory. Global.Item_box is the base amount and global.upgrade will be the remaining total:

36 total = global.item_box=12 + global.upgrade


I made it like this so you may give the player the ability to upgrade their inventory if they so choose. Or you can just enter a single value in global.item_box and not have to deal with the upgrade aspect in this system.

You may wonder why not add more to global.item_box for more item boxes instead of doing this? The reason is that the inv loop must be initialized first with all the inventory boxes you intend to use in the entire game, upgrades included. If you add any additional inventory slots to the global than what was first initialized you will get an error.

I will explain more about the inv loop further down at the bottom of the create event.




Code:
active=false; // <<< Checks to see if the inventory is being viewed or not.
sub_box=(global.upgrade/2); // <<< This variable keeps track of the player inventory upgrades.

Code:
//Item Information, 2D Array...
info[0,0]="Nothing"; /// <<< Item Name.
info[0,1]="Empty"; /// <<< Item Description.
info[0,2]=spr_inv_noone; /// <<< Item image to be placed in inventory slot.

/// And so on...
info[1,0]="ItemA";
info[1,1]="Description A.";
info[1,2]=spr_inv_itemA;

info[1,0]="ItemB";
info[1,1]="Description B.";
info[1,2]=spr_inv_itemB;

This is the first 2d array, called info, that will hold the information about each item in your game. When we get to obj_inv we will use this array. For now, type in your item name and what description you want for it. Ensure the sprite you want represented in your inventory for that item is in the array at position [X,2].


If you want to add more items you should add it like this:

info[3,0]="itemC"; // <<< Item name
info[3,1]="Description of itemC"; // <<< Item Description
info[3,2]=spr_inv_itemC; // <<< Item sprite

To explain arrays think of them as groups. With three [3,X] in this example being the third group in that array. With [X,0], [X,1], [X,2] being lines in that group you can add information to. If you want to add more items just add more groups. Just remember that 0 is the first number and not 1.

In addition, arrays cannot have negative numbers. Remember that detail when creating your own arrays.


Code:
//2D Array, item slots
for(var j=0;j<global.item_box; j++)
{
     inv[j,0]=0
     inv[j,1]=0
}
Here is the main array for this project. This is a for loop that will create the item slots for this project. Mind you this will not draw the item slots but this will create however many item slots in memory that is dictated in the global.item_box. Going back to the example, If you set global.item_box to 36 total (global.item_box=12 + global.upgrade=24) then the loop will create 36 2d array slots starting from [0,0] and [0,1] to [35,0] and [35,1]. While confusing at first it will make sense when you understand that hard coding every 2d array would be extremely time consuming and ultimately pointless when you can dictate the loop from a single variable.



STEP EVENT:

Code:
upgrade = (global.item_box - global.upgrade);
Now here is an important variable: Var upgrade. This subtracts the current global.item_box by global.upgrade and as contradictory as this is despite what you see in the create event it is needed so that the for loop in the create event can create the maximum number of inventory slots for the entire game. When in the step event you subtract that exact amount, this will allow the user to implement actions for the player to upgrade their inventory in-game from the minimum slots at the start.



Code:
// Debug key functions
if keyboard_check_released(ord("E")) || keyboard_check_released(vk_escape){game_end()} /// <<< End game
if keyboard_check_released(ord("R")){game_restart()} /// <<< Restart room, loose items.
if keyboard_check_released(ord("T")){room_restart()} /// <<< Room restart, wont loose items.

//Keyboard keys to upgrade inventory.
if keyboard_check_released(vk_right){if global.upgrade<24{global.upgrade+=sub_box;}} // <<< Add Inventory Boxes
if keyboard_check_released(vk_left){if global.upgrade>0{global.upgrade-=sub_box;}} // <<< Subtract Iventory Boxes

In conjunction with the upgrade variable when using the left arrow key is pressed item boxes will be subtracted and added to the total; Upgrading the player inventory. The opposite is true when pressing the right arrow key.




Code:
if (mouse_check_button_released(mb_left) && !instance_position(mouse_x,mouse_y,obj_inv))
{
instance_create_depth(mouse_x, mouse_y, 0, choose(obj_item_basketball,obj_item_bomb,obj_item_book,obj_item_burger,obj_item_deed,obj_item_diamond,obj_item_gold,obj_item_banana,obj_item_potion));
}
When LMB is pressed a random item will be created in the room. You can remove this function when you add this to you game.




Code:
if keyboard_check_released(vk_space){active = !active}
else {exit;}
When vk_space is released this will set active from true to false and vise versa. When set to false the exit statement will start and will not execute the code at the bottom.


Code:
//Initialize

var i=0;
var h = 0;
var w = 0;
var size = 64 // <<< Type the size of the spr_inventory_box. If it's 32x32 enter 32. In this instance it's 64.
var pos_x = 128 // <<< Position of the first item box to be created on the x axis, stating from x0,y0 on screen.
var pos_y = 128 // <<< Position of the first item box to be created on the y axis, stating from x0,y0 on screen.
var w_box = 4 // <<< How many item boxes you want created on the x axis
var inst_box=0;


if active==true // <<< If active equals true
{

if (instance_number(obj_inv) < upgrade) // <<< This will check the amount of inventory slots to be created on screen.

       {

       for(i=0;i<upgrade; i++) //<<< i=0, when i is less than upgrade, i will increase by 1.

       {

 
                   var inst = instance_create_depth(pos_x + (w*size), pos_y + (h*size),0,obj_inv);
                   /*^^^^^^^^^^^^^^
                   var inst is set to create the number of item boxes. var w and var h will increase.
                   w is width, h is height and this dependent on the var size variable you set at the top.
                   */

                   inst.index = inst_box; // <<< the index will increment by one when a new obj_inv is created.
                   inst_box++; // <<< inst_box increment by 1 every step during the loop.

                   w++; // <<< This will add +1 to var w every step when the loop starts.
                   if (w = w_box) // <<< This is based on how many item boxes you want on the x axis.
                   {
                   w = 0; h++; // if var w = 4, var w resets to 0, var h+=1
                   }

                   if inst_box>=global.item_box{inst_box-=1;} //Subtract from current index}
                   /*^^^^^^^^^^^^^^
                   Due to some glitch with the for loop, I added a subtract event
                   to prevent the loop from going out of index and crashing.
                   Do not modify this line. Ever.
                   */

       }

 
     }

}
else /// <<< If active == false
{


   if (instance_exists(obj_inv)) /// <<< If the item box(s) is on screen.

       {
       with(obj_inv){instance_destroy(obj_inv)}
       //^^^^^^^^^^^^^^^
       //This will destroy the item boxes on screen.
       //You will not lose the items in the inventory.
       }


}

NOTICE: For this to work in 1.4 use instance_create(x,y,obj) instead of instance_create_depth(x,y,depth,obj).



This loop will create the item boxes in the camera view from x128 to y128. Depending on the upgrade value this will create that many inventory boxes (obj_inv).

Notes:
Var pos_x is the position of the first box to be created on the x axis.
Var pos_y is the position of the first box to be created on the y axis.
Var size is dependent on the size of the inventory sprite for obj_inv. If it’s 64x64 then input 64 in the variable.
Var w_box is how many boxes you want on the x axis. Once w=w_box a line of inventory boxes will appear under the first row and will continue to create more rows of obj_inv boxes until the loop ends.


DRAW EVENT:


Code:
show_debug_overlay(0); /// <<< Shows resources being used when the game is running 1/true = On, 0/false = off. Read Docs for more info...
draw_set_font(font_AgencyFB) // <<< Sets font.
draw_set_color(c_white); // <<< Sets font to color to white.
draw_text(10,80,"Active:  " + string(active)) // <<< This string displays the inventory visibility.
draw_text(10,105,"Inv_Box Total:  " + string(global.item_box)) // <<< This string displays the inventory visibility.
draw_text(10,130,"Upgrade:  " + string(global.upgrade)) // <<< This string displays the inventory visibility.



Create Event

Code:
depth=5 // <<< Depth is set to 5
index=0
With the for loop in the step event of obj_controller this index variable will increment by one with each new creation of obj_inv - starting from 0. This variable is the id for this object that will be retrieved when global.selected comes into play.




Code:
// xpre and ypre get the last x and y positions of the item boxes in the gui.
xpre=xprevious
ypre=yprevious
//These var's are blank and will be filled when your cursor hovers over an item box.
current_item=""
item_name="" // <<< Draw item name from 2d array [info]
item_description="" // <<< draw item description from 2d array [info]

STEP EVENT:

Code:
//ITEM NAME && DESCRIPTION
if position_meeting(mouse_x,mouse_y,id) // <<< When mouse hovers over inventory box
{
image_alpha=0.5 /// <<< Indicates the item box is selected and will half its alpha to 0.5.
item_name=Obj_Controller.info[Obj_Controller.inv[index,0],0] //<<< Gets name of item from a separate 2d array...
item_description=Obj_Controller.info[Obj_Controller.inv[index,0],1] //<<< Gets description of item from a separate 2d array...
}
else /// <<< If not hovering over item_box.
{
item_name="";
item_description=""
image_alpha=1;
//^^^^^^^^^^^^^^^^
//This sets the alpha back to 1 and clears item description and name variables to =””.

}

This is where our info array comes into play. When the mouse hovers over an item box its alpha will be halved (0.5) and will call both the name and description from the array and add them to variables item_description and item_name. When the mouse leaves the object will revert to an alpha of 1 and clears the item_name and item_description variables.




Code:
//ITEM ACTION

if instance_position(mouse_x,mouse_y,id) && mouse_check_button_released(mb_left)
{

       global.selected = index; //<=== This gets the index from the inventory box (#ID)
       global.item_id = Obj_Controller.inv[index,0] // <==== Gets the current item type (#ID)

       //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       //When pressing LMB on the item box this will get the index ID of the object
       //and the item ID from the 2d array. The item_id is needed for the item function.
       //See below...

       switch(global.item_id)
       {

       //Item action when LMB is pressed...
       case 1: //Item1
       audio_play_sound(snd_use_itemA,0,0) /// <<< The action when itemA is used
       break;

       case 2: //Item2
       audio_play_sound(snd_use_itemB,0,0) /// <<< The action when itemB is used
       break;

 


if Obj_Controller.inv[index,0]!=0 // <<< If item doesn't = 0.
{
   Obj_Controller.inv[index,1]-=1 // <<< Subtract from current item amount.

       if Obj_Controller.inv[index,1]<=0 // <<< If item is less than or equal to 0.
       {
        Obj_Controller.inv[index,0]=0 // <<< Revert item image to spr_inv_noone.
       }

}


}
This where the sounds you imported will be used. The case/id for the items correlates to the 2d array in obj_controller. If itemA in the array equals [1,X] then it should be case 1 in the switch statement, itemB in the array equals [2,X] then it should equal 2 in the switch statement and so forth. info[0,X] is nothing/empty and should not be listed in the switch statement. The sounds you imported are used here. Now, when LMB is pressed a different sound will play and 1 of that item will be subtracted from the current amount. Once that item goes to 0 the image will revert to spr_inv_noone.


//2D Array, Item order...
info[0,0]="Nothing";
info[1,0]="itemA";
info[2,0]="itemB";




Code:
//DISCARD FUNCTION
if instance_position(mouse_x,mouse_y,id) && mouse_check_button_released(mb_right)
{

       global.selected = index; //<=== This gets the index from the inventory box (#ID)
       global.item_id = Obj_Controller.inv[index,0] // <==== Gets the current item type (#ID)
       //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
       //When pressing RMB on the item box this will get the index ID
       //and the item ID from the 2d array.

   if Obj_Controller.inv[global.selected,0]!=0 // << If there is an item in the array.
   {

       Obj_Controller.inv[global.selected,1]=0 // <<< Discards the item amount.
       Obj_Controller.inv[global.selected,0]=0 // <<< Sets item back to noone/empty.

       global.selected="";
       global.item_id="";
       //^^^^^^^^^^^^^^^^^
       //Clears global item ID's.

   }


}
Similar to the item use code, this will get the item id from the info array and index id from the obj_inv when RMB is pressed. Then it will discard everything from that item box, set obj_inv to the item image spr_inv_noone, and resets the global.selected and global.item_id values.




END STEP:

Code:
var camera_x=camera_get_view_x(view_camera[0]); /// <<< Gets camera view x
var camera_y=camera_get_view_y(view_camera[0]); /// <<< Geta camera view y

x=camera_x+xpre
y=camera_y+ypre

This code will lock the inventory boxes in their set location where you they are positioned in the for loop of obj_controller. In this instance x128 by y128 in the upper left hand corner of the screen. It's important to note that xprevious and yprevious are very important in this equation because if you remove that particular value you end up having the item boxes overlap and not spread out in a grid.





DRAW EVENT:

Code:
draw_self() // <<< This needed in conjuction x=camera_x+xpre and y=camera_y+ypre.
draw_set_font(font_AgencyFB); // <<< Draw set font
draw_set_color(c_white); // <<< Draw font color white
draw_sprite(Obj_Controller.info[Obj_Controller.inv[index,0],2],0,x,y) //<=== draw displayed (current) item.
draw_text(x+-16,y+2,Obj_Controller.inv[index,1]) //<=== Draw displayed current amount of item.

DRAW GUI

Code:
draw_set_font(font_AgencyFB); // <<< Sets font
draw_set_color(c_white) // <<< This sets the color of the text
draw_text(350,100,string(item_name)); // <<< Draw item_name string to be displayed on screen.
draw_text(350,125,string(item_description)); // <<< Draw item_description string to be displayed on screen.


STEP EVENT:
Item pickup code...
Code:
var upgrade = Obj_Controller.upgrade // <<< This gets the upgrade variable from obj_controller
slot=-1 // <<< Slot is -1 by default...


if position_meeting(x,y,Obj_Player) // <<< When player object collides with item...
{


       //This loop tries to find item
       for (var item = 0; item<upgrade; item++)
       {
       if Obj_Controller.inv[item,0]=index
           {
               slot=item;
           }

       }


if slot!=-1 // <<< if the item is found
{

   if Obj_Controller.inv[slot,1]>=90 //if the item is less than max
  {
 
       for (var i_item = 0; i_item<upgrade; i_item++)
      {

         if Obj_Controller.inv[i_item,0]=0
[INDENT]         {
           slot=i_item;
           break;
       }[/INDENT]

     }


 Obj_Controller.inv[slot,0]=index


    }


}
else // <<< If item hasn't been found (slot=-1)...
{


   for (var i_item = 0; i_item<upgrade; i_item++) //<====Loops through 2d array
   {

 
       if Obj_Controller.inv[i_item,0]=0
       {
           slot=i_item;
           break;
       }

}
if slot!=-1
{

   Obj_Controller.inv[slot,0]=index

   }
   else{exit;}
   //^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   //If for any reason slot still equals -1 this will exit the code, preventing a crash.
   //In addition if slot=1 the current 2d array will add the item you collected.

}

if Obj_Controller.inv[slot,1]<90{instance_destroy(); Obj_Controller.inv[slot,1]+=amount;}

if Obj_Controller.inv[slot,1]>=90-1 && Obj_Controller.inv[slot,1]<400{Obj_Controller.inv[slot,1]=90;} // <<< if item greater than level cap, revert to item cap
}


Add the spr_player sprite to the object and copy this code into the step event of the player.


STEP:


Code:
if keyboard_check(ord("W")) {y-=5;}
if keyboard_check(ord("A")) {x-=5;}
if keyboard_check(ord("S")) {y+=5;}
if keyboard_check(ord("D")) {x+=5;}



///Make sure this code is the last in the step event...
var c_w = camera_get_view_width(view_camera[0])
var c_h = camera_get_view_height(view_camera[0])
camera_set_view_pos(view_camera[0], x - (c_w * 0.5), y - (c_h * 0.5))
WASD is the movement for the player and the last section of the step event ensures that the camera view in the room is centered on the player object. Special Thanks to @IndianaBones for this code that fixed a problem with the GMS2 camera.


Finally, we have the items themselves. At this point everything should be set up and now you should create two objects called obj_itemA and obj_itemB. Before anything else be sure you parent both objects to par_item.


CREATE EVENT:

obj_itemA:

Code:
depth=80;// <<< Sets depth.
index=1; // <<< This is item 1 (item 0 is nothing)
amount = choose(5,10,15); // <<< This will add a random number of that item, you can just set a single number instead of a random number.
The index is the most important variable here. Depending on the placement of the object in the info array - in this case itemA - the index variable should be assigned to a value of 1. With itemB the index should equal 2 and so on.
 
Last edited:

Simon Gust

Member
I have something to say about your fail safe code
Code:
//FAIL SAFE MODE
draw_set_color(c_red); // <<< Sets text color to red.
if room_speed<28 || fps_real <28{draw_text(0,0,"WARNING GAME SLOWDOWN!!!")} /// <<< Will display message at x=0,y=0 to indicate slowdown.
else if room_speed<10 || fps_real <10 {game_end()} /// <<< If slowdown drops to less than 15 frames the game will quit so you will not have
I see 2 problems here,
first, the logic doesn't play well together:
if fps_real is say 9, you'd expect the else statement to run
but since fps_real is less than 28 it will run the first clause and never the second one.

The second problem arises if the first problem is solved.
You must know that if your project is big enough it may encounter load times.
These load times can easily struck down fps_real to below 1 -> in the same frame the loading happens, the game ends.

To solve that, you have to check if the game is loading anything.
How you do that I don't know.

And another thing, there is no reason to check room_speed, check fps instead.
There is probably also no reason to check fps_real as it will feature the same behaviour below room_speed values as fps.
 
A

Agreeable

Guest
Unless I am missing something room_speed won't drop below (say 60 for example).

FPS might, but if room speed and is 60 it will stay 60, so no point in checking it.
 

PlayerOne

Member
I have something to say about your fail safe code
Code:
//FAIL SAFE MODE
draw_set_color(c_red); // <<< Sets text color to red.
if room_speed<28 || fps_real <28{draw_text(0,0,"WARNING GAME SLOWDOWN!!!")} /// <<< Will display message at x=0,y=0 to indicate slowdown.
else if room_speed<10 || fps_real <10 {game_end()} /// <<< If slowdown drops to less than 15 frames the game will quit so you will not have
I see 2 problems here,
first, the logic doesn't play well together:
if fps_real is say 9, you'd expect the else statement to run
but since fps_real is less than 28 it will run the first clause and never the second one.

The second problem arises if the first problem is solved.
You must know that if your project is big enough it may encounter load times.
These load times can easily struck down fps_real to below 1 -> in the same frame the loading happens, the game ends.

To solve that, you have to check if the game is loading anything.
How you do that I don't know.

And another thing, there is no reason to check room_speed, check fps instead.
There is probably also no reason to check fps_real as it will feature the same behaviour below room_speed values as fps.
Unless I am missing something room_speed won't drop below (say 60 for example).

FPS might, but if room speed and is 60 it will stay 60, so no point in checking it.

When I wrote the fail safe code I had several experiences where the fps would drop below acceptable levels when testing code and in some cases where the fps would freeze my computer causing me to do a force restart. Room speed became another factor in some instances where it would drop below 60 and into the 15 or 10 range. I wrote that code out of a necessary need to shutdown the program when pressing vk_esc wasn't enough.

Checking to see if anything is loading wasn't the intention. Only if the program runs slower than it should and if it drops below the acceptable amount from either the room speed or fps_real a message would pop in to indicate something was wrong. Then if it dropped even lower it would exit the program if it got really bad.

The code isn't perfect and I didn't factor in loading times now that you mention it. This code was rough to start with so I'll remove it from the tutorial. Thanks.
 
Last edited:
F

FiReWaTeR

Guest
hi, im just letting you know the download link is broken.
i would like to check out your inventory system, it looks nice from what i can tell.
 

PlayerOne

Member
hi, im just letting you know the download link is broken.
i would like to check out your inventory system, it looks nice from what i can tell.
Updated the link.

Due be aware I updated the project code in the thread and not in the project files. I feel like I should take this project down due to the code mistakes I made when I was a newby at the time. However I feel someone might get something out of this.
 
F

FiReWaTeR

Guest
im learning about arrays and looking for examples as i read up on the subject. i just downloaded and tried it and it works but ill go over it tomorrow and see what changes you made in the thread and implement them b4 i dive too deep. ty
 
Top