Coffee-Break Tutorials: Simple Inventory (GML)


Coffee-Break Tutorials: Simple Inventory (GML)

Hello and welcome to another one of our coffee-break tutorials! This time around we're going to be showing you how to make a simple inventory system in the style of the original "Zelda" games. In this tutorial we'll be looking at using arrays and macros to construct our inventory, and by the end of it you'll have a small project with a five slot inventory that permits you to pick things up, stack them, and then put them down again. Now, before getting started, this tutorial requires you to download the following the project and open it in GMS2:

Simple Inventory GML

NOTE: This tutorial is for people that use GML. If you prefer to use DnD™ to make your games, we have a companion article for you here.

Before getting stuck in, we recommend that you take a moment to look through the project as it already contains some objects and some code to make your life easier. You'll see we have an inventory object and an item object. The inventory has some basic code to track the currently selected "slot" and to draw itself to the screen, while the item object simply selects a sprite to display from a selection of possible item sprites. If you run the project you can use the mouse-wheel up or down to change the currently selected inventory slot, but not do much else... So, let's fix that and create an actual working inventory!

NOTE: The graphics used in the demo project are courtesy of Kenny Assets, and we highly recommend checking out their stuff as it's great for rapid prototyping and getting started making projects in GameMaker!


ARRAYS

To manage our inventory we're going to be using arrays. An array is really just a way of making a single variable hold more than one value, and they are incredibly useful when programming as they permit you to easily store large amounts of data using a single "source" variable, and also permit you to easily iterate over that data using a loop.

NOTE: For more information on arrays and how they work, please see the manual.

For our inventory, we'll be using a 2 dimensional array. What this means is that we'll have our base array - that corresponds to each of the item slots in our inventory - as our first dimension and then each "slot" of that array will itself contain another array as the second dimension. This second array which will correspond to the information about the item being stored. Below you can see a schematic image of how this is going to work:

2D Array Inventory Setup

So, for example, say we want to get the amount of items in the inventory slot 3. We would access the array using the following syntax:

amount = inventory_array[2][2]

Array slots are always counted from 0, so slot 3 in the inventory is slot 2 of the array, and it holds another array where its slot 2 is the amount of items being stored. Don't worry if this seems a little confusing as I'm sure it'll become clearer as we put it into practice! In fact, the next thing we need to discuss is an ideal way to make working with arrays a lot easier, and that's to use macros.


MACROS

In GameMaker Studio 2 a macro is a type of constant that is created with a value when the game is first run, and the value it is given never changes and cannot be changed in the game. This means that once you assign a value to a macro, it can be used everywhere in your code and you'll know that it will always be the same, no matter what. Why is this useful to us? Well, it means that we can essentially assign names to the different slots in an array, which in turn means you don't have to remember which number corresponds to which value in the array, as you simply use the predefined macro.

Let's go ahead and create these macros now. For this we'll create a new script asset and call it init_game. By default it'll be created with the code to create a function, but we don't need that to define macros as they'll be pre-compiled when you run the game anyway, so delete all the code in the script before continuing. We're going to define a number of different macros that will all be used to make it easier to control what's happening with our inventory, starting with some macros for each of the different item types that our game can have - in this case a sword, a bow, a key, a potion, and food, and we also define a macro for when no item is present:

// Item Definitions
#macro item_none 0
#macro item_potion 1
#macro item_food 2
#macro item_sword 3
#macro item_bow 4
#macro item_key 5

We are also going to assign macros to each of the array slots for item type, item sprite, and item amount, so add those too:

// Array Constants
#macro item_type 0
#macro item_sprite 1
#macro item_amount 2

If you wanted to you could add further macros for each of the inventory array "slots" too, but I don't think that's really necessary in this case as we know that array slots 0 - 4 will correspond to inventory slots 1 - 5, and we'll generally be using local variables to reference these slots anyway.

Let's quickly look at the example we gave above for getting the amount of items in the inventory slot 3, only this time using the macro:

amount = inventory_array[2][item_amount]

That might be slightly longer code, but it's now much more obvious what value we are retrieving! This means it's time to start adding some code to our objects and actually get this inventory working...


THE ITEM OBJECT

With those preliminaries out of the way, let's open up the object obj_Item and edit the Create Event. We want to use the new macros we have created here to define what kind of item is being created and to store some values so that when the item is "picked up" to put in the inventory, we know what kind of item it is. For that we'll edit the choose function already there and add some extra code to set the correct sprite index:

item = choose(item_food, item_bow, item_key, item_potion, item_sword);
switch(item)
{
case item_food:
    sprite_index = spr_Item_Apple;
    break;
case item_bow:
    sprite_index = spr_Item_Bow;
    break;
case item_key:
    sprite_index = spr_Item_Key;
    break;
case item_potion:
    sprite_index = spr_Item_Potion;
    break;
case item_sword:
    sprite_index = spr_Item_Sword;
    break;
}

Now, each item in the project will have a variable item which will hold a macro for the type of item it is, and we have also set the sprite for the object based on this value. Note that in your own games it's unlikely you'll have a single item for picking up, but what's important here is that each item in the game has the same variable that can be used to identify what the item actually is.


THE INVENTORY OBJECT

It's time to make our inventory functional, so open the object obj_Inventory now and go to the Create Event. There is already some code in there, so we'll be adding the new code under that, as shown in the code below:

randomize();
draw_set_font(fnt_Small);
draw_set_halign(fa_right);
draw_set_valign(fa_bottom);

item_pos = 0;
item_pos_index = 0;
item_array = array_create(5, [ ], [ ], [ ], [ ], [ ]);
for(var i = 0; i < 5; i += 1)
    {
    item_array[i][item_type] = item_none;
    item_array[i][item_sprite] = -1;
    item_array[i][item_amount] = 0;
    }

What we are doing here is using the function array_create() to create a 5 slot array to represent each slot in the inventory, and we are setting each inventory slot to be an empty array using [ ]. We then use a for loop to go through each of the empty arrays and populate them with initial values for the item type, the item sprite, and the item amount.


PICKING ITEMS UP

We need to go back to our item object and we'll have it respond to a click from the mouse, which will add the item to the inventory. For this you need to open obj_Item (if it's not already open) and add a Mouse > Left Pressed Event to it. Before we actually add in the code we require let's quickly outline what we'll be doing in this event:

  • First we'll create some temporary local variables with the data for the item
  • Next we'll change scope to the inventory object
  • In the inventory object we'll loop through the item array to see if there is already an item of the one being picked up in it
  • If there's not, then we'll loop through the array again and see if there is an empty space in the inventory
  • Finally, assuming there is space or the item already exists in the inventory, we'll add the item we're picking up

Now you know the plan, let's add the actions! We'll do this in chunks to try and keep it clear what's happening... so to start with, let's create some local variables and change the scope:

var _pos = 0;
var _type = item;
var _sprite = sprite_index;

with(obj_Inventory)
    {

    }

With those local variables created and the scope set, we want to do the first loop through the item array to see if the item being picked up already exists (as we can then stack the item in the inventory). For that we add the following code inside the with:

while (_pos < 5)
    {
    if (item_array[_pos][item_type] == _type)
        {
        break;
        }
    else
        {
        _pos += 1;
        }
    }

Here we are using the _poslocal variable as a counter while looping through the item array. If it reaches 5, it means that no item of the type being picked up currently exists in the item array, and if it's less than 5, then the item does (_pos will be a value from 0 - 4 and correspond to the array slot that the picked up item belongs to). We'll now check the _pos value again and perform another loop if it's 5 to find the first empty space in the inventory. For that we'll use the following code, still inside the with, and under what you just added:

if (_pos > 4)
    {
    _pos = 0;
    while (_pos < 5)
        {
        if (item_array[_pos][item_type] == item_none)
            {
            break;
            }
        else
            {
            _pos += 1;
            }
        }
    }

Finally, we'll check the value of _pos again, and if it's 5 then we know that there is no space in the inventory and the item being picked up is also not already in one of the occupied slots. If it's less than 5, then we can go ahead and add the item to the inventory and destroy the item instance in the game. This means that the full code would be as follows:

var _pos = 0;
var _type = item;
var _sprite = sprite_index;

with(obj_Inventory)
    {
    while (_pos < 5)
        {
        if (item_array[_pos][item_type] == _type)
            {
            break;
            }
        else
            {
            _pos += 1;
            }
        }
    if (_pos > 4)
        {
        _pos = 0;
        while (_pos < 5)
            {
            if (item_array[_pos][item_type] == item_none)
                {
                break;
                }
            else
                {
                _pos += 1;
                }
            }
        }
if (_pos < 5)
    {
    item_array[_pos][item_type] = _type;
    item_array[_pos][item_sprite] = _sprite;
    item_array[_pos][item_amount] += 1;
    with(other) instance_destroy();
    }
    }

Note that the last line uses with again to set the scope to other to destroy the instance calling the code. This is very important, as - if you simply place the instace destroy function on it's own - you'll destroy the inventory instance rather than the item instance, since the inventory instance is the current scope for the actions. You could add the function outside of the inventory with, but that would then mean that the item will be destroyed whether it actually gets added to the inventory or not.


DRAWING THE INVENTORY

Now that we can pick things up, we need to be able to see what they are. For that we'll edit the obj_Inventory Draw Event, so open that now. We already have some code here to draw the inventory base and the currently selected inventory slot, and we need to add further code underneath that. We'll be using another for loop to loop through the inventory array and draw the items based on the type and the sprite that has been stored in the item array for each slot along with the amount of each item, so the full code will look like this:

draw_self();
var _item_x = item_pos * 22;
var _xx = bbox_left + 13 ;
var _yy = bbox_top + 13;
draw_sprite(spr_Inventory_Selected, item_pos_index, _xx + _item_x, _yy);

item_pos_index += 0.2;
for(var i = 0; i < 5; i += 1)
    {
    if !(item_array[i, item_type] == item_none)
        {
        draw_sprite(item_array[i, item_sprite], 0, _xx, _yy);
        draw_text(_xx + 8, _yy + 9,  + string(item_array[i, item_amount]));
        }
    _xx += 22;
}


DROPPING ITEMS

The last part of the inventory puzzle that we need to resolve is how to drop items and remove them from the inventory array. For that we'll simply use the right mouse button, so in the object obj_Inventory add the Mouse > Global > Global Right Pressed Event to the object. In this event we'll first check that an item exists in the selected inventory slot, then we'll create the item, and then remove 1 from it's amount in the inventory (and if we have none left, set the slot to have no items). To do all that we'll need the following code:

if !(item_array[item_pos][item_type] == item_none)
    {
    var _type = item_array[item_pos][item_type];
    var _sprite = item_array[item_pos][item_sprite];
    item_array[item_pos][item_amount] += -1;
    var _inst = instance_create_layer(mouse_x, mouse_y, "Instances", obj_Item);
    with(_inst)
        {
        item = _type;
        sprite_index = _sprite;
        }
    if (item_array[item_pos][item_amount] < 1)
        {
        item_array[item_pos][item_type] = item_none;
        }
    }

Note that the with function is being used to apply the correct item values to the newly created instance with the ID stored in the local variable _inst.


SUMMARY

You can go ahead and run the project now! If all has gone well, you should be able to:

  • Left click on an item to pick it up and add it to the inventory
  • Pick up multiple items of the same type and see them stack
  • Use the mouse wheel to change the currently selected inventory slot
  • Right click anywhere to remove the currently selected item from the inventory
Inventory Item Drop

Hopefully this has given you not only a good base on which to build more sophisticated inventories for your own games, but also a better understanding of how to use arrays and macros when working with GameMaker Studio!

Happy GameMaking!