Skip to content

Intro

This will be the first of two parts to create a full-fledged inventory system with gooey. As said before, there are multiple ways of doing this, even with gooey itself, but we'll stick to one to teach about different things with the library.

In this first part, we will create a basic graphical inventory system to display the current items the player has. We will be able to use the "I" key to open/close the inventory.

The current state

Currently in the game, if you take a look at the obj_Player's code, the inventory data structure is already created, and you can actually pick up stuff already, as you might have seen. However, there's no way to display the player inventory graphically.

Let's take a look at how it's built (click the code block to expand). The inventory instance variable is an array of structs, where each struct is an item and is composed of the keys item_name (the name) and qty (the number of items of that object). The array currently grows and shrinks1 as items are added and removed, and the maximum inventory size is controlled by the inventory_max_size instance variable. There are methods to add and remove items from the inventory as needed:

Code
obj_Player / Create
    self.inventory_max_size = 4;
    self.inventory = []; // {item_name, qty}
    self.inventory_add = function(_item_name, _qty=1) {
        var _idx = array_find_index(self.inventory, method({_item_name}, function(_elem, _i) {
            return _elem.item_name == _item_name;
        }));
        if (_idx != -1) {
            self.inventory[_idx].qty += _qty;
            return true;
        }
        else if (array_length(self.inventory) < self.inventory_max_size) {
            array_push(self.inventory, {item_name: _item_name, qty: _qty});
            return true;
        }
        else {
            return false;
        }
    }

    self.inventory_remove = function(_item_name, _qty=999999999) {
        var _idx = array_find_index(self.inventory, method({_item_name}, function(_elem, _i) {
            return _elem.item_name == _item_name;
        }));
        if (_idx != -1) {
            self.inventory[_idx].qty = max(0, self.inventory[_idx].qty - _qty);
            if (self.inventory[_idx].qty == 0) {
                array_delete(self.inventory, _idx, 1);

            }
            return true;
        }
        else {
            return false;
        }
    }

The panel and grid

Let's setup a UIPanel and UIGrid as before. We will have a square player inventory, with 2 rows and 2 columns. We will write some preliminary code similar to what we've done before to calculate the width and the height of the panel. In this case, we do want the inventory window to be moveable, but not resizable, so we'll set that up.

Also, in this case I want to use the darker brown panel sprite (dt_box_9slice_c) for the inventory and the lighter brown sprite (lt_box_9slice_c) for the slots. Remember from the Grids tutorial that grids are essentially invisible. However, they are also a collection of UIGroup widgets functioning as cells. Since a UIGroup can have a sprite as a background, we will use this to set the light sprite for every cell of the grid using a double for loop:

Game / Draw GUI
    if (!ui_exists("Panel_Inventory")) {
        var _num_rows = 2;
        var _num_cols = 2;
        var _slot_size = 90;
        var _margin = 20;
        var _spacing = 15;
        var _margin_top = 60;
        var _width = _num_cols * _slot_size + 2 * _margin + (_num_cols-1) * _spacing;
        var _height = _num_rows * _slot_size + _margin_top + _margin + (_num_rows-1) * _spacing;

        var _panel = new UIPanel("Panel_Inventory", 0, 0, _width, _height, dt_box_9slice_c, UI_RELATIVE_TO.MIDDLE_CENTER);
        _panel.setResizable(false).setImageAlpha(0.9).setTitle("Inventory").setTitleFormat("[fnt_UI][scale,2][fa_top]").setDragBarHeight(_margin_top);
        _panel.setVisible(false);

        var _grid = new UIGrid("Grid_Inventory", _num_rows, _num_cols);
        _grid.setMargins(_margin).setMarginTop(_panel.getDragBarHeight()).setSpacingHorizontal(_spacing).setSpacingVertical(_spacing);
        _panel.add(_grid);

        for (var _row=0; _row<_num_rows; _row++) {
            for (var _col=0; _col<_num_cols; _col++) {
                _grid.getCell(_row, _col).setSprite(lt_box_9slice_c);
            }
        }
    }

Up to now, we have created a basic inventory panel. We set its visibility to false by default, and we can add a simple check on the Step event to show/hide it (the Input verb is already configured):

Game / Step
    if (InputPressed(INPUT_VERB.INVENTORY))         ui_get("Panel_Inventory").setVisible(!ui_get("Panel_Inventory").getVisible());

With this, we have successfully created a 2x2 inventory panel like so:

Inventory - the starting panel
The starting panel for the inventory.

You can play around with the _num_rows and _num_cols to see how the inventory grows/shrinks, but since we've set a max size of 4 for the obj_Player object, we will stick to that size for the rest of the tutorial.

Displaying the currently held items

For each slot of the inventory, we can show a sprite of the currently held item. Also, we can show a little text indicating the quantity of that item in the inventory. For this, we will add UISprite and UIText widgets to each slot of the inventory, and set them initially to undefined and empty strings. Let's add these to our double for loop:

Game / Draw GUI
    for (var _row=0; _row<_num_rows; _row++) {
        for (var _col=0; _col<_num_cols; _col++) {
            _grid.getCell(_row, _col).setSprite(lt_box_9slice_c);
            var _sprite = new UISprite(string($"Sprite_Inventory_{_row}_{_col}"), 0, 0, undefined,,,,UI_RELATIVE_TO.MIDDLE_CENTER);
            _grid.addToCell(_sprite, _row, _col);
            var _qty_txt = new UIText(string($"Text_Quantity_Inventory_{_row}_{_col}"), -5, -5, "", UI_RELATIVE_TO.BOTTOM_RIGHT);
            _qty_txt.setTextFormat("[fnt_UI][fa_right][fa_bottom]", true);
            _grid.addToCell(_qty_txt, _row, _col);
        }
    }

Next, since we want our gooey inventory to be updated in sync with the player's inventory array, let's use .setPreRenderCallback to define a function. This runs every frame and thus allows us to keep the sprites and texts in sync. We will check every index up until the inventory max size: if there's an item in the player's inventory, we will set the sprite and the text to the contents of the struct in that position2; if not, we will set them to undefined, 0x0 dimensions and empty string respectively. It is important to do this every frame, especially when items are removed from the inventory (such as when we hand the quest items to the NPC to complete the quest).

In order to define the row and column, we want to use modulo arithmetic. If we think about this, we can map each index in the inventory array (0 to 3) to a row and column index. If we start filling from top to bottom and from left to right, we can calculate the row as the integer division of the index by the number of columns, and we can calculate each column as the remainder of the division by the number of columns.

Let's take a look:

Game / Draw GUI
    _panel.setPreRenderCallback(function() {        
        for (var _i=0, _n=obj_Player.inventory_max_size; _i<_n; _i++) {
            var _item = _i < array_length(obj_Player.inventory) ? obj_Player.inventory[_i] : undefined;
            var _row = _i div 2;
            var _col = _i % 2;

            if (_item != undefined) {
                var _sprite = sprite_exists(asset_get_index(_item.item_name)) ? asset_get_index(_item.item_name) : asset_get_index(_item.item_name+"_05");
                var _scale = 3;
                ui_get(string($"Sprite_Inventory_{_row}_{_col}")).setSprite(_sprite).setDimensions(,,sprite_get_width(_sprite) * _scale, sprite_get_height(_sprite) * _scale);              
                var _str = _item.qty == 1 ? "" : string($"({_item.qty})");
                ui_get(string($"Text_Quantity_Inventory_{_row}_{_col}")).setText(_str, true);
            }
            else {
                ui_get(string($"Sprite_Inventory_{_row}_{_col}")).setSprite(undefined).setDimensions(,,0,0);
                ui_get(string($"Text_Quantity_Inventory_{_row}_{_col}")).setText("", true);
            }
        }
    });

The final result

Now that we've done this, we already have a fully working basic inventory! We can check out what we have and it gets updated as new items are added or removed. Check out the gifs below:

Our basic inventory
The fully working basic inventory system we created with gooey.

In the next tutorial we will add some more advanced functionality to the inventory, such as the ability to reorder and drop items in the inventory, and adding a tooltip to the items to get some information about them.


  1. We will change this behavior in the advanced inventory tutorial, in order to allow for inventory reorganization via drag and drop. 

  2. On line 150, we need to perform a small "hack", as sprite names for non-crops are equal to the item names, but sprite names for crops have the _05 suffix (when fully grown).