← Назад к вопросам

Реализовать систему крафта предметов

2.0 Middle🔥 191 комментариев

Условие

Реализуйте систему крафта предметов.

Требования

  1. Рецепты как ScriptableObject
  2. Проверка наличия ингредиентов
  3. Время крафта
  4. Очередь крафта
  5. UI крафт меню
  6. Разблокировка рецептов

Рецепт содержит

  • Список ингредиентов с количеством
  • Результат крафта
  • Время изготовления
  • Требуемый уровень навыка

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Система крафта предметов в Unity

Создам модульную архитектуру на основе ScriptableObject и Manager паттернов. Система должна быть гибкой, расширяемой и удобной в использовании.

Структура рецепта (ScriptableObject)

[System.Serializable]
public struct RecipeIngredient
{
    public Item item;
    public int quantity;
}

public class Recipe : ScriptableObject
{
    [SerializeField] private string recipeName;
    [SerializeField] private List<RecipeIngredient> ingredients;
    [SerializeField] private Item result;
    [SerializeField] private int resultQuantity = 1;
    [SerializeField] private float craftingTime = 5f;
    [SerializeField] private int requiredSkillLevel = 1;
    [SerializeField] private bool isUnlocked;

    public string RecipeName => recipeName;
    public List<RecipeIngredient> Ingredients => ingredients;
    public Item Result => result;
    public int ResultQuantity => resultQuantity;
    public float CraftingTime => craftingTime;
    public int RequiredSkillLevel => requiredSkillLevel;
    public bool IsUnlocked => isUnlocked;

    public void Unlock() => isUnlocked = true;
}

Manager крафта с очередью

public class CraftingManager : MonoBehaviour
{
    [SerializeField] private Queue<CraftingTask> craftingQueue = new();
    [SerializeField] private CraftingTask currentTask;
    [SerializeField] private Inventory inventory;
    [SerializeField] private PlayerSkills playerSkills;

    [System.Serializable]
    private class CraftingTask
    {
        public Recipe recipe;
        public float timeRemaining;
    }

    public bool TryStartCrafting(Recipe recipe)
    {
        if (!recipe.IsUnlocked)
        {
            Debug.Log("Рецепт не разблокирован");
            return false;
        }

        if (playerSkills.GetSkillLevel("Crafting") < recipe.RequiredSkillLevel)
        {
            Debug.Log($"Требуется уровень {recipe.RequiredSkillLevel}");
            return false;
        }

        foreach (var ingredient in recipe.Ingredients)
        {
            if (inventory.GetItemCount(ingredient.item) < ingredient.quantity)
            {
                Debug.Log($"Недостаточно {ingredient.item.name}");
                return false;
            }
        }

        foreach (var ingredient in recipe.Ingredients)
        {
            inventory.RemoveItem(ingredient.item, ingredient.quantity);
        }

        var task = new CraftingTask
        {
            recipe = recipe,
            timeRemaining = recipe.CraftingTime
        };

        if (currentTask == null)
        {
            currentTask = task;
            OnCraftingStarted?.Invoke(recipe);
        }
        else
        {
            craftingQueue.Enqueue(task);
        }

        return true;
    }

    private void Update()
    {
        if (currentTask == null && craftingQueue.Count == 0)
            return;

        if (currentTask == null)
        {
            currentTask = craftingQueue.Dequeue();
            OnCraftingStarted?.Invoke(currentTask.recipe);
        }

        currentTask.timeRemaining -= Time.deltaTime;
        OnCraftingProgress?.Invoke(1f - (currentTask.timeRemaining / currentTask.recipe.CraftingTime));

        if (currentTask.timeRemaining <= 0)
        {
            CompleteCrafting();
        }
    }

    private void CompleteCrafting()
    {
        var recipe = currentTask.recipe;
        inventory.AddItem(recipe.Result, recipe.ResultQuantity);
        playerSkills.AddExperience("Crafting", (int)recipe.CraftingTime);
        
        OnCraftingCompleted?.Invoke(recipe);
        currentTask = null;
    }

    public event System.Action<Recipe> OnCraftingStarted;
    public event System.Action<float> OnCraftingProgress;
    public event System.Action<Recipe> OnCraftingCompleted;
}

UI меню крафта

public class CraftingUI : MonoBehaviour
{
    [SerializeField] private Transform recipesContainer;
    [SerializeField] private CraftingRecipeButton recipeButtonPrefab;
    [SerializeField] private Text progressText;
    [SerializeField] private Image progressBar;
    [SerializeField] private CraftingManager craftingManager;
    [SerializeField] private List<Recipe> allRecipes;

    private void Start()
    {
        DisplayRecipes();
        craftingManager.OnCraftingStarted += UpdateUI;
        craftingManager.OnCraftingProgress += UpdateProgressBar;
    }

    private void DisplayRecipes()
    {
        foreach (var recipe in allRecipes)
        {
            var button = Instantiate(recipeButtonPrefab, recipesContainer);
            button.Setup(recipe, craftingManager);
        }
    }

    private void UpdateProgressBar(float progress)
    {
        progressBar.fillAmount = progress;
        progressText.text = $"{progress:P0}";
    }

    private void OnDestroy()
    {
        craftingManager.OnCraftingStarted -= UpdateUI;
        craftingManager.OnCraftingProgress -= UpdateProgressBar;
    }
}

Преимущества решения

  • Модульность: рецепты — независимые ScriptableObject'ы
  • Очередь: несколько крафтов подряд
  • Валидация: проверка ингредиентов перед стартом
  • Event система: слабая связанность между компонентами
  • Расширяемость: легко добавить баффы, скидки, критические успехи