In this lesson, we’ll get started building the inventory system. It’ll contain rows of slots where we’ll store stacks of items.
A BlueprintEntity
will act as the contents of the slot and as its storage. When clicked on, the item stack will snap to the mouse cursor and follow it.
The user can place one of those entities in the world, hold that entity as a tool in hand, drop items on the ground, or put it into an inventory slot.
We also want to manage splitting stacks up so the user can choose to put a certain amount of the resources into the generator.
The user interface is going to be its own scene to keep its logic separate from the game world. We commonly do this and recommend this approach as it limits coupling in your codebase.
This technique of using an object as a funnel that handles two-way communication is called the Mediator pattern. You can find a dedicated guide in the design patterns chapter.
By encapsulating the UI in a scene and instantiating it outside of the game world, you become unable to access game objects directly. In turn, it forces you to write the UI code in such a way it doesn’t depend on getting too many references to the world’s entities.
Having a dedicated scene for UI also makes it easier to change its design without affecting the rest of the game.
Our GUI scene’s root node will be a middleman for the Simulation
and systems to interact with the inventory. This more or less implements the “mediator” programming pattern.
Create a new scene, GUI.tscn
, with a CenterContainer
as its root named GUI. This node will keep our inventory centered in the viewport, as seen in Minecraft and Factorio.
Use the Layout drop-down menu to make it take up the whole screen.
You can also assign the builder_theme.tres
we provided with the starter project to its Theme property to give it a consistent look.
Back in Simulation.tscn
, add a new CanvasLayer
as a child of Simulation
and leave its Layer -> Layer property to the default value, 1
.
The CanvasLayer
node creates an entirely separate rendering stack, and making its layer higher than the ones before it ensures the GUI always appears above the game world.
Also, it makes it stay on the screen even when the camera move. If you omit the canvas layer, the world camera will leave the GUI behind as the player moves around!
Now, instantiate GUI.tscn
as a child of it. Your Simulation
scene should look like this.
Our end goal regarding the inventory is to display a crafting list on the left and the inventory window on the right, sharing space in the same GUI window.
This chapter will focus on the right part, the panel containing items, and the quick-bar. But we can lay the foundations to align the two panels later.
To do so, back in GUI.tscn
, add an HBoxContainer
. This node takes each of its children and tries to line them up horizontally.
Add a MarginContainer
named InventoryWindow
to the HBoxContainer
. Right-click on the new node and choose Save branch as Scene to separate it into its own scene and open it into the editor.
Your scene tree should look like this.
We’re ready to build the inventory, but what nodes should we use? It can be daunting to figure out Godot’s GUI system, but you can simplify your life by looking at the final design of what you’re going to build.
We want a panel surrounded by margins that contain the inventory grid, also surrounded by margins. We can build that in order.
Open the InventoryWindow
scene.
We have the root margin container already, InventoryWindow
. In its Custom Constants category in the Inspector, tick all four Margin * properties and give them a healthy number around the panel. I went with 25
.
This will force the container’s child node to follow those margins.
For the panel, we add a PanelContainer
node. This container resizes automatically with its content, so we don’t need to change its properties.
The interface may appear tiny in the scene, but thanks to containers’ nature, they’ll automatically resize to fit all item slots once we add them.
You will notice that the theme isn’t applied here. That’s because the GUI node is not in this scene, and we’re using it to propagate the theme. You could ignore that and work with the default theme, but it’s nice to see what you’re doing with how the UI will look in-game for design purposes. Assign the builder_theme.tres
theme to the InventoryWindow
’s Theme property.
We add a new MarginContainer
node under the PanelContainer
for the margins around the inventory’s contents. Once again, we set its four margin Custom Contants to a healthy number. I went with 20
, a little smaller than the pixels on the outside.
We now want to design a grid where we can align items in rows and columns of slots.
Looking at the light blue squares above, your first instinct may be to make a GridContainer
and use it to align the slots.
If all we ever want is to align item slots in a grid layout, that’s a good way to do it. Here, though, there’s a catch and an extra complication. We’re going to lay items in a quick access bar that’ll live at the bottom of the screen.
When the player opens the inventory, we’ll move it to the inventory to make it faster for the player to place items on the bar. Doing so reduces the space to travel with the mouse.
As a result, we’ll design the quick-bar in a dedicated scene. Using a GridContainer
for the inventory and using an instance of the quick-bar as a child of it would make it occupy only one slot, and it wouldn’t look right.
In our case, we can create a more flexible grid using vertical and horizontal box containers.
Add a VBoxContainer
named Inventories as a child of the inner MarginContainer
, and add one HBoxContainer
node named Inventory1 as its child.
To separate the inventory panels, set both the Inventories and Inventory1’s Custom Constants Separation property to enabled and 10
pixels.
Right-click on Inventory1
and choose Save branch as Scene to split it off into its own scene, InventoryBar.tscn
.
The inventory bar represents a set of slots in which the user can be place items. While we could create a bar with a fixed number of slots, we’d better not.
If we make it more flexible and have it create its slots at run-time, we can have it be an inventory bar, a single fuel slot, a bar with two slots for a chemical and a catalyst, so on and so forth. To do that, we’ll make the inventory bar create a set number of slots for us.
For example, the furnace device will have an inventory slot dedicated to fuel, one dedicated to input for it to cook or smelt, and one dedicated to its output.
Add an InventoryBar.gd
script to the bar. We’ll add more code to as we delve into inventory management, but for now, we want it to create some inventory panels at run time.
## Class that represents a bar of inventory slots. ## Forwards function calls and signals from and to children. class_name InventoryBar extends HBoxContainer ## A scene resource for the inventory panel, a scene we'll create next and that represents ## individual item slots. export var InventoryPanelScene: PackedScene ## How many panels to create as children of the bar. export var slot_count := 10 ## An array of references to the panels we create so we can refer to them and ## check their contents later. var panels := [] func _ready() -> void: # Create the bar's panels first thing. _make_panels() ## Creates several inventory panel instances as a child of this horizontal bar. ## Adds them to the `panels` object variable. func _make_panels() -> void: # For each slot for _i in slot_count: # Instance a panel, add it as a child, and add it to the `panels` array. var panel := InventoryPanelScene.instance() add_child(panel) panels.append(panel)
We need a scene for that InventoryPanelScene
resource now, so let’s create it next.
Create a new scene called InventoryPanel
and make the root node a Panel
node. This panel will become the workhorse of managing individual items in our inventory in the next lesson, but we need it to test the window for now.
Panel
is not a container, so it will not change size based on its contents. We need to set its size ourselves.
Set its Min Size property to a 75x75 square. You can choose a different size: try 50x50 if you are working with a smaller resolution, or 100x100 if you want it to take up a large amount of space. Bigger values may work well for touch screens and smaller ones on desktop.
We’ll add some code to change the GUI scale from within the project settings in a future lesson, but for now, pick something that will fit.
Note that containers, like the ones we use in InventoryWindow.tscn
, take control of their children’s width and height. Setting the Min Size property on the InventoryPanel
will force containers to give inventory slots a minimum size of 75
by 75
.
Go back to InventoryBar.tscn
and assign the InventoryPanel.tscn
scene resource to its Inventory Panel Scene property.
Go back to InventoryWindow
and duplicate Inventory1
twice. That gives us 30 inventory slots in the window itself. The quick-bar will be ten more for a total of 40, but we’ll get to that in a future lesson.
If you then go back to GUI.tscn
and test the scene, you should see the fruits of your labor come up in the middle of the screen.
At the moment, we have a window with a bunch of squares on it. It’s not exciting, but it’s an important step. In the next lesson, we’ll make it actually hold some items for us.
Here’s the complete script for InventoryBar.gd
.
class_name InventoryBar extends HBoxContainer export var InventoryPanelScene: PackedScene export var slot_count := 10 var panels := [] func _ready() -> void: _make_panels() func _make_panels() -> void: for _i in slot_count: var panel := InventoryPanelScene.instance() add_child(panel) panels.append(panel)