How to make inventory script more modular?

Godot Version

4.3

Question

I am trying to make my inventory script more modular to support other inventories like a hot bar and chests. My current inventory script has been working perfectly fine until I tried using the same script to make my hot bar.

I thought I could use the same script in a separate scene to create a separate inventory. But since i add the items in the script it adds it to every instance of the script.

I tried changing the inventory to an array to make it more modular but now I dont know how to add the items from the array to my grid based inventory. As well as make each instance of the inventory script its own inventory.

class_name Inventory
extends Control

@export var inventory_size: int
@export var slotSize: int = 48

@onready var grid = get_node("Grid")

@export var inventory: Array[Item_Data] = []

func _ready() -> void:
	inventory.resize(inventory_size)
	for i in inventory:
		var slot = InventorySlot.new()
		slot.init(Item_Data.ItemType.EMPTY, Vector2(slotSize, slotSize))
		grid.add_child(slot)
	add_item("Sword")

func add_item(itemName:String) -> void:
	var item = InventoryItem.new()
	item.init(Game.items[itemName])
	if item.data.stackable:
		for i in inventory.size():
			if grid.get_child(i).get_child_count() > 0:
				if grid.get_child(i).get_child(0).data == item.data:
					#Update the counter
					grid.get_child(i).get_child(0).data.count += 1
					#Update label counter
					grid.get_child(i).get_child(0).get_child(0).text = str(grid.get_child(i).get_child(0).data.count)
					break
			else:
				grid.get_child(i).add_child(item)
				break
	else:
		for i in inventory.size():
			if grid.get_child(i).get_child_count() == 0:
				grid.get_child(i).add_child(item)
				break
``

Here is the script where I hold the item data in the inventory

class_name InventoryItem
extends TextureRect

@export var data: Item_Data

func _ready() -> void:
	if data:
		expand_mode = TextureRect.EXPAND_IGNORE_SIZE
		stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
		texture = data.itemTexture
		tooltip_text = "Name: %s\n %s\n" % [data.itemName, data.description]
		if data.stackable:
			var label = Label.new()
			label.text = str(data.count)
			label.position = Vector2(24, 16)
			add_child(label)
			label.show()
			if data.count <= 1:
				label.hide()

func init(d: Item_Data) -> void:
	data = d

func _get_drag_data(at_position: Vector2) -> Variant:
	set_drag_preview(make_drag_preview(at_position))
	return self

func make_drag_preview(at_position: Vector2) -> Control:
	var t := TextureRect.new()
	t.texture = self.texture
	t.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
	t.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
	t.custom_minimum_size = self.size
	t.modulate.a = 0.5
	t.position = Vector2(-at_position)
	var c := Control.new()
	c.add_child(t)
	return c
	

and here is where I keep the data for the inventory slots

class_name InventorySlot
extends PanelContainer

var slotType: Item_Data.ItemType

func _ready() -> void:
	self.process_mode = Node.PROCESS_MODE_ALWAYS

func init(t: Item_Data.ItemType, cms: Vector2) -> void:
	slotType = t
	custom_minimum_size = cms
	
func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
	if data is InventoryItem:
		if slotType == Item_Data.ItemType.EMPTY:
			#Check if item in slot 
			if get_child_count() == 0:
				return true
			else:
				if slotType == data.get_parent().slotType:
					return true
				return get_child(0).data.type == data.data.type
		else:
			return data.data.type == slotType
	else:
		return false

func _drop_data(at_position: Vector2, data: Variant) -> void:
	if get_child_count() > 0:
		var item := get_child(0)
		if item == data:
			return
		if item.data == data.data:
			if item.data.stackable:
					#Update the counter
					item.data.count += 1
					#Show hidden counter
					item.get_child(0).show()
					#Update label counter
					item.get_child(0).text = str(item.data.count)
					#Dragged item moves to new slot
					data.reparent(self)
					#Clear dragged item
					data.queue_free()
		item.reparent(data.get_parent())
	data.reparent(self)

func _gui_input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		if (event.button_index == 2) and (event.button_mask == 0):
			if get_child_count() > 0:
				if (get_child(0).data.type == Item_Data.ItemType.CONSUMABLE):
					Game.healPlayer(get_child(0).data.healAmount)
					get_child(0).data.count -= 1
					get_child(0).get_child(0).text = str(get_child(0).data.count)
					if get_child(0).data.count <= 0:
						get_child(0).queue_free()

I guess I’m asking if I can adapt this to be more modular or if i need to rebuild a new inventory system. If so is there a better design pattern to for a modular inventory system

1 Like

Good work on your script. I can see you have come pretty far already.

Can you share your scene layout? I’m guessing you have some kind of HUD scene that includes a node that has the inventory script attached. What you can do is select the node that has the script attached and right-click it and move it to a separate scene.

Then go back to your hud scene tree and right click it and select “instantiate child scene” and select your newly create inventory scene. Now it still uses the same script but it will be it’s own instance.

In the inventory script, change

func _ready() -> void:
	inventory.resize(inventory_size)
	for i in inventory:
		var slot = InventorySlot.new()
		slot.init(Item_Data.ItemType.EMPTY, Vector2(slotSize, slotSize))
		grid.add_child(slot)
	add_item("Sword")

to


func _ready() -> void:
	inventory.resize(inventory_size)
	for i in inventory_size:
		var slot = InventorySlot.new()
		slot.init(Item_Data.ItemType.EMPTY, Vector2(slotSize, slotSize))
		grid.add_child(slot)

	for i in inventory:
		var itemdata: Item_Data = inventory[i]
		add_item(itemdata.name)

I don’t know what your Item_Data class looks like, but if it has a name property, you can pass that to the add_item function. I also changed the first for-loop to creates the same amount of slots as inventory_size but if you have a reason to create slots equal to inventory you should do that.

If this approach works for you, the next step is to try separating the data from the UI. Right now your UI controls are manipulating the properties of the slots. It looks like the grid is really the inventory. What you could do is work only with the inventory array. add_item would add an item to the array and you can make a remove_item to remove it from the array. When the array is updated, remove all children from the grid and re-populate it from the beginning using the inventory array. That means re-creating all slots and putting the items back in there.

Personally I used an inventory addon called gloot which saved me a lot of time. Multiple of these addons can be found on Github if you’re interested.

3 Likes

Thank you! This has helped me, I guess the problem I was trying to figure out is the next part that you explained. It’s the separating the item data I’m not sure how to approach.

I have the inventory script that i add_item in but it causes a problem when i use the same script for the hot bar.

This is my inventory scene that holds the main inventory grid as well as the different inventory slots for equipment. The different slots are their own scripts that inherit from inventory slot that i read and send item info to player stats.

I have all this load inside my GUI canvas layer. I guess since its a more ui based inventory system this is how ive organized it.

The hot bar I move back and forth from inside the inventory node to the hotbar menu at the bottom of the screen. I know its a bit weird , its just how i have it set up for the time being.

My item data class is pretty basic with variables like name, type, count, stackable, the item texture, and obj.

Screenshot 2024-09-04 221256

I would like to start using an array to hold the item data of items in the inventory so i can use it for saving and loading. But originally i thought using an array could help fix the issue shown in the first picture.

I need a way to have separate inventories that i can save and add items to independently and not straight through the inventory script. Something that can be used to create chest inventories as well.

Is there a way to go through each slot in the inventory and add the item data to the array. and use that to update and save the inventory. Or maybe a database that i can store itemdata and use those files to create separate inventories