El plan#
Un inventario bien hecho separa tres cosas: qué es un ítem (datos), qué llevas (lógica) y cómo se ve (UI). Si las mezclas, acabas con un monstruo imposible de mantener. Usaremos ScriptableObjects para los ítems y eventos para avisar a la UI.
Paso 1 — El ítem como dato (ScriptableObject)#
using UnityEngine;
[CreateAssetMenu(fileName = "Item", menuName = "Inventario/Item")]
public class ItemData : ScriptableObject
{
public string nombre;
public Sprite icono;
[TextArea] public string descripcion;
public int maxApilado = 99; // 1 = no apilable (armas), 99 = pociones
}Cada ítem ("Poción", "Llave", "Espada") es un asset que creas y editas sin tocar código.
Paso 2 — Una ranura (slot)#
[System.Serializable]
public class Ranura
{
public ItemData item;
public int cantidad;
public Ranura(ItemData item, int cantidad)
{
this.item = item;
this.cantidad = cantidad;
}
}Paso 3 — La lógica del inventario#
using System;
using System.Collections.Generic;
using UnityEngine;
public class Inventario : MonoBehaviour
{
[SerializeField] private int capacidad = 20;
public List<Ranura> ranuras = new List<Ranura>();
public event Action AlCambiar; // la UI se suscribe a esto
public bool Añadir(ItemData item, int cantidad = 1)
{
// 1) Si es apilable y ya lo tengo, suma a la ranura existente
if (item.maxApilado > 1)
{
foreach (var r in ranuras)
{
if (r.item == item && r.cantidad < item.maxApilado)
{
r.cantidad += cantidad;
AlCambiar?.Invoke();
return true;
}
}
}
// 2) Si hay hueco, crea una ranura nueva
if (ranuras.Count < capacidad)
{
ranuras.Add(new Ranura(item, cantidad));
AlCambiar?.Invoke();
return true;
}
return false; // inventario lleno
}
public void Quitar(ItemData item, int cantidad = 1)
{
for (int i = ranuras.Count - 1; i >= 0; i--)
{
if (ranuras[i].item == item)
{
ranuras[i].cantidad -= cantidad;
if (ranuras[i].cantidad <= 0) ranuras.RemoveAt(i);
AlCambiar?.Invoke();
return;
}
}
}
}La lógica del inventario no sabe nada de la UI. Cuando algo cambia, solo grita "he cambiado" (AlCambiar?.Invoke()). La UI escucha y se redibuja. Así puedes cambiar toda la interfaz sin tocar la lógica, o tener varias vistas del mismo inventario. Ver eventos.
Paso 4 — La UI escucha#
using UnityEngine;
public class InventarioUI : MonoBehaviour
{
[SerializeField] private Inventario inventario;
[SerializeField] private Transform contenedorSlots;
[SerializeField] private GameObject slotPrefab;
void OnEnable() => inventario.AlCambiar += Redibujar;
void OnDisable() => inventario.AlCambiar -= Redibujar;
void Redibujar()
{
// Limpia y reconstruye (sencillo; para muchos slots, reutiliza en vez de recrear)
foreach (Transform hijo in contenedorSlots) Destroy(hijo.gameObject);
foreach (var ranura in inventario.ranuras)
{
var slot = Instantiate(slotPrefab, contenedorSlots);
slot.GetComponent<SlotUI>().Mostrar(ranura.item.icono, ranura.cantidad);
}
}
}Guardar el inventario#
Para persistirlo, guarda los nombres/ids de los ítems y sus cantidades (no los ScriptableObjects directamente) y reconstruye al cargar buscando los assets por id. Ver guardado con JSON.
Errores frecuentes#
- Meter la lógica y la UI en la misma clase (imposible de mantener).
- Guardar el ScriptableObject en el archivo de partida (guarda ids y reconstruye).
- Recrear toda la UI cada cambio con muchos slots (para inventarios grandes, reutiliza los slots existentes).
- No controlar el
maxApiladoy apilar armas que deberían ocupar una ranura cada una.
Resumen#
Separa ítem (ScriptableObject), lógica (Inventario con List<Ranura>, apilado y evento AlCambiar) y UI (escucha el evento y se redibuja). Los ítems son assets editables sin código; la UI está desacoplada por eventos; y para guardar, persiste ids + cantidades con JSON. Es una base que aguanta crafting, equipamiento y más.