Making a Grid Inventory System with Godot

Thrivevolt
17 min readJan 14, 2022

--

Photo by the author

Hi there, and welcome to this tutorial. This tutorial will guide you through making a grid inventory system with Godot.

Table of Contents

  1. Import Item Data from JSON file
  2. Inventory to Hold Player’s Item
  3. Building the Inventory UI
  4. Capture Player Input with Action
  5. Pick and Drop Item with Mouse
  6. Item Stacking and Splitting
  7. Keep Track of the Selected Item
  8. Display Item Tooltip

Prerequisite

This tutorial assumes a basic knowledge with the Godot engine. You should have some familiarity with the Godot Editor, Node and Scene.

Important Note: I wrote and tested the code in this tutorial with Godot 3. If you use newer version of Godot, some code may need some tweak in order to work correctly.

Getting Started

Make sure you have Godot installed on your machine. Then, download the starter project here. This contains the font, images and JSON file I’ve included in the assets folder. Unzip these files. Launch Godot and import the project.

Import Item Data from JSON file

There are various ways to store data in Godot. A most common way is to store information in JSON format which we’ll be using in this tutorial. Note that JSON file is not a built-in resource in Godot, so we’ll have to open it from code editor. If you open up res://assets/json/items.json file, I’ve prepared some dummy items to start with: “sword”, “armor”, “apple” and “potion”.

Take a closer look at the data structure. The item is represented as a Dictionary with key-value pair. The key is a String (the unique key of the item) and the value is a Dictionary that store any keys and associated values for the item’s properties.

Now that we’ve a way to store the item data. Next thing is to load and use them. First, let’s create a new global.gd script in the root directory, then add the following:

We define a utility function called read_from_JSON. It takes a file path as its parameter, then use Godot built-in File class to open and read the file if the file path given exists, else it’ll log an error to the console. If no error, the file content is retrieved and parsed and return as a value so that we can store it in a variable.

In the _ready function, we call the function read_from_JSON to load res://assets/json.items.json file and store the return value in a variable called items. Inside the loop, we also store the key as a property of the item. As each item is identified by a unique key, we can refer to this property to tell if two items are of the same type later.

To access the data globally, let’s make it into a singleton by autoload the script. Go to Project -> Project Settings, then under AutoLoad tab, select and add global.gd script to the list. Set the Node Name to Global. Now the singleton can be accessed directly with the name Global.

Before wrap up this section, let’s add a utility function to look up an item by its key. The way we structure our data allows us to do this easily. Add the following at the end of the global.gd script:

This function takes an item’s key as its parameter, then return a unique copy of the item if it exists.

To test if everything is working correctly, first we need to add a main scene to the project. Create a new scene, add a Node2D node and rename it to Main. Save the scene as main.tscn in the root directory. Run the project (F5) and select main.tscn as our main scene.

Now let’s use the function get_item_by_key to get a “sword” item and print it out in the console. Put a line at the end of the _ready function call in global.gd script:

print(get_item_by_key("sword"))

Run the project again (F5) to confirm the following output in the console:

Link to project files

Inventory To Hold Player’s Item

In this section, we’ll create an inventory to hold the player’s item. Create a new inventory.gd script in the root directory, then add the following:

First, we declare four variables:

  • cols is the number of columns we have in the inventory i.e. 9 columns.
  • rows is the number of rows we have in the inventory i.e. 3 rows.
  • slots is the total number of slots we have in the inventory i.e. 9 x 3 = 27.
  • items is an array to store the player’s item.

Initially, the inventory will be empty. In the _ready function, we populate the items array with an empty Dictionary by looping through the number of slots we have.

Notice that each element in the items array represents an item slot at a position in the array’s index number. We can find out which item is at which index number like this:

items[0]

At the top of the script, we declare a custom signal called items_changed with the keyword signal. The signal will be emitted each time we operate on the items array. We send out an array of index number in a variable indexes along with the signal to tell which item slot has changed. We’ll connect this signal to notify the inventory UI later.

To interact with the inventory, we define a few utility functions:

  • set_item function allows us to add an item into the item slot. It takes two parameters: index (the index number of the item slot) and item (an item Dictionary to add into the item slot). The function returns back any item previously stored in the item slot.
  • remove_item function that we can call to remove an item from the item slot. It takes the index number of the item slot as its parameter. The function returns back any item previously stored in the item slot.
  • set_item_quantity function allows us to add or subtract a certain amount to the item’s quantity. It takes two parameters: index (the index number of the item slot) and amount (the amount to add or subtract). We’ll remove the item if its quantity value falls below zero.

Notice that these functions emit the signal items_changed we declared earlier to notify that the item slot has changed.

Lastly, let’s also autoload inventory.gd script and set the Node Name to Inventory. Up to this point, we’ve two singleton objects: Global and Inventory.

Link to project files

Building the Inventory UI

Now we’re ready to build the inventory UI. First, create a new ui folder in the root directory. We’ll put all the files for the inventory UI in this folder to keep the project neat.

Let’s start with an item slot. An item slot has a single responsibility, which is to display the icon and quantity of the item.

Create a new scene, add a ColorRect node and rename it to ItemSlot. In the Inspector, set Color to #333333, then under Rect -> Min Size set the x and y values to 18 and 18 respectively. Also, under Rect -> Size reset the x and y values to 18 and 18 respectively. Save the scene as item_slot.tscn in the ui/item_slot folder.

Add a child node to ItemSlot scene: a TextureRect node and rename it to ItemIcon. In the Inspector, drag and drop apple.png file in the assets/images folder into its Texture property. Under Rect -> Size set the x and y values to 16 and 16 respectively and set Layout to Center.

Add another child node to ItemSlot scene: a Label node and rename it to ItemQuantity. In the Inspector, set Text to 1 and set Align to Right.

To use our custom font, we need to create a new DynamicFont type resource. In the FileSystem panel, right click on the assets/font folder, select New Resource, choose DynamicFont from the list, and save it as custom_font.tres file. In the Inspector, let’s set the following:

  • Under Settings, set Size to 4.
  • Under Font/FontData, select New DynamicFontData, then click on the DynamicFontData to expand it. Now under Font Path, click on the folder icon to load PressStart2P.ttf font in the assets/font folder.

Back to the ItemQuantity node, set Custom Font to load custom_font.ttf in the assets/font folder and set Layout to Bottom Wide. Save the scene again, and your ItemSlot scene should look like this:

res://ui/item_slot/item_slot.tscn

Let’s also add ItemSlot scene to a group. In the editor, click to switch to Node tab next to the Inspector tab. Then, next to Signals, click Groups, type in the text input a new group name “item_slot” and click the Add button. This’ll be handy when we want to get all the item slots in an array later.

Now let’s add a script to the scene:

We get a reference to the child nodes with the onready keyword. The display_item function can be called with an item, then it will update the icon and quantity of the item in the UI elements.

Now let’s create a script that extends from base class GridContainer that we’ll use in hotbar and inventory menu. Create a new slot_container.gd script and save it in the ui/slot_container folder. Add the following in the script:

First, we give the script a class_name to reference it later.

At the top of the script, we declare a variable ItemSlot and use export (PackedScene) keyword to allow us to set the ItemSlot scene we want to instance in the Inspector.

In the function display_item_slots, we can pass in how many columns and rows of the item slots that we want to display in the inventory UI. The function will update the “columns” property of the GridContainer so that it’ll arrange its child nodes accordingly. Then we loop through the number of slots we have, instance a new ItemSlot scene and add it as a child node to the scene.

Finally, we connect the signal items_changed declared earlier in the Inventory singleton to update the item slot.

Now let’s build the hotbar. Create a new scene, add a GridContainer node and rename it to Hotbar. Then under Rect -> Size reset the x and y values to 0 and 0 respectively. Save the scene as hotbar.tscn in the ui/hotbar folder. Then add a script to the scene:

The script extends from the base class SlotContainer above so that we can access and call the function display_item_slots. For the hotbar, we only want to display one row of the item slots, so we pass in the paramaters Inventory.cols for the number of columns and 1 for the number of rows we want to display. Finally, we update the “rect_position” property of the node to align to the center bottom of the viewport after a game frame has passed. Back to the editor, drag and drop res://ui/item_slot/item_slot.tscn scene into the ItemSlot property in the Inspector.

The last piece of the UI we need is an inventory menu. The setup is pretty much similar to the hotbar. Create a new scene, add a GridContainer node and rename it to InventoryMenu. Save the scene as inventory_menu.tscn in the ui/inventory_menu folder. Then add a script to the scene:

The inventory menu will display all the item slots in the inventory. It is positioned in the center of the viewport. By default the inventory menu is not show so we also need to hide it in the _ready function. Also in the editor, drag and drop res://ui/item_slot/item_slot.tscn scene into the ItemSlot property in the Inspector.

We’ve now completed the setup for the inventory UI.

Link to project file

Capture Player Input with Action

Now that we’ve created hotbar and inventory menu. Let’s add them into Main scene. Open res://Main.tscn and add a CanvasLayer node as a child. Rename the node to UI. A CanvasLayer node allows us to render all the UI elements on top of the rest of the game.

Click to select UI node again, then click the Instance button, let’s add both Hotbar and InventoryMenu scene as children of UI node. When you’re finished, your Main scene should look like this:

res://main.tscn

The player can open the inventory menu by pressing either the “Escape” or “E” key. Let’s make use of Godot’s Input Map to do this. Go to Project -> Project Settings and select the Input Map tab. Create new action “ui_menu” and click the Add button. Scroll to the bottom and you’ll see the new action added in the list. To assign input, click the plus icon to the right, select Key, press the “Escape” key on the keyboard and click the OK button. Repeat this step to add the “E” key.

Now let’s add a script to the Main scene:

We can use the _unhandled_input function, which will be called when an input event occurs. Then we’ll look for the action “ui_menu” by calling event.is_action_pressed. We toggle the visible property of hotbar and inventory_menu if that happens.

Run the project (F5) and confirm that you can now open and close the inventory menu.

Link to project file

Pick and Drop Item with Mouse

A common feature of the inventory menu is that we can pick and drop item with mouse to rearrange them. Let’s implement that in this section.

First, we’ll create a DragPreview scene. The drag preview is responsible to show the item being dragged around. Also we’ll need to update its position every frame to follow the mouse.

Create a new scene, add a Control node and rename it to DragPreview. In the Inspector, under Rect -> Size set the x and y values to 18 and 18 respectively. Set Mouse -> Filter to Ignore to ignore mouse event. Save the scene as drag_preview.tscn in the ui/drag_preview folder.

The drag preview needs to display the icon and quantity of the item. Let’s add a TextureRect child node to DragPreview scene and rename it to ItemIcon. In the Inspector, under Rect -> Size set the x and y values to 16 and 16 respectively. Also set Mouse -> Filter to Ignore. Then set the Layout to Center.

Lastly, add a Label child node to DragPreview scene and rename it to ItemQuantity. Set Custom Fonts to load custom_font.tres in the assets/font folder. Set Align to Right and set Layout to Bottom Wide. When you’re finished, save the scene and your DragPreview scene should look like this:

res://ui/drag_preview/drag_preview.tscn

Now let’s add a script to the scene:

Here, we use a variable dragged_item to store the item being dragged. Initially it is empty. We also define a setter function set_dragged_item with the keyword setget. When the value of dragged item is modified, it’ll automatically call this setter function to update the icon and quantity of the UI elements.

In the _process function, we make the node to follow the mouse if the dragged item is not empty.

Now we’ve completed the node setup for drag preview. Let’s instance DragPreview scene in Main scene. Open res://Main.tscn, click to select UI node, add DragPreview scene as a child node. Up to this point, your Main scene should look like this:

res://main.tscn

In the res://main.gd script, let’s get a reference to the DragPreview scene:

Next, let’s connect the signal gui_input from the item slot. In the same script, add the following:

To get all the item slots in an array, we can call SceneTree.get_nodes_in_group by passing in the group name we’re looking for (remember that we add item slot to a group called “item_slot” earlier on). Inside the loop, we connect the signal gui_input from the item slot. This signal will be emitted when the node receives an input event. We also send out a variable index (the index number of the item slot) along with the signal.

Now we can define the function _on_ItemSlot_gui_input:

If the player clicks the left mouse button on the item slot, while the inventory menu is opened, we’ll call the function drag_item:

Lastly, let’s modify the function _unhandled_input in the res://main.gd script:

What’s new here is that on line 3, we return earlier from the function if the player tries to close the inventory menu while there is a dragged item around as we do not want that.

To test if everything is working correctly, let’s add some items in the inventory. Open res://inventory.gd script and add the following at the end of the _ready function call:

Run the project (F5), open the inventory menu (Escape or E) and confirm that you can move items around.

Link to project file

Item Stacking and Splitting

We can move items around in the inventory menu, but there are two issues: the former is that same items do not stack together, while the latter is that there is no way to split items from a stack. Let’s tackle these issues in this section.

Open res://main.gd script and let’s modify the function drag_item:

What’s new here is that on line 12, we check to see if both the inventory’s item and dragged item are of the same type (by comparing the unique “key” property of the item), and that the item is stackable. If both the conditions are true, we can stack them together, otherwise we swap the two items.

Next up, to split items from a stack, in the same script, let’s modify the function _on_ItemSlot_gui_input:

Here we’ve new lines 6–8. If the player clicks the right mouse button on the item slot, while the inventory menu is opened, we’ll call the function split_item:

Let’s walk through how it works. First, we get a reference to the inventory’s item and dragged item. We return earlier from the function if there is no item in the slot or the item is not stackable.

Then, we get the split_amount by halve the quantity of the inventory’s item.

On lines 6–8, if we’ve the dragged item and that it is of the same type with the inventory’s item (they’ve the same “key” property), we add to the dragged item, while subtract from the inventory’s item, the split_amount from the item’s quantity.

On lines 9–13, if there is no dragged item yet, we’ll add one of the same type with the inventory’s item, and modify its quantity to have the same amount as the split_amount value. Finally, we subtract that same amount from the inventory’s item.

Run the project (F5) and make sure item stacking and splitting works correctly now.

Link to project files

Keep Track of the Selected Item

In this section, we’ll implement the selected item in the inventory that the player can equip or interact with.

First, we declare a variable selected to store the index number of the selected item in the inventory. Open res://inventory.gd script, then add the following line:

Next, let’s define two utility functions to set and get the selected item. In the same script, add the following:

Now whenever a selected item has changed, we may want to notify, for example the player to update the item that is held over in hand. For this, we’ll use a custom signal. Let’s declare the signal selected_changed at the top of the script after we declare the signal item_changed:

When we emit the signal items_changed each time we operate on the inventory, we want to check if the selected item has changed too. To do this, let’s define a utility function broadcast_signal that’ll take care of this:

This function takes an array of index number as its parameter. It first emits the signal items_changed, then it loops through the entire array to see if the selected item has changed. If the latter is true, it’ll also emit the signal selected_changed.

Before this, we use the function emit_signal to emit signal in code. Now we need to modify them to use the function broadcast_signal instead. Putting them all together, here is the complete code listing for the res://inventory.gd script:

To visually view which item slot is currently being selected, let’s make the item slot to show a light background color (#7b7b7b) for the selected one. Open res://ui/item_slot/item_slot.tscn and modify the function display_item:

Notice the new lines 8–12 here. We use the function get_parent to get the parent node of the item slot. If it is a hotbar, we’ll show the light background color for the slot that’s currently selected.

The last piece is to allow the player to change the selected item. Open res://main.gd script, and modify the function _on_ItemSlot_gui_input:

On new lines 6–7, when the player clicks the left mouse button on the item slot in the hotbar, we’ll call the function select_item:

Here, we call the function set_selected in the Inventory singleton to update the selected index.

Let’s provide two other ways that the player can change the selected item: with a mouse wheel or by pressing the numbers “1” through “9” on the keyboard. To do this, let’s add the function _input in the res://ui/hotbar/hotbar.gd script:

If the player scrolls mouse wheel up, we raise the selected index by one. This’ll work until the selected index is the last one: in that case, we want to set it back to the first one. To do this, we can use the modulo operation (%). We do the same for when the player scrolls mouse wheel down with an if-else statement to check what is the current selected index set in the inventory.

Notice that we also get the timestamp in millisecond by calling OS.get_ticks_msec and store it in a variable tick. This is because we do not want the input event from firing too frequently, but only process it if 8ms has passed.

Finally, the player can press the numbers “1” through “9” on the keyboard to change the selected item as in lines 13–15. The value of the keys 1 to 9 ranges from 49 to to 57 so we’ll subtract 49 from it to get the correct selected index.

Run the project (F5) and we can now interact with the selected item.

Link to project files

Display Item Tooltip

As a final touch, we’ll create a tooltip that display more item’s information when the mouse hovers over an item slot. To keep things simple, we’ll only display the name of the item in the tooltip.

First, let’s create a new Tooltip scene. Create a new scene, add a ColorRect node and rename it to Tooltip. In the Inspector, set Color to #000000, then set Mouse -> Filter to Ignore to ignore mouse event. Save the scene as tooltip.tscn in the ui/tooltip folder.

Add a MarginContainer node as a child to the Tooltip scene. In the Inspector, under Custom Constants, set the Margin Right, Top, Left and Bottom to 4, 2, 4 and 2 respectively and set Mouse -> Filter to Ignore.

Lastly, add a child node to MarginContainer node: a Label node and rename it to ItemName. In the Inspector, set Custom Fonts to load custom_font.tres in the assets/font folder. Save the scene and your Tooltip scene should look like this:

res://ui/tooltip/tooltip.tscn

Now let’s add a script to the scene:

We update the position of the tooltip to follow the mouse every frame. Then we define a function display_info that when call with an item, it’ll display the item’s information and resize properly after a game frame has passed.

Now let’s instance Tooltip scene in Main scene. By default, the tooltip is not show so click on the eye icon to the right to hide it. When you’re finished, your Main scene should look like this:

res://main.tscn

In the res://main.gd script, let’s get a reference to the tooltip:

To show or hide the tooltip, let’s connect the signal mouse_entered and mouse_exited from the item slot. In the Main script, modify the function _ready:

Now we can define the function show_tooltip and hide_tooltip:

In the function show_tooltip, we only want to show the tooltip if there is an inventory’s item in the slot while there is no dragged item.

Finally, we also want to hide the tooltip when the player toggles the inventory menu, or there is item being dragged around. In the res://main.gd script, modify the following function:

What’s new here are the lines to call the function hide_tooltip to hide the tooltip.

Run the project (F5) to see a working tooltip when the mouse hovers over an item slot.

Link to project files

Finishing Up

Thank you for reading this! I hope that you enjoy this tutorial and learn a thing or two here. If you have any question, feel free to leave a comment. Until next time!

--

--

Thrivevolt

Hey there, I am a web developer. I like to make games during my free time. I drink coffee too! #followme #followback