Creating a Basic Inventory System in Godot 4: Item and Backpack Management
This tutorial will guide you through the essential steps to build a functional inventory system in Godot 4. You'll learn how to define items, manage inventory slots, and create an interactive user interface for your player's backpack.
Hey there, game developer! Are you ready to empower your players to collect, use, and manage items in your Godot 4 game? An inventory system is fundamental to almost any RPG, adventure game, or survival title. In this tutorial, we'll build a basic yet robust inventory system from scratch.
Here, we'll focus on the logic behind items, inventory slots, and how the user interface interacts with these components. Get ready to organize your pixels!
🎯 What will we learn in this tutorial?
- Define the structure of our items using a custom resource (
Resource). - Create an
Inventorysystem that managesslots. - Implement a user interface for the player's backpack (UI).
- Add and remove items from the inventory.
- Visualize items in the user interface.
📖 Step 1: Defining an Item (ItemResource) ✨
The first step is to define what an item is in our game. In Godot, the best way to do this is by using a Resource. This allows us to create item templates that can be instantiated and used anywhere in the game without needing a node in the scene.
Creating ItemResource.gd
Create a new script named ItemResource.gd in a res/items folder (or wherever you prefer) and paste the following code:
# res/items/ItemResource.gd
class_name ItemResource extends Resource
@export_category("Item Data")
@export var id: String = "new_item" # Unique item identifier
@export var item_name: String = "Nuevo Objeto" # Visible name
@export var description: String = "Una descripción genial para este objeto." # Item description
@export var texture: Texture2D # Icon texture
@export var max_stack_size: int = 1 # How many items can stack in one slot
@export var is_stackable: bool = false # Whether the item can be stacked or not
func _init(p_id: String = "new_item", p_name: String = "Nuevo Objeto", p_desc: String = "", p_texture: Texture2D = null, p_max_stack_size: int = 1, p_stackable: bool = false):
id = p_id
item_name = p_name
description = p_desc
texture = p_texture
max_stack_size = p_max_stack_size
is_stackable = p_stackable
func can_stack_with(other_item_resource: ItemResource) -> bool:
return is_stackable and other_item_resource.is_stackable and id == other_item_resource.id
Creating Item Resources (Examples)
Now, you can create some example items. Right-click on the res/items folder (or the one you're using) in the Filesystem, select New Resource..., and search for ItemResource. Create at least two:
-
res/items/Potion.tres:id:potion_healthitem_name:Health Potiondescription:Restores a bit of health.texture: Assign a texture (you can create a simple one with a red circle).max_stack_size:5is_stackable: ✅
-
res/items/Sword.tres:id:rusty_sworditem_name:Rusty Sworddescription:An old sword, not very sharp.texture: Assign a texture (a sword icon).max_stack_size:1is_stackable: ❌
📖 Step 2: The Inventory Slot (InventorySlot.gd) 📦
Each space in our inventory will be an InventorySlot. This resource will hold a reference to the ItemResource and the quantity of that item in the slot.
Creating InventorySlot.gd
Create an InventorySlot.gd script in res/inventory:
# res/inventory/InventorySlot.gd
class_name InventorySlot extends Resource
@export var item_resource: ItemResource = null
@export var current_amount: int = 0
func is_empty() -> bool:
return item_resource == null or current_amount <= 0
func add_item(item: ItemResource, amount: int = 1) -> int:
if item_resource == null:
item_resource = item
current_amount = amount
return 0 # All items were added
if item_resource.can_stack_with(item):
var space_left = item_resource.max_stack_size - current_amount
var to_add = min(amount, space_left)
current_amount += to_add
return amount - to_add # Returns the remaining amount that couldn't be added
return amount # Nothing could be added if not stackable or doesn't match
func remove_item(amount: int = 1) -> ItemResource:
if is_empty():
return null
var removed_item = item_resource
current_amount -= amount
if current_amount <= 0:
clear_slot()
return removed_item # Slot was emptied and item type is returned
return removed_item # Items were removed, but the slot was not emptied
func clear_slot():
item_resource = null
current_amount = 0
func get_item_id() -> String:
return item_resource.id if item_resource else ""
func get_item_name() -> String:
return item_resource.item_name if item_resource else "Empty"
📖 Step 3: The Inventory System (Inventory.gd) 👜
Now we'll create the heart of our system: the Inventory itself. This script will manage a collection of InventorySlots and provide the logic for adding, removing, and searching for items.
Creating Inventory.gd
Create Inventory.gd in res/inventory:
# res/inventory/Inventory.gd
class_name Inventory extends Resource
@export var inventory_size: int = 10 # Number of slots in the inventory
@export var slots: Array[InventorySlot] # Array of slots
signal inventory_changed # Emitted when the inventory changes
func _init(size: int = 10):
inventory_size = size
_initialize_slots()
func _initialize_slots():
slots.clear()
for i in range(inventory_size):
slots.append(InventorySlot.new())
emit_signal("inventory_changed")
func add_item(item: ItemResource, amount: int = 1) -> int:
var remaining_amount = amount
# Try to add to existing slots (stackable)
for slot in slots:
if slot.item_resource != null and slot.item_resource.can_stack_with(item):
remaining_amount = slot.add_item(item, remaining_amount)
if remaining_amount == 0:
emit_signal("inventory_changed")
return 0
# If items remain, try to add to empty slots
for slot in slots:
if slot.is_empty():
remaining_amount = slot.add_item(item, remaining_amount)
if remaining_amount == 0:
emit_signal("inventory_changed")
return 0
emit_signal("inventory_changed")
return remaining_amount # Returns items that couldn't be added
func remove_item(item_id: String, amount: int = 1) -> bool:
var total_removed = 0
var slots_to_clear: Array[InventorySlot] = []
# Iterate from the end to avoid issues when deleting
for i in range(slots.size() - 1, -1, -1):
var slot = slots[i]
if not slot.is_empty() and slot.get_item_id() == item_id:
var can_remove = min(amount - total_removed, slot.current_amount)
if can_remove > 0:
slot.remove_item(can_remove)
total_removed += can_remove
if slot.is_empty():
slots_to_clear.append(slot)
if total_removed == amount:
break
if total_removed == amount:
emit_signal("inventory_changed")
return true
# If the total amount couldn't be removed, revert or handle the error
# For simplicity, here we assume that if ALL couldn't be removed, it's a failure.
# In a real game, you might want to remove what you can.
return false # The full amount could not be removed
func get_item_count(item_id: String) -> int:
var count = 0
for slot in slots:
if not slot.is_empty() and slot.get_item_id() == item_id:
count += slot.current_amount
return count
func has_item(item_id: String, amount: int = 1) -> bool:
return get_item_count(item_id) >= amount
func get_slot(index: int) -> InventorySlot:
if index >= 0 and index < slots.size():
return slots[index]
return null
📖 Step 4: Creating the User Interface (UI) 🎨
Now that we have the inventory logic, we need a way to visualize it. We'll create a scene for each individual slot and then a scene for the entire backpack that will contain multiple slots.
InventorySlotUI.tscn Scene
Create a new scene of type Control and name it InventorySlotUI.tscn. Save the associated script in res/ui/InventorySlotUI.gd.
Node Structure:
- InventorySlotUI (Control)
- Background (TextureRect) - for the slot's appearance
- ItemTexture (TextureRect) - to display the item icon
- ItemAmount (Label) - to display the item quantity
Styling (TextureRect Background):
- In the Inspector, for
Background, look forTextureand you can use a square texture with a border. SetStretch ModetoScale On Expand. - Adjust the
Min SizeofInventorySlotUI(e.g.,(64, 64)) andBackgroundto the same size. Make sure theLayoutofBackgroundis set toFull Rect.
InventorySlotUI.gd Script:
# res/ui/InventorySlotUI.gd
extends Control
@onready var item_texture: TextureRect = $ItemTexture
@onready var item_amount: Label = $ItemAmount
var assigned_slot: InventorySlot = null
func _ready():
update_ui()
func assign_slot(slot: InventorySlot):
assigned_slot = slot
update_ui()
func update_ui():
if assigned_slot and not assigned_slot.is_empty():
item_texture.texture = assigned_slot.item_resource.texture
item_texture.show()
if assigned_slot.item_resource.is_stackable:
item_amount.text = str(assigned_slot.current_amount)
item_amount.show()
else:
item_amount.hide()
else:
item_texture.texture = null
item_texture.hide()
item_amount.hide()
InventoryUI.tscn Scene
Now, create the main inventory scene. New scene of type Control, name it InventoryUI.tscn, and save the associated script in res/ui/InventoryUI.gd.
Node Structure:
- InventoryUI (Control)
- Panel (PanelContainer) - A container for the inventory
- MarginContainer (MarginContainer)
- VBoxContainer (VBoxContainer)
- Label (Label) - Title: "Backpack"
- GridContainer (GridContainer) - Will contain the InventorySlotUI
Configuration:
- Adjust the
Panelto have a reasonable size and position on the screen (e.g.,Rect -> Position = (200, 100),Rect -> Size = (300, 400)). - The
GridContainerwill be where ourInventorySlotUIs are dynamically instantiated. SetColumnsto a reasonable number, for example,5. - Ensure the
MarginContainerapplies some margin (e.g.,10pxin all directions) and theVBoxContainerhandles vertical spacing.
InventoryUI.gd Script:
# res/ui/InventoryUI.gd
extends Control
@onready var grid_container: GridContainer = $Panel/MarginContainer/VBoxContainer/GridContainer
@export var inventory_slot_ui_prefab: PackedScene # Assign InventorySlotUI.tscn here
var player_inventory: Inventory = null
func _ready():
hide() # Hide at startup
func set_inventory(inventory_resource: Inventory):
if player_inventory:
player_inventory.inventory_changed.disconnect(self._on_inventory_changed)
player_inventory = inventory_resource
if player_inventory:
player_inventory.inventory_changed.connect(self._on_inventory_changed)
_on_inventory_changed() # Update UI immediately
func _on_inventory_changed():
_draw_inventory()
func _draw_inventory():
# Clear old slots
for child in grid_container.get_children():
child.queue_free()
if player_inventory:
# Create and add new slots
for slot_data in player_inventory.slots:
var slot_ui = inventory_slot_ui_prefab.instantiate() as InventorySlotUI
grid_container.add_child(slot_ui)
slot_ui.assign_slot(slot_data)
func _input(event: InputEvent):
if event.is_action_pressed("toggle_inventory"): # You need to configure this action in Project Settings -> Input Map
if is_visible():
hide()
else:
show()
_on_inventory_changed() # Ensure it's updated when shown
📖 Step 5: Integrating with the Game (Main Scene) 🎮
Finally, we need to integrate our inventory into the main game scene.
Main.tscn Scene (or your main game scene)
Create a main scene or use an existing one. Add a Node or CharacterBody2D/3D node for your player. Add a CanvasLayer node for the UI. Instance your InventoryUI.tscn as a child of the CanvasLayer.
Structure:
- Main (Node2D)
- Player (CharacterBody2D)
- Player.gd
- CanvasLayer
- InventoryUI (Control)
Player.gd Script (or similar):
Modify your player script to have an inventory instance and be able to interact with it. We'll also handle assigning the inventory to the UI.
# Player.gd
extends CharacterBody2D # Or your player node
@export var initial_inventory_size: int = 10
@export var inventory_resource_path: String = "res://inventory/player_inventory.tres" # Path to save/load
var player_inventory: Inventory
var inventory_ui: InventoryUI
func _ready():
_setup_inventory()
# Link the inventory UI
inventory_ui = get_tree().get_first_node_in_group("inventory_ui") as InventoryUI
if inventory_ui:
inventory_ui.set_inventory(player_inventory)
else:
print("Warning: InventoryUI with group 'inventory_ui' not found.")
# Example of how to add items at the start
var potion_item = load("res://items/Potion.tres") as ItemResource
var sword_item = load("res://items/Sword.tres") as ItemResource
player_inventory.add_item(potion_item, 3)
player_inventory.add_item(sword_item, 1)
player_inventory.add_item(potion_item, 2)
player_inventory.add_item(sword_item, 1) # This should not add it if the sword is not stackable
func _setup_inventory():
if ResourceLoader.exists(inventory_resource_path):
player_inventory = load(inventory_resource_path) as Inventory
print("Inventory loaded.")
else:
player_inventory = Inventory.new(initial_inventory_size)
player_inventory.resource_path = inventory_resource_path # Save to this path
player_inventory.resource_saver_save(player_inventory, inventory_resource_path) # Save for the first time
print("New inventory created and saved.")
func _input(event: InputEvent):
if event.is_action_pressed("test_add_item"): # Another action, for example "E"
var potion_item = load("res://items/Potion.tres") as ItemResource
var remaining = player_inventory.add_item(potion_item, 1)
if remaining == 0:
print("Potion added.")
else:
print("Inventory full, could not add: ", remaining, " potions.")
if event.is_action_pressed("test_remove_item"): # For example "R"
if player_inventory.remove_item("potion_health", 1):
print("Potion removed.")
else:
print("No potions to remove.")
📈 Inventory System Flow Diagram
Here's a visual representation of how the different components of our inventory system interact:
Inventory System Flow Diagram: This diagram illustrates the interaction between ItemResource, InventorySlot, Inventory (Resource), InventorySlotUI, InventoryUI, and Player / Game components.
Structure Interface Core Logic Interaction
🏁 Conclusion and Next Steps
Congratulations! 🎉 You've built a basic yet functional inventory system in Godot 4. Now your players can collect and organize items in their backpack. We've covered item definition, slot management, inventory logic, and the user interface.
This system is a solid foundation upon which you can build. Here are some ideas to expand it:
- Drag & Drop: Implement functionality to move items between slots.
- Item Usage: Add logic so that right-clicking or double-clicking an item
Tutoriales relacionados
- Creando Sistemas de Dialogo Interactivos en Godot 4: Da Voz a tus Personajesintermediate20 min
- Navegación entre Escenas y Gestión de Estado en Godot 4: Creando un Flujo de Juego Dinámicointermediate15 min
- Creando Efectos de Partículas Espectaculares en Godot 4: Explosiones, Magia y Másintermediate20 min
- Creando Personajes Animados 2D en Godot 4: Diseñando y Programando Movimientobeginner15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!