tutoriales.com

Creando un Sistema de Crafteo y Recetas Dinámico en Unity para Juegos de Supervivencia y RPG 🛠️

Este tutorial te guiará paso a paso en la creación de un sistema de crafteo flexible y dinámico en Unity. Aprenderás a definir recetas, gestionar inventarios y permitir a los jugadores combinar recursos para obtener nuevos objetos, esencial para juegos de supervivencia y RPG.

Intermedio20 min de lectura8 views
Reportar error

¡Hola, aventurero del código! 👋 ¿Alguna vez has soñado con construir tu propio mundo de supervivencia o un RPG donde los jugadores puedan crear sus propias herramientas, armas y pociones? ¡Estás en el lugar correcto! En este tutorial, desglosaremos cómo implementar un sistema de crafteo robusto y dinámico en Unity.

El crafteo es una mecánica fundamental en muchos géneros de videojuegos, permitiendo a los jugadores interactuar profundamente con el mundo, recolectar recursos y transformarlos en objetos útiles. No solo añade profundidad, sino que también fomenta la exploración y la gestión de recursos.

🎯 ¿Qué aprenderás en este tutorial?

  • Diseñar la estructura de datos para ítems y recetas.
  • Implementar la lógica para verificar si una receta es crafteable.
  • Gestionar el inventario de los jugadores para añadir y consumir materiales.
  • Crear una interfaz de usuario (UI) básica para el sistema de crafteo.
  • Extender el sistema para añadir nuevas recetas fácilmente.

🛠️ Requisitos Previos

Antes de sumergirnos en el código, asegúrate de tener lo siguiente:

  • Unity Hub y Unity Editor: Versión 2020.3 o superior.
  • Conocimientos básicos de C#: Variables, clases, métodos, listas.
  • Nociones básicas de la UI de Unity: Canvas, paneles, botones, textos.
💡 Consejo: Si eres nuevo en Unity o C#, te recomiendo revisar tutoriales básicos sobre creación de scripts y UI antes de empezar.

📦 Paso 1: Definición de los Ítems y Recetas

Primero, necesitamos una forma de representar nuestros ítems y las recetas para crearlos. Usaremos ScriptableObjects para esto, ya que nos permiten crear instancias de datos fuera de nuestras escenas, facilitando la gestión y edición.

1.1. La Clase Base Item

Todos los objetos en nuestro juego, ya sean materiales, herramientas o productos finales, heredarán de una clase base Item. Esto nos permite tener propiedades comunes y extender su funcionalidad fácilmente.

Crea una nueva carpeta llamada Scripts y dentro de ella, otra llamada Items. Crea un script C# llamado Item:

using UnityEngine;

[CreateAssetMenu(fileName = "New Item", menuName = "Inventory/Item")]
public class Item : ScriptableObject
{
    public string itemName = "New Item";
    public Sprite icon = null;
    public string description = "";
    public bool isCraftable = false;
    public bool isConsumable = false;
    public int maxStack = 1;
}
  • CreateAssetMenu: Permite crear instancias de Item directamente desde el menú contextual de Unity.
  • itemName: Nombre del ítem.
  • icon: Icono visual para la UI.
  • description: Una breve descripción del ítem.
  • isCraftable: Indica si este ítem puede ser un resultado de crafteo (opcional, pero útil para filtrar).
  • isConsumable: Si el ítem se consume al usarlo (por ejemplo, pociones).
  • maxStack: Cuántos de este ítem pueden apilarse en un slot del inventario.

1.2. La Clase Recipe

Una receta necesita una lista de ingredientes y un resultado. También será un ScriptableObject.

Crea un script C# llamado Recipe en la carpeta Scripts/Items:

using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "New Recipe", menuName = "Crafting/Recipe")]
public class Recipe : ScriptableObject
{
    public Item resultItem;
    public int resultQuantity = 1;
    public List<Ingredient> ingredients = new List<Ingredient>();
}

[System.Serializable]
public class Ingredient
{
    public Item item;
    public int quantity;
}
  • resultItem: El ítem que se obtiene al craftear la receta.
  • resultQuantity: Cuántos ítems del resultado se obtienen.
  • ingredients: Una lista de objetos Ingredient. Cada Ingredient tiene un Item y una quantity requerida.
  • [System.Serializable]: Hace que la clase Ingredient sea visible en el Inspector de Unity, permitiéndonos rellenar sus propiedades sin crear un ScriptableObject separado para cada ingrediente.
📌 Nota: Al usar ScriptableObjects, podemos crear fácilmente muchos ítems y recetas directamente en el editor de Unity sin tocar el código.

🛒 Paso 2: El Inventario del Jugador

Necesitamos una forma de almacenar los ítems que el jugador posee. Un sistema de inventario es crucial para esto. Para simplificar, usaremos una lista de ítems y una clase InventorySlot para manejar la cantidad de cada ítem.

Crea una nueva carpeta Scripts/Inventory. Dentro, crea InventorySlot.cs:

using UnityEngine;

[System.Serializable]
public class InventorySlot
{
    public Item item;
    public int quantity;

    public InventorySlot(Item item, int quantity)
    {
        this.item = item;
        this.quantity = quantity;
    }

    public void AddQuantity(int amount)
    {
        quantity += amount;
    }

    public void RemoveQuantity(int amount)
    {
        quantity -= amount;
    }
}

Y luego, el script principal del InventoryManager:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class InventoryManager : MonoBehaviour
{
    public static InventoryManager Instance { get; private set; }

    public List<InventorySlot> inventorySlots = new List<InventorySlot>();
    public int inventorySize = 20;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public bool AddItem(Item item, int quantity = 1)
    {
        // Check if item exists and can be stacked
        foreach (InventorySlot slot in inventorySlots)
        {
            if (slot.item == item && slot.quantity < item.maxStack)
            {
                int spaceLeft = item.maxStack - slot.quantity;
                int amountToAdd = Mathf.Min(quantity, spaceLeft);
                slot.AddQuantity(amountToAdd);
                quantity -= amountToAdd;
                if (quantity == 0) return true; // All added
            }
        }

        // Add to new slot if space available
        while (quantity > 0 && inventorySlots.Count < inventorySize)
        {
            int amountToStack = Mathf.Min(quantity, item.maxStack);
            inventorySlots.Add(new InventorySlot(item, amountToStack));
            quantity -= amountToStack;
        }

        return quantity == 0; // Returns true if all quantity was added
    }

    public bool RemoveItem(Item item, int quantity = 1)
    {
        int currentQuantity = GetItemQuantity(item);
        if (currentQuantity < quantity) return false; // Not enough items

        // Remove from existing slots, starting from the end to simplify removal
        for (int i = inventorySlots.Count - 1; i >= 0; i--)
        {
            if (inventorySlots[i].item == item)
            {
                if (inventorySlots[i].quantity > quantity)
                {
                    inventorySlots[i].RemoveQuantity(quantity);
                    return true;
                }
                else
                {
                    quantity -= inventorySlots[i].quantity;
                    inventorySlots.RemoveAt(i);
                    if (quantity == 0) return true;
                }
            }
        }
        return false; // Should not reach here if initial check passed
    }

    public int GetItemQuantity(Item item)
    {
        return inventorySlots.Where(slot => slot.item == item).Sum(slot => slot.quantity);
    }
}

Este InventoryManager es un Singleton (Instance) para que sea fácilmente accesible desde cualquier script. Incluye métodos para añadir, remover y obtener la cantidad de un ítem.

⚠️ Advertencia: Para un juego completo, el sistema de inventario sería más complejo, incluyendo manejo de slots visuales, arrastrar y soltar, etc. Este es un punto de partida funcional.

📖 Paso 3: El CraftingManager

Ahora, la pieza central: el CraftingManager. Este script contendrá la lógica principal para procesar las recetas, verificar ingredientes y realizar el crafteo.

Crea una nueva carpeta Scripts/Crafting y dentro, un script C# llamado CraftingManager:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class CraftingManager : MonoBehaviour
{
    public static CraftingManager Instance { get; private set; }

    public List<Recipe> allRecipes = new List<Recipe>();

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public bool CanCraft(Recipe recipe)
    {
        if (recipe == null) return false;

        foreach (Ingredient ingredient in recipe.ingredients)
        {
            if (InventoryManager.Instance.GetItemQuantity(ingredient.item) < ingredient.quantity)
            { 
                return false; // Not enough of this ingredient
            }
        }
        return true; // All ingredients are available
    }

    public bool CraftItem(Recipe recipe)
    {
        if (!CanCraft(recipe)) return false; // Cannot craft if ingredients are missing

        // Consume ingredients
        foreach (Ingredient ingredient in recipe.ingredients)
        {
            InventoryManager.Instance.RemoveItem(ingredient.item, ingredient.quantity);
        }

        // Add result item to inventory
        InventoryManager.Instance.AddItem(recipe.resultItem, recipe.resultQuantity);
        
        Debug.Log($"Crafted {recipe.resultQuantity} x {recipe.resultItem.itemName}");
        return true;
    }

    public List<Recipe> GetCraftableRecipes()
    {
        List<Recipe> craftable = new List<Recipe>();
        foreach(Recipe recipe in allRecipes)
        {
            if (CanCraft(recipe))
            {
                craftable.Add(recipe);
            }
        }
        return craftable;
    }

    public List<Recipe> GetAllRecipes()
    {
        return allRecipes;
    }
}

Este script también es un Singleton. Tiene una lista allRecipes que contendrá todas las recetas disponibles en el juego. Sus métodos principales son:

  • CanCraft(Recipe recipe): Verifica si el jugador tiene todos los ingredientes necesarios en su inventario para una receta específica.
  • CraftItem(Recipe recipe): Si CanCraft es verdadero, consume los ingredientes y añade el ítem resultante al inventario del jugador.
  • GetCraftableRecipes(): Devuelve una lista de recetas que el jugador puede craftear con sus ítems actuales.
  • GetAllRecipes(): Devuelve una lista de todas las recetas disponibles.

🎨 Paso 4: Creando la Interfaz de Usuario (UI) de Crafteo

Necesitamos una forma visual para que el jugador interactúe con el sistema. Crearemos una UI básica que muestre las recetas disponibles y un botón para craftear.

4.1. Configuración del Canvas

  1. En la jerarquía de Unity, haz clic derecho -> UI -> Canvas. Renómbralo a CraftingUI_Canvas.
  2. Selecciona el Canvas y en el Inspector, en Canvas Scaler, cambia UI Scale Mode a Scale With Screen Size y establece Reference Resolution a 1920x1080.

4.2. Panel de Crafteo Principal

  1. Dentro del CraftingUI_Canvas, haz clic derecho -> UI -> Panel. Renómbralo a CraftingPanel.
  2. Ajusta su Rect Transform para que ocupe una porción de la pantalla (por ejemplo, anclado al centro, con un tamaño de 800x600).
  3. Añade un componente Vertical Layout Group al CraftingPanel y configura Child Alignment a Upper Center y Control Child Size para Height.
  4. Dentro del CraftingPanel, añade un Text (TMP) (asegúrate de importar TextMeshPro Essential Resources si se te pide) y cámbiale el texto a "Mesa de Crafteo".

4.3. Listado de Recetas

  1. Dentro del CraftingPanel, crea un GameObject vacío y renómbralo a RecipeListContent.
  2. Añade un Vertical Layout Group y un ContentSizeFitter a RecipeListContent. En ContentSizeFitter, establece Vertical Fit a Preferred Size.
  3. Para poder desplazarnos si hay muchas recetas, añade un Scroll View (UI -> Scroll View) dentro de CraftingPanel y muévelo para que RecipeListContent sea el Content de este Scroll View.

4.4. Elemento de Receta (Prefab)

Crearemos un prefab que representará cada receta en la UI:

  1. Dentro de RecipeListContent, haz clic derecho -> UI -> Panel. Renómbralo a RecipeItem_Prefab.
  2. Ajusta su tamaño (ej. 75 de altura) y añade un componente Horizontal Layout Group con Child Alignment a MiddleLeft y Child Force Expand desactivado para Width.
  3. Dentro de RecipeItem_Prefab, añade los siguientes elementos (todos con Layout Element -> Preferred Width para controlarlos):
    • Image (para el icono del resultado del ítem).
    • Text (TMP) (para el nombre del ítem resultante).
    • Text (TMP) (para la cantidad de ítems resultantes).
    • Text (TMP) (para mostrar los ingredientes necesarios).
    • Button (para el botón "Craftear").
  4. Asegúrate de que el botón tenga un Text (TMP) dentro con el texto "Craftear".
  5. Crea una nueva carpeta Prefabs/UI y arrastra RecipeItem_Prefab a ella. Luego, borra la instancia de la jerarquía.
CraftingUI_Canvas CraftingPanel Mesa de Crafteo (Text) Scroll View (RecipeList) Viewport Content (RecipeListContent) RecipeItem_Prefab Icon (Image) Item Name (Text) Quantity (Text) Ingredients (Text) Craft Button (Button)

4.5. Script RecipeUI

Este script se encargará de actualizar un RecipeItem_Prefab con los datos de una Recipe específica.

Crea un script Scripts/UI/RecipeUI.cs:

using UnityEngine; 
using UnityEngine.UI; 
using TMPro; 

public class RecipeUI : MonoBehaviour
{
    public Image itemIcon;
    public TextMeshProUGUI itemNameText;
    public TextMeshProUGUI itemQuantityText;
    public TextMeshProUGUI ingredientsText;
    public Button craftButton;

    private Recipe currentRecipe;

    public void SetRecipe(Recipe recipe)
    {
        currentRecipe = recipe;

        itemIcon.sprite = recipe.resultItem.icon;
        itemNameText.text = recipe.resultItem.itemName;
        itemQuantityText.text = "x" + recipe.resultQuantity.ToString();

        string ingredientsString = "Ingredientes: ";
        foreach (Ingredient ingredient in recipe.ingredients)
        {
            ingredientsString += $"{ingredient.item.itemName} ({ingredient.quantity}) ";
        }
        ingredientsText.text = ingredientsString;

        craftButton.onClick.RemoveAllListeners();
        craftButton.onClick.AddListener(CraftItem);

        UpdateCraftButtonState();
    }

    public void UpdateCraftButtonState()
    {
        bool canCraft = CraftingManager.Instance.CanCraft(currentRecipe);
        craftButton.interactable = canCraft;
        craftButton.GetComponentInChildren<TextMeshProUGUI>().text = canCraft ? "Craftear" : "Faltan Ing.";
    }

    void CraftItem()
    {
        CraftingManager.Instance.CraftItem(currentRecipe);
        // Optionally, refresh all recipe UIs after crafting
        FindObjectOfType<CraftingCanvasUI>()?.RefreshRecipes();
    }
}

Adjunta este script al RecipeItem_Prefab y arrastra los componentes UI correspondientes a sus slots en el Inspector.

4.6. Script CraftingCanvasUI

Este script gestionará la creación de instancias de RecipeUI y su actualización.

Crea un script Scripts/UI/CraftingCanvasUI.cs:

using System.Collections.Generic;
using UnityEngine;

public class CraftingCanvasUI : MonoBehaviour
{
    public GameObject craftingPanel;
    public GameObject recipeListContent;
    public GameObject recipeUIPrefab;

    private List<RecipeUI> activeRecipeUIs = new List<RecipeUI>();

    void Start()
    {
        craftingPanel.SetActive(false); // Start hidden
        RefreshRecipes(); // Initial refresh
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.C))
        {
            ToggleCraftingPanel();
        }
    }

    public void ToggleCraftingPanel()
    {
        craftingPanel.SetActive(!craftingPanel.activeSelf);
        if (craftingPanel.activeSelf)
        {
            RefreshRecipes();
        }
    }

    public void RefreshRecipes()
    {
        // Clear old recipes
        foreach (RecipeUI ui in activeRecipeUIs)
        {
            Destroy(ui.gameObject);
        }
        activeRecipeUIs.Clear();

        // Instantiate new ones
        foreach (Recipe recipe in CraftingManager.Instance.GetAllRecipes())
        {
            GameObject recipeGO = Instantiate(recipeUIPrefab, recipeListContent.transform);
            RecipeUI recipeUI = recipeGO.GetComponent<RecipeUI>();
            if (recipeUI != null)
            {
                recipeUI.SetRecipe(recipe);
                activeRecipeUIs.Add(recipeUI);
            }
        }
    }

    // Call this to update button states, e.g., after adding/removing items from inventory
    public void UpdateAllRecipeButtonStates()
    {
        foreach (RecipeUI ui in activeRecipeUIs)
        {
            ui.UpdateCraftButtonState();
        }
    }
}

Adjunta este script al CraftingUI_Canvas y arrastra los GameObjects correspondientes (CraftingPanel, RecipeListContent, RecipeItem_Prefab) a sus slots en el Inspector.

🔥 Importante: Asegúrate de que los sprites de los iconos de los ítems tengan el `Texture Type` en `Sprite (2D and UI)` en sus propiedades de importación.

✨ Paso 5: Puesta en Marcha - Crear Ítems y Recetas

Ahora es el momento de crear nuestros ítems y recetas usando los ScriptableObjects.

5.1. Crear Ítems de Prueba

  1. En tu carpeta Assets, crea una nueva carpeta Items.
  2. Haz clic derecho -> Create -> Inventory -> Item.
  3. Crea varios ítems, como:
    • Wood (madera): maxStack = 99
    • Stone (piedra): maxStack = 99
    • Iron Ore (mineral de hierro): maxStack = 99
    • Iron Bar (barra de hierro): isCraftable = true, maxStack = 99
    • Wooden Pickaxe (pico de madera): isCraftable = true, maxStack = 1

Asigna un icono a cada uno si los tienes. Puedes usar placeholders simples por ahora.

5.2. Crear Recetas de Prueba

  1. En tu carpeta Assets, crea una nueva carpeta Recipes.
  2. Haz clic derecho -> Create -> Crafting -> Recipe.
  3. Crea algunas recetas, por ejemplo:
    • Recipe: Iron Bar
      • Result Item: Iron Bar (Result Quantity = 1)
      • Ingredients:
        • Iron Ore (Quantity = 2)
    • Recipe: Wooden Pickaxe
      • Result Item: Wooden Pickaxe (Result Quantity = 1)
      • Ingredients:
        • Wood (Quantity = 3)
        • Stone (Quantity = 2)

5.3. Configurar los Managers en la Escena

  1. Crea un GameObject vacío en tu escena y renómbralo a GameManagers.
  2. Añade el componente InventoryManager a GameManagers.
  3. Añade el componente CraftingManager a GameManagers.
  4. En el CraftingManager, arrastra todas tus Recipe ScriptableObjects (de la carpeta Recipes) a la lista All Recipes en el Inspector.
📌 Nota: Los managers se iniciarán en la escena y `CraftingManager` cargará todas las recetas que le has asignado.

5.4. Probar el Crafteo

Para probar, podemos añadir algunos ítems iniciales al inventario del jugador. Puedes hacerlo manualmente añadiendo ítems al inventorySlots de InventoryManager en el Inspector, o añadiendo un método en Start() para ello:

// En InventoryManager.cs, dentro de Awake o Start para fines de prueba
void Start()
{
    // Para propósitos de prueba: añadir algunos ítems al inicio
    // Asegúrate de arrastrar tus ScriptableObjects de Item aquí en el Inspector
    if (testStartingItems.Count > 0)
    {
        foreach (var itemPair in testStartingItems)
        {
            AddItem(itemPair.item, itemPair.quantity);
        }
    }
}

// Y crea esta clase en InventoryManager si quieres
[System.Serializable]
public class ItemQuantityPair
{
    public Item item;
    public int quantity;
}

public List<ItemQuantityPair> testStartingItems = new List<ItemQuantityPair>();

Ahora, arrastra tus ítems de prueba (Wood, Stone, Iron Ore) a la lista Test Starting Items y dales una cantidad inicial (ej. 10 de cada uno).

  1. Ejecuta el juego.
  2. Presiona la tecla C para abrir/cerrar el panel de crafteo.
  3. Verás las recetas. Si tienes los ingredientes, el botón "Craftear" estará activo. Haz clic en él.
  4. Observa cómo el Debug.Log muestra el ítem crafteado y cómo los ítems se consumen de tu inventario (lo puedes ver en el Inspector del InventoryManager si lo seleccionas durante la ejecución).
¡Sistema de Crafteo Funcional!

🚀 Paso 6: Mejoras y Extensiones Futuras

Este es un sistema básico pero funcional. Aquí hay ideas para extenderlo:

  • Feedback Visual: Añadir efectos de sonido o partículas al craftear.
  • Inventario Visual: Crear una UI para el inventario del jugador y que los cambios se reflejen allí.
  • Recetas Descubribles: Que las recetas solo aparezcan en la UI una vez que el jugador ha recolectado al menos uno de sus ingredientes, o ha encontrado un pergamino de receta.
  • Crafteo por Estructuras: Algunas recetas solo se pueden craftear en una mesa de trabajo, una fragua, etc. Puedes añadir un requisito de CraftingStation a la Recipe.
  • Animaciones: Animar el panel de crafteo al abrir/cerrar.
  • Categorías de Recetas: Organizar las recetas en la UI por categorías (armas, herramientas, pociones).
  • Arrastrar y Soltar: Permitir arrastrar ítems del inventario a slots de crafteo específicos.
¿Por qué usar ScriptableObjects para ítems y recetas? Los ScriptableObjects son activos de datos. No están atados a un GameObject en la escena, lo que los hace ideales para:
  • Gestión de datos: Puedes crear miles de ítems y recetas como archivos en el proyecto sin tener que crear un prefabs para cada uno o cargar datos desde archivos externos (aunque eso también es posible).
  • Reutilización: Múltiples instancias de un ítem pueden referenciar el mismo ScriptableObject de ítem, ahorrando memoria.
  • Facilidad de edición: Los diseñadores de juego pueden crear y modificar ítems y recetas directamente en el Inspector de Unity sin necesidad de tocar el código.

✅ Conclusión

¡Felicidades! 🎉 Has construido un sistema de crafteo dinámico en Unity. Has aprendido a estructurar los datos de ítems y recetas con ScriptableObjects, a manejar un inventario básico y a crear la lógica central del crafteo junto con una interfaz de usuario funcional.

Este sistema es una base sólida que puedes expandir y adaptar a las necesidades específicas de tu juego. La clave es la modularidad y la facilidad de extensión. ¡Ahora sal y crea mundos donde el crafteo sea el corazón de la aventura!

Si tienes alguna pregunta o sugerencia, no dudes en dejar un comentario.

¡Feliz Crafteo!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!