Creando un Sistema de Inventario Básico en Godot 4: Gestión de Objetos y Mochila
Este tutorial te guiará a través de los pasos esenciales para construir un sistema de inventario funcional en Godot 4. Aprenderás a definir objetos, gestionar slots de inventario y crear una interfaz de usuario interactiva para tu mochila de jugador.
Sistema de Inventario Básico en Godot 4: Gestión de Objetos y Mochila 🎒
¡Hola, desarrollador de videojuegos! ¿Estás listo para darle a tus jugadores la capacidad de recolectar, usar y gestionar objetos en tu juego de Godot 4? Un sistema de inventario es fundamental en casi cualquier RPG, juego de aventura o survival. En este tutorial, construiremos un sistema de inventario básico pero robusto desde cero.
Aquí, nos enfocaremos en la lógica detrás de los objetos, los slots de inventario y cómo la interfaz de usuario interactúa con estos componentes. ¡Prepárate para organizar tus píxeles!
🎯 ¿Qué aprenderemos en este tutorial?
- Definir la estructura de nuestros objetos con un recurso personalizado (Resource).
- Crear un sistema de
Inventarioque gestione losslots. - Implementar una interfaz de usuario para la mochila del jugador (UI).
- Añadir y eliminar objetos del inventario.
- Visualizar los objetos en la interfaz de usuario.
📖 Paso 1: Definición de un Objeto (ItemResource) ✨
El primer paso es definir qué es un objeto en nuestro juego. En Godot, la mejor manera de hacer esto es usando un Resource. Esto nos permite crear plantillas de objetos que pueden ser instanciadas y utilizadas en cualquier lugar del juego sin necesidad de tener un nodo en la escena.
Creando ItemResource.gd
Crea un nuevo script llamado ItemResource.gd en una carpeta res/items (o donde prefieras) y pégalo:
# res/items/ItemResource.gd
class_name ItemResource extends Resource
@export_category("Item Data")
@export var id: String = "new_item" # Identificador único del item
@export var item_name: String = "Nuevo Objeto" # Nombre visible
@export var description: String = "Una descripción genial para este objeto." # Descripción del item
@export var texture: Texture2D # Textura del icono
@export var max_stack_size: int = 1 # Cuántos items se pueden apilar en un slot
@export var is_stackable: bool = false # Si el item se puede apilar o no
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
Creando Recursos de Objetos (Ejemplos)
Ahora, puedes crear algunos objetos de ejemplo. Haz clic derecho en la carpeta res/items (o la que uses) en el Filesystem, selecciona New Resource... y busca ItemResource. Crea al menos dos:
-
res/items/Potion.tres:id:potion_healthitem_name:Poción de Saluddescription:Restaura un poco de vida.texture: Asigna una textura (puedes crear una simple con un círculo rojo).max_stack_size:5is_stackable: ✅
-
res/items/Sword.tres:id:rusty_sworditem_name:Espada Oxidadadescription:Una vieja espada, no muy afilada.texture: Asigna una textura (un icono de espada).max_stack_size:1is_stackable: ❌
📖 Paso 2: El Slot del Inventario (InventorySlot.gd) 📦
Cada espacio en nuestro inventario será un InventorySlot. Este recurso contendrá una referencia al ItemResource y la cantidad de ese objeto que hay en el slot.
Creando InventorySlot.gd
Crea un script InventorySlot.gd en 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 # Todos los items se añadieron
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 # Devuelve la cantidad restante que no se pudo añadir
return amount # No se pudo añadir nada si no es stackable o no coincide
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 # Se vació el slot y se devuelve el tipo de item
return removed_item # Se removieron items, pero el slot no se vació
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 "Vacío"
📖 Paso 3: El Sistema de Inventario (Inventory.gd) 👜
Ahora crearemos el corazón de nuestro sistema: el Inventory propiamente dicho. Este script gestionará una colección de InventorySlots y proveerá la lógica para añadir, quitar y buscar objetos.
Creando Inventory.gd
Crea Inventory.gd en res/inventory:
# res/inventory/Inventory.gd
class_name Inventory extends Resource
@export var inventory_size: int = 10 # Número de slots en el inventario
@export var slots: Array[InventorySlot] # Array de slots
signal inventory_changed # Se emite cuando el inventario cambia
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
# Intenta añadir a slots existentes (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
# Si quedan items, intenta añadir a slots vacíos
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 # Devuelve items que no se pudieron añadir
func remove_item(item_id: String, amount: int = 1) -> bool:
var total_removed = 0
var slots_to_clear: Array[InventorySlot] = []
# Iterar desde el final para evitar problemas al eliminar
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
# Si no se pudo remover la cantidad total, revertir o manejar el error
# Por simplicidad, aquí asumimos que si no se pudo remover TODO, es un fallo.
# En un juego real, quizás querrías remover lo que puedas.
return false # No se pudo remover la cantidad completa
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
📖 Paso 4: Creando la Interfaz de Usuario (UI) 🎨
Ahora que tenemos la lógica del inventario, necesitamos una forma de visualizarlo. Crearemos una escena para cada slot individual y luego una escena para la mochila completa que contendrá múltiples slots.
Escena InventorySlotUI.tscn
Crea una nueva escena de tipo Control y llámala InventorySlotUI.tscn. Guarda el script asociado en res/ui/InventorySlotUI.gd.
Estructura del Nodo:
- InventorySlotUI (Control)
- Background (TextureRect) - para la apariencia del slot
- ItemTexture (TextureRect) - para mostrar el icono del item
- ItemAmount (Label) - para mostrar la cantidad del item
Estilo (TextureRect Background):
- En el Inspector, para
Background, buscaTexturey puedes usar una textura cuadrada con un borde.Stretch ModeaScale On Expand. - Ajusta el
Min SizedelInventorySlotUI(ej.(64, 64)) y elBackgroundal mismo tamaño. Asegúrate de queLayoutdelBackgroundesté enFull Rect.
Script InventorySlotUI.gd:
# 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()
Escena InventoryUI.tscn
Ahora, crea la escena principal del inventario. Nueva escena de tipo Control, llámala InventoryUI.tscn y guarda el script asociado en res/ui/InventoryUI.gd.
Estructura del Nodo:
- InventoryUI (Control)
- Panel (PanelContainer) - Un contenedor para el inventario
- MarginContainer (MarginContainer)
- VBoxContainer (VBoxContainer)
- Label (Label) - Título: "Mochila"
- GridContainer (GridContainer) - Contendrá los InventorySlotUI
Configuración:
- Ajusta el
Panelpara que tenga un tamaño y una posición razonables en la pantalla (ej.Rect -> Position = (200, 100),Rect -> Size = (300, 400)). - El
GridContainerserá donde se instancien dinámicamente nuestrosInventorySlotUIs. EstableceColumnsa un número razonable, por ejemplo,5. - Asegúrate de que el
MarginContaineraplique un poco de margen (ej.10pxen todas las direcciones) y elVBoxContainermaneje el espaciado vertical.
Script InventoryUI.gd:
# res/ui/InventoryUI.gd
extends Control
@onready var grid_container: GridContainer = $Panel/MarginContainer/VBoxContainer/GridContainer
@export var inventory_slot_ui_prefab: PackedScene # Asignar InventorySlotUI.tscn aquí
var player_inventory: Inventory = null
func _ready():
hide() # Ocultar al inicio
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() # Actualizar UI inmediatamente
func _on_inventory_changed():
_draw_inventory()
func _draw_inventory():
# Limpiar slots antiguos
for child in grid_container.get_children():
child.queue_free()
if player_inventory:
# Crear y añadir nuevos 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"): # Necesitas configurar esta acción en Project Settings -> Input Map
if is_visible():
hide()
else:
show()
_on_inventory_changed() # Asegurarse de que esté actualizado al mostrar
📖 Paso 5: Integración con el Juego (Main Scene) 🎮
Finalmente, necesitamos integrar nuestro inventario en la escena principal del juego.
Escena Main.tscn (o tu escena de juego principal)
Crea una escena principal o usa una existente. Añade un nodo Node o CharacterBody2D/3D para tu jugador. Añade un nodo CanvasLayer para la UI. Instancia tu InventoryUI.tscn como hijo del CanvasLayer.
Estructura:
- Main (Node2D)
- Player (CharacterBody2D)
- Player.gd
- CanvasLayer
- InventoryUI (Control)
Script Player.gd (o similar):
Modifica el script de tu jugador para que tenga una instancia del inventario y pueda interactuar con él. También, gestionaremos la asignación del inventario a la UI.
# Player.gd
extends CharacterBody2D # O tu nodo de jugador
@export var initial_inventory_size: int = 10
@export var inventory_resource_path: String = "res://inventory/player_inventory.tres" # Ruta para guardar/cargar
var player_inventory: Inventory
var inventory_ui: InventoryUI
func _ready():
_setup_inventory()
# Enlazar la UI del inventario
inventory_ui = get_tree().get_first_node_in_group("inventory_ui") as InventoryUI
if inventory_ui:
inventory_ui.set_inventory(player_inventory)
else:
print("Advertencia: No se encontró InventoryUI con el grupo 'inventory_ui'.")
# Ejemplo de cómo añadir items al inicio
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) # Esto no debería añadirlo si la espada no es stackable
func _setup_inventory():
if ResourceLoader.exists(inventory_resource_path):
player_inventory = load(inventory_resource_path) as Inventory
print("Inventario cargado.")
else:
player_inventory = Inventory.new(initial_inventory_size)
player_inventory.resource_path = inventory_resource_path # Guardar en este path
player_inventory.resource_saver_save(player_inventory, inventory_resource_path) # Guardar por primera vez
print("Nuevo inventario creado y guardado.")
func _input(event: InputEvent):
if event.is_action_pressed("test_add_item"): # Otra acción, por ejemplo "E"
var potion_item = load("res://items/Potion.tres") as ItemResource
var remaining = player_inventory.add_item(potion_item, 1)
if remaining == 0:
print("Poción añadida.")
else:
print("Inventario lleno, no se pudo añadir: ", remaining, " pociones.")
if event.is_action_pressed("test_remove_item"): # Por ejemplo "R"
if player_inventory.remove_item("potion_health", 1):
print("Poción removida.")
else:
print("No hay pociones para remover.")
📈 Diagrama de Flujo del Sistema de Inventario
Aquí tienes una representación visual de cómo interactúan los diferentes componentes de nuestro sistema de inventario:
Estructura Interfaz Lógica Principal Interacción
🏁 Conclusión y Próximos Pasos
¡Felicidades! 🎉 Has construido un sistema de inventario básico pero funcional en Godot 4. Ahora tus jugadores pueden recolectar y organizar objetos en su mochila. Hemos cubierto la definición de objetos, la gestión de slots, la lógica del inventario y la interfaz de usuario.
Este sistema es una base sólida sobre la cual puedes construir. Aquí tienes algunas ideas para expandirlo:
- Arrastrar y Soltar (Drag & Drop): Implementa la funcionalidad para mover items entre slots.
- Uso de Items: Añade lógica para que al hacer clic derecho o doble clic, un item se "use" (ej. una poción restaura vida).
- Equipamiento: Crea slots específicos para equipar armas, armaduras, etc.
- Persistencia: Mejora el guardado y carga del inventario usando
JSONo el sistema de guardado nativo de Godot. - Filtrado/Clasificación: Permite a los jugadores ordenar el inventario por tipo, nombre, etc.
- Inventario del Mundo: Añade lógica para que los objetos puedan ser recogidos del suelo.
¡Sigue experimentando y construyendo! El desarrollo de videojuegos es un viaje de aprendizaje continuo. Si tienes preguntas o mejoras, no dudes en compartirlas. ¡Feliz desarrollo con Godot! 🚀
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!