How to make good inventory system in Godot 4.5.1?

Best practices for creating an inventory system

Godot 4.5.1

Hello everyone, I’m new to Godot. My team and I are making an overlay desktop idle game. (here’s sceenshot of our prototype below). We made minimal prototype, and now I want to improve the inventory system.


Inventory system

The inventory system is currently made with a GridContainer node. It has pretty uncomplicated script I made with a guide:

extends Panel

@onready var icon: TextureRect = $Icon
@export var item: ItemData


func _ready() -> void:
	update_ui()


func update_ui() -> void:
	if not item:
		icon.texture = null
		tooltip_text = ""
		return
	
	icon.texture = item.icon
	tooltip_text = item.item_name


func _get_drag_data(_at_position: Vector2) -> Variant:
	if not item:
		return
	
	var preview = duplicate()
	var c = Control.new()
	c.add_child(preview)
	preview.position -= Vector2(25, 25)
	preview.self_modulate = Color.TRANSPARENT
	c.modulate = Color(c.modulate, 0.5)
	
	set_drag_preview(c)
	icon.hide()
	return self


func _can_drop_data(_at_position: Vector2, _data: Variant) -> bool:
	return true

func _drop_data(_at_position: Vector2, data: Variant) -> void:
	var tmp = item
	item = data.item
	data.item = tmp
	icon.show()
	data.icon.show()
	update_ui()
	data.update_ui()


func _notification(what: int) -> void:
	if what == NOTIFICATION_DRAG_END:
		icon.show()


Placing script

I also made a script for placing items in the inventory, and copied it to all the placeable items:

extends Control

@onready var pod_sprites: AnimatedSprite2D = get_node_or_null("../PodSprites")
@onready var timer: Timer = $"../PodTimer/Timer"
@onready var label: Label = $"../PodTimer/Label"
@export var accepted_type: String
const INVENTORY_SLOTS_GROUP := "inventory_slots"

var _inventory_state: bool


func _process(_delta: float) -> void:
	if not timer.is_stopped():
		var progress := 1.0 - (timer.time_left / timer.wait_time)
		if progress >= 0.66:
			pod_sprites.play("grow_position_big")
		elif progress >= 0.33:
			pod_sprites.play("grow_position_medium")
		else:
			pod_sprites.play("grow_position_small")


func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
	if not timer.is_stopped() or pod_sprites.animation != "grow_position_seed":
		return false
	else:
		return data and data.item and data.item.item_type == accepted_type
	
	
func _drop_data(_at_position: Vector2, data: Variant) -> void:
	data.item = null
	data.update_ui()
	label.show()
	timer.start()


func _on_timer_timeout() -> void:
	label.hide()
	timer.stop()
	_inventory_state = true


func _get_inventory_slots_root() -> Node:
	var slots_root := get_tree().get_first_node_in_group(INVENTORY_SLOTS_GROUP)
	if slots_root == null:
		push_warning("Inventory slots root not found. Add GridContainer to group '%s'." % INVENTORY_SLOTS_GROUP)
	return slots_root


func add_item_to_inventory(item: ItemData) -> bool:
	var slots_root := _get_inventory_slots_root()
	if slots_root == null:
		return false

	for slot in slots_root.get_children():
		if slot.item == null:
			slot.item = item
			slot.update_ui()
			return true
	return false


func _on_gui_input(event: InputEvent) -> void:
	if event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_LEFT and _inventory_state == true:
		add_item_to_inventory(preload("res://resources/plant.tres"))
		pod_sprites.play("grow_position_seed")
		_inventory_state = false

After all that, it looked like this. But as I understand, it’s a bad solution to store data in Godot UI nodes (if it’s possible at all). I really don’t know how to store data in Godot. I thought about using JSON or resources, but here’s the problem: I don’t know how to store data in Godot correctly.

Gameplay

As I understand, my code is not good right now, and I’m looking forward to making it better. So:

  1. What are the best practices in making inventory?
  2. How to make a good inventory management system? (If you have any cool articles, please share them with me)
  3. How do you make inventories in your games? What functions and classes do you use? How you make your code?
  4. Is it worth using Godot plugins for working with inventory?

Working on new inventory system – structure

I haven’t got any answers to my post, so I decided to try building an inventory on my own. I hope it will help someone someday. I’m new to godot, be aware of bad code (and bad English)!

Structure

First, I decided to build an architecture of the inventory. This guide helped me in building (I’m mostly coping his structure):

ItemData

This resource contains information about the item itself:

extends Resource
class_name ItemData


@export var name: String
@export_multiline var description: String
@export var texture: Texture2D
@export_enum("potion", "plant", "seed") var item_type: String
@export var is_stackable: bool

As I understood it’s useful to separate slot_data and item_data for better resource management. So, slot_data is another resource that contains information from item_data:

extends Resource
class_name SlotData


const MAX_STACK_SIZE: int = 32

@export var item_data: ItemData
@export_range(1, MAX_STACK_SIZE) var quanitiy: int

The last resource is inventory_data that contains:

extends Resource

@export var slot_array: Array[SlotData]

We’ll make our inventory UI from this array later.

Basic UI

I decided to separate UI in three different scenes slot, inventory, and inventory_tab.

Inventory

I would like to have some tabs in my inventory, so I’m using PanelContainer as a base node and TabContainer to store inventory scenes.

PanelContainer
|_TabContainer
  |_InventoryTab (Scene)
  |_InventoryTab2 (Scene)

InventoryTab

InventoryTab is made of ScrollContainer and GridContainer, it makes inventory easy for developing and scrollable:

PanelContainer
|_ScrollContainer
  |_GridContainer
    |_Slot (Scene)

Slot

Slots are made of MarginContainer with TextureRect and Label.

PanelContainer
|_MarginContainer
  |_TextureRect
|_Label
  • Label is used for a counter if item is_stackable in other way, it must be hidden.
  • MarginContainer is used for storing TextureRect with texture of our item.

Now, when UI and data structure for it is made, let’s store this data somewhere.

I don’t know how I’ll write this, but I decided to write autoloaded script that will parce data to JSON.

Now I’m going to write “frontend” part of inventory, functions for adding and deleting items, and then saving to JSON.

1 Like

I think it might be difficult to find best practices for inventory systems because requirements for inventories vary a lot.
E.g. if your inventory only exists during one session, you can use a simple class/node for storage. If you want to persist the data, you can use a custom Resource or save/load into a file, e.g. JSON (serialization). You also may want to have stackable items or not, save them to specific positions in the bag (slots), use multi-slot layouts for playing inventory tetris, have weight behavior, use item categories, equipment vs. bag slots,… and so on.

That being said, I think you current approach is reasonable.

For storing the data, you’ll probably want to use FileAccessfor saving/loading, JSON.parse() / JSON.stringify()for json conversion, and str_to_var() / var_to_str()for conversion from and to non-string variables. I’d suggest checking out the Godot Documentation on Savegames

1 Like

Thanks for your help, after a week of tries, I decided to use Inventory System for inventory. I tried building it with AI, it succeeded, but I really don’t want to use AI-code that I don’t understand in my game. So, I’m just going to build UI and use plugin for inventory