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.
¡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.
📦 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 deItemdirectamente 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 objetosIngredient. CadaIngredienttiene unItemy unaquantityrequerida.[System.Serializable]: Hace que la claseIngredientsea visible en el Inspector de Unity, permitiéndonos rellenar sus propiedades sin crear unScriptableObjectseparado para cada ingrediente.
🛒 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.
📖 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): SiCanCraftes 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
- En la jerarquía de Unity, haz clic derecho ->
UI->Canvas. Renómbralo aCraftingUI_Canvas. - Selecciona el Canvas y en el Inspector, en
Canvas Scaler, cambiaUI Scale ModeaScale With Screen Sizey estableceReference Resolutiona1920x1080.
4.2. Panel de Crafteo Principal
- Dentro del
CraftingUI_Canvas, haz clic derecho ->UI->Panel. Renómbralo aCraftingPanel. - Ajusta su
Rect Transformpara que ocupe una porción de la pantalla (por ejemplo, anclado al centro, con un tamaño de 800x600). - Añade un componente
Vertical Layout GroupalCraftingPanely configuraChild AlignmentaUpper CenteryControl Child SizeparaHeight. - Dentro del
CraftingPanel, añade unText (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
- Dentro del
CraftingPanel, crea unGameObjectvacío y renómbralo aRecipeListContent. - Añade un
Vertical Layout Groupy unContentSizeFitteraRecipeListContent. EnContentSizeFitter, estableceVertical FitaPreferred Size. - Para poder desplazarnos si hay muchas recetas, añade un
Scroll View(UI->Scroll View) dentro deCraftingPanely muévelo para queRecipeListContentsea elContentde esteScroll View.
4.4. Elemento de Receta (Prefab)
Crearemos un prefab que representará cada receta en la UI:
- Dentro de
RecipeListContent, haz clic derecho ->UI->Panel. Renómbralo aRecipeItem_Prefab. - Ajusta su tamaño (ej. 75 de altura) y añade un componente
Horizontal Layout GroupconChild AlignmentaMiddleLeftyChild Force Expanddesactivado paraWidth. - Dentro de
RecipeItem_Prefab, añade los siguientes elementos (todos conLayout Element->Preferred Widthpara 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").
- Asegúrate de que el botón tenga un
Text (TMP)dentro con el texto "Craftear". - Crea una nueva carpeta
Prefabs/UIy arrastraRecipeItem_Prefaba ella. Luego, borra la instancia de la jerarquía.
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.
✨ 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
- En tu carpeta
Assets, crea una nueva carpetaItems. - Haz clic derecho ->
Create->Inventory->Item. - Crea varios ítems, como:
Wood(madera):maxStack = 99Stone(piedra):maxStack = 99Iron Ore(mineral de hierro):maxStack = 99Iron Bar(barra de hierro):isCraftable = true,maxStack = 99Wooden 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
- En tu carpeta
Assets, crea una nueva carpetaRecipes. - Haz clic derecho ->
Create->Crafting->Recipe. - 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)
- Recipe: Iron Bar
5.3. Configurar los Managers en la Escena
- Crea un
GameObjectvacío en tu escena y renómbralo aGameManagers. - Añade el componente
InventoryManageraGameManagers. - Añade el componente
CraftingManageraGameManagers. - En el
CraftingManager, arrastra todas tusRecipeScriptableObjects(de la carpetaRecipes) a la listaAll Recipesen el Inspector.
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).
- Ejecuta el juego.
- Presiona la tecla C para abrir/cerrar el panel de crafteo.
- Verás las recetas. Si tienes los ingredientes, el botón "Craftear" estará activo. Haz clic en él.
- Observa cómo el
Debug.Logmuestra el ítem crafteado y cómo los ítems se consumen de tu inventario (lo puedes ver en el Inspector delInventoryManagersi lo seleccionas durante la ejecución).
🚀 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
CraftingStationa laRecipe. - 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
- Creando un Sistema de Puzzles Basados en la Física en Unity 🧩 Desafíos Ingeniososintermediate20 min
- Optimización Avanzada de Rendimiento en Unity: Sacando el Máximo a tus Juegos 🚀intermediate18 min
- Creando un Sistema de Inventario Modular en Unity para RPGs y Juegos de Aventuraintermediate25 min
- Creando un Sistema de Portales Dimensiones con Render Textures en Unity 🌌intermediate18 min
- Creando un Sistema de Partículas Personalizado en Unity para Efectos Visuales Impresionantes ✨intermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!