Redirect to the Docs main page.Docs

Scripting

Creating a Crafting System

Learn how to develop a complete crafting system with Ultimate Grid Inventory, including recipe management, inventory validation, and item crafting.

Building a Crafting System with UGI

This guide will show you how to implement a complete crafting system using Ultimate Grid Inventory (UGI). You'll learn how to create recipes, validate ingredients, and handle the crafting process.

Understanding Crafting Systems

A crafting system in games typically involves:

  • Recipes: Definitions of what items can be crafted and what ingredients are required
  • Inventory Validation: Checking if the player has all required ingredients
  • Crafting Process: Creating the new item and consuming the ingredients
  • UI Integration: Displaying available recipes and crafting options to the player

Core Components

Our crafting system implementation will include:

  1. A Recipe class that defines crafted items and their requirements
  2. Methods to check the player's inventory for required ingredients
  3. Functions to handle the crafting process (removing ingredients and adding crafted items)
  4. Integration with the UGI inventory system for seamless item management

Implementation Example

Here's a complete example of a crafting system that integrates with UGI:

using System;
using System.Collections.Generic;
using System.Linq;
using Inventory.Scripts.Core.Controllers;
using Inventory.Scripts.Core.Grids;
using Inventory.Scripts.Core.Enums;
using Inventory.Scripts.Core.Items;
using Inventory.Scripts.Core.Items.Metadata;
using Inventory.Scripts.Core.ScriptableObjects;
using Inventory.Scripts.Core.ScriptableObjects.Items;
using UnityEngine;
 
namespace Inventory.Scripts.Utilities
{
    public class CraftingSystem : MonoBehaviour
    {
        [SerializeField] private InventorySo playerInventorySo;
        [SerializeField] private InventorySupplierSo inventorySupplierSo;
 
        [Tooltip("Optional list of recipes available in this crafting station")]
        [SerializeField] private List<Recipe> availableRecipes = new List<Recipe>();
 
        /// <summary>
        /// Attempts to craft an item based on the provided recipe
        /// </summary>
        /// <param name="recipe">The recipe to craft</param>
        /// <returns>True if crafting was successful, false otherwise</returns>
        public bool CraftItem(Recipe recipe)
        {
            // Step 1: Get the recipe requirements
            var recipeRequirements = recipe.RecipeRequirements;
 
            // Step 2: Check if player has all required ingredients
            var (canCraftItem, itemsToRemove) = ContainsAllItemsInPlayerInventory(recipeRequirements);
 
            // Step 3: If missing ingredients, abort crafting
            if (!canCraftItem)
            {
                Debug.Log("Cannot craft - missing required ingredients");
                return false;
            }
 
            // Step 4: Try to find space for the crafted item in inventory
            var (item, gridResponse) =
                inventorySupplierSo.FindPlaceForItemInGrids(recipe.ItemToCraft, playerInventorySo.GetGrids());
 
            // Step 5: If no space available, abort crafting
            if (gridResponse != GridResponse.Inserted)
            {
                Debug.Log("Cannot craft - inventory is full");
                return false;
            }
 
            // Step 6: Remove the ingredients that were used
            foreach (var itemTable in itemsToRemove)
            {
                inventorySupplierSo.RemoveItem(itemTable);
            }
 
            // Step 7: Crafting successful!
            Debug.Log($"Successfully crafted: {item.ItemDataSo.DisplayName}");
            return true;
        }
 
        /// <summary>
        /// Checks if the player's inventory contains all required items for a recipe
        /// </summary>
        private (bool, List<ItemTable>) ContainsAllItemsInPlayerInventory(List<ItemDataSo> itemsRecipe)
        {
            // Get all items from all inventory grids
            var allItemsFromGrids =
                playerInventorySo.GetGrids().SelectMany(grid => grid.GetAllItemsFromGrid()).ToList();
 
            return CheckRecipe(allItemsFromGrids, itemsRecipe);
        }
 
        /// <summary>
        /// Validates if all recipe requirements are met and returns the items to be consumed
        /// </summary>
        private static (bool, List<ItemTable>) CheckRecipe(List<ItemTable> playerItems, List<ItemDataSo> recipeItems)
        {
            var recipeCounts = new Dictionary<ItemDataSo, double>();
            foreach (var item in recipeItems)
            {
                recipeCounts.TryAdd(item, 0);
                recipeCounts[item] += 1;
            }
 
            var itemsToRemove = new List<ItemTable>();
            var remainingNeeds = new Dictionary<ItemDataSo, double>(recipeCounts);
 
            foreach (var kvp in remainingNeeds.ToList())
            {
                var neededItem = kvp.Key;
                var remainingAmount = kvp.Value;
 
                var matchingItems = playerItems
                    .Where(i => i.ItemDataSo == neededItem)
                    .ToList();
 
                foreach (var item in matchingItems)
                {
                    if (remainingAmount <= 0)
                        break;
 
                    var metadata = item.GetMetadata<CountableMetadata>();
 
                    if (metadata != null)
                    {
                        var available = metadata.Stack;
                        var toTake = Math.Min(available, remainingAmount);
 
                        var stackableData = item.GetItemData<ItemStackableDataSo>();
                        var minStack = stackableData != null 
                            ? stackableData.MinStack 
                            : 0;
 
                        // Check if we can split without breaking rules
                        var canSplit = toTake >= minStack && available - toTake >= minStack;
 
                        if (canSplit)
                        {
                            var splitItem = metadata.Split(toTake);
                            if (splitItem != null)
                            {
                                itemsToRemove.Add(splitItem);
                                remainingAmount -= toTake;
                            }
                        }
                        else if (available <= remainingAmount && available >= minStack)
                        {
                            // Take the full item if possible (no need to split)
                            itemsToRemove.Add(item);
                            remainingAmount -= available;
 
                            // Avoid double-using this item
                            playerItems.Remove(item);
                        }
                    }
                    else
                    {
                        itemsToRemove.Add(item);
                        remainingAmount -= 1.0;
                    }
                }
 
                remainingNeeds[neededItem] = remainingAmount;
            }
 
            var canCraft = remainingNeeds.Values.All(v => v <= 0.0001);
            return (canCraft, canCraft ? itemsToRemove : new List<ItemTable>());
        }
 
 
        /// <summary>
        /// Recipe definition class - can be extracted to a separate file
        /// </summary>
        [Serializable]
        public class Recipe
        {
            [Tooltip("The item that will be created when crafting")]
            [SerializeField] private ItemDataSo itemToCraft;
 
            [Tooltip("The items required to craft this recipe")]
            [SerializeField] private List<ItemDataSo> recipeRequirements;
 
            public ItemDataSo ItemToCraft => itemToCraft;
            public List<ItemDataSo> RecipeRequirements => recipeRequirements;
        }
    }
}

Implementation Notes

  • Recipe Management: The Recipe class is defined as a serializable class, allowing you to create and configure recipes directly in the Unity Inspector.
  • Inventory Integration: The system uses InventorySupplierSo to interact with the player's inventory grids.
  • Error Handling: The code includes checks for missing ingredients and full inventory conditions.
  • Extensibility: You can easily extend this system to include crafting animations, sound effects, or crafting stations with different available recipes.

Creating Recipe Scriptable Objects

For a more modular approach, you can create a RecipeSO scriptable object:

  1. Create a new C# script called RecipeSO.cs
  2. Implement it as a ScriptableObject with the same properties as the Recipe class
  3. Create recipe instances via Create > Inventory > Crafting > New Recipe

Next Steps

After implementing your crafting system, consider these enhancements:

  • Add a crafting UI that displays available recipes and required ingredients
  • Implement recipe discovery mechanics where players can learn new recipes
  • Create crafting stations with different available recipes
  • Add crafting animations and sound effects for a more immersive experience
  • Implement crafting requirements beyond just ingredients (e.g., player level, skills)

By following this guide, you'll have a robust crafting system that integrates seamlessly with the Ultimate Grid Inventory system, enhancing your game's depth and player engagement.

On this page