using UnityEngine; using System.Collections.Generic; using UnityEngine.Events; using System.IO; public class InventoryManager : MonoBehaviour { public static InventoryManager Instance { get; private set; } [Header("Inventory Settings")] public int inventorySize = 36; public Slot[] slots; public Slot[] hotbarSlots; // 9 слотов хотбара (индексы 0-8) [Header("Item Database")] public List itemDatabase; [Header("References")] public GameObject inventoryPanel; public Canvas inventoryCanvas; [Header("Events")] /// ID предмета /// Количество public UnityEvent OnItemAdded; public UnityEvent OnItemRemoved; // Приватные поля private Dictionary _itemDict; private int _selectedHotbarSlot = 0; void Awake() { // Сделать этот объект корневым для DontDestroyOnLoad transform.SetParent(null); if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); return; } // Создаём InventoryItemFactory если его нет if (InventoryItemFactory.Instance == null) { var factoryGO = new GameObject("InventoryItemFactory"); factoryGO.AddComponent(); DontDestroyOnLoad(factoryGO); } // Словарь создаём лениво RebuildItemDictionary(); } /// /// Пересоздать словарь предметов из itemDatabase /// public void RebuildItemDictionary() { _itemDict = new Dictionary(); if (itemDatabase != null) { foreach (var item in itemDatabase) { if (!string.IsNullOrEmpty(item?.id)) { _itemDict[item.id] = item; } } } Debug.Log($"[InventoryManager] Loaded {_itemDict.Count} items into dictionary"); } void Start() { // Инициализация слотов if (slots == null || slots.Length == 0) { Debug.LogWarning("Slots not assigned! Assign them in Inspector or use InventoryUIBuilder."); } else { foreach (var slot in slots) { slot?.UpdateSlotUI(); } } if (hotbarSlots != null) { foreach (var slot in hotbarSlots) { slot?.UpdateSlotUI(); } } // Скрыть инвентарь по умолчанию if (inventoryPanel != null) inventoryPanel.SetActive(false); // Убедимся что события существуют if (InventoryEvents.Instance == null) { var eventsGO = new GameObject("InventoryEvents"); eventsGO.AddComponent(); } // Попробуем загрузить сохранённый инвентарь LoadFromFile(); // Ensure PlayerInput exists and subscribe to input events if (PlayerInput.Instance == null) { var go = new GameObject("PlayerInput"); go.AddComponent(); } PlayerInput.Instance.OnToggleInventory += ToggleInventory; PlayerInput.Instance.OnDropPressed += DropSelectedItem; PlayerInput.Instance.OnHotbarSelect += SelectHotbarSlot; } public void ToggleInventory() { if (inventoryPanel != null) { bool wasActive = inventoryPanel.activeSelf; inventoryPanel.SetActive(!wasActive); if (!wasActive) { Cursor.lockState = CursorLockMode.None; InventoryEvents.Instance?.OnInventoryOpened?.Invoke(); if (PlayerInput.Instance != null) PlayerInput.Instance.Enabled = false; } else { Cursor.lockState = CursorLockMode.Locked; InventoryEvents.Instance?.OnInventoryClosed?.Invoke(); if (PlayerInput.Instance != null) PlayerInput.Instance.Enabled = true; } } } public void SelectHotbarSlot(int index) { if (index < 0 || index >= 9 || hotbarSlots == null || hotbarSlots.Length < 9) return; // Снимаем выделение со старого if (_selectedHotbarSlot >= 0 && _selectedHotbarSlot < hotbarSlots.Length) { hotbarSlots[_selectedHotbarSlot].SetSelected(false); } _selectedHotbarSlot = index; hotbarSlots[index].SetSelected(true); // Получаем выбранный предмет var selectedItem = hotbarSlots[index].item; Debug.Log($"[Inventory] Selected hotbar slot: {index} - {selectedItem?.itemName ?? "Empty"}"); // TODO: Эвент для использования предмета } /// /// Получить предмет в выбранном слоте хотбара /// public ItemData GetSelectedHotbarItem() { if (hotbarSlots != null && _selectedHotbarSlot >= 0 && _selectedHotbarSlot < hotbarSlots.Length) { return hotbarSlots[_selectedHotbarSlot].item; } return null; } /// /// Выбросить выбранный предмет из хотбара /// public void DropSelectedItem() { if (hotbarSlots == null || _selectedHotbarSlot >= hotbarSlots.Length) return; var slot = hotbarSlots[_selectedHotbarSlot]; if (slot.IsEmpty) return; var item = slot.item; int amount = slot.amount; // Создаём предмет в мире через factory if (InventoryItemFactory.Instance != null) { // Используем позицию игрока Vector3 dropPos = Vector3.zero; // Заменить на позицию игрока if (Camera.main != null) { dropPos = Camera.main.transform.position + Camera.main.transform.forward * 2f; } InventoryItemFactory.Instance.DropItem(item, amount, dropPos); } // Очищаем слот slot.Clear(); Debug.Log($"[Inventory] Dropped {item.itemName} x{amount}"); } // ==================== ОСНОВНЫЕ ОПЕРАЦИИ ==================== /// /// Добавить предмет в инвентарь /// /// Количество не поместившихся предметов public int AddItem(string itemId, int amount = 1) { if (!_itemDict.TryGetValue(itemId, out ItemData item)) { Debug.LogWarning($"Item not found: {itemId}"); return amount; } return AddItem(item, amount); } public int AddItem(ItemData item, int amount = 1) { if (item == null) { Debug.LogWarning("[AddItem] item is null!"); return amount; } Debug.Log($"[AddItem] Adding {item.itemName} x{amount}, slots count: {slots?.Length ?? 0}"); if (slots == null || slots.Length == 0) { Debug.LogWarning("[AddItem] Slots not initialized!"); return amount; } // Сначала пытаемся доложить в существующие стеки (основной инвентарь) foreach (var slot in slots) { if (slot.IsEmpty) continue; if (slot.item.id == item.id) { amount = slot.AddItems(item, amount); if (amount <= 0) { OnItemAdded?.Invoke(item.id, amount); InventoryEvents.Instance?.InvokeItemAdded(item.id, slot.amount); return 0; } } } // Добавляем в пустые слоты (основной инвентарь) foreach (var slot in slots) { if (slot.IsEmpty) { amount = slot.AddItems(item, amount); if (amount <= 0) { OnItemAdded?.Invoke(item.id, amount); InventoryEvents.Instance?.InvokeItemAdded(item.id, slot.amount); return 0; } } } // Пробуем хотбар foreach (var slot in hotbarSlots) { if (!slot.IsEmpty && slot.item.id == item.id) { amount = slot.AddItems(item, amount); if (amount <= 0) { OnItemAdded?.Invoke(item.id, amount); InventoryEvents.Instance?.InvokeItemAdded(item.id, slot.amount); return 0; } } } foreach (var slot in hotbarSlots) { if (slot.IsEmpty) { amount = slot.AddItems(item, amount); if (amount <= 0) { OnItemAdded?.Invoke(item.id, amount); InventoryEvents.Instance?.InvokeItemAdded(item.id, slot.amount); return 0; } } } return amount; } /// /// Удалить предмет из инвентаря /// public bool RemoveItem(string itemId, int amount = 1) { int remaining = amount; foreach (var slot in slots) { if (slot.item != null && slot.item.id == itemId) { if (slot.amount >= remaining) { var (item, taken) = slot.TakeItems(remaining); OnItemRemoved?.Invoke(itemId, taken); InventoryEvents.Instance?.InvokeItemRemoved(itemId, taken); return true; } else { remaining -= slot.amount; slot.Clear(); OnItemRemoved?.Invoke(itemId, slot.amount); InventoryEvents.Instance?.InvokeItemRemoved(itemId, slot.amount); } } } // Пробуем хотбар if (remaining > 0) { foreach (var slot in hotbarSlots) { if (slot.item != null && slot.item.id == itemId) { if (slot.amount >= remaining) { var (item, taken) = slot.TakeItems(remaining); OnItemRemoved?.Invoke(itemId, taken); InventoryEvents.Instance?.InvokeItemRemoved(itemId, taken); return true; } else { remaining -= slot.amount; slot.Clear(); OnItemRemoved?.Invoke(itemId, slot.amount); InventoryEvents.Instance?.InvokeItemRemoved(itemId, slot.amount); } } } } return remaining <= 0; } /// /// Получить количество предмета в инвентаре /// public int GetItemCount(string itemId) { int count = 0; if (slots != null) { foreach (var slot in slots) { if (slot.item != null && slot.item.id == itemId) { count += slot.amount; } } } if (hotbarSlots != null) { foreach (var slot in hotbarSlots) { if (slot.item != null && slot.item.id == itemId) { count += slot.amount; } } } return count; } /// /// Есть ли предмет в инвентаре /// public bool HasItem(string itemId, int amount = 1) { return GetItemCount(itemId) >= amount; } /// /// Получить слот по индексу /// public Slot GetSlot(int index) { if (index >= 0 && index < slots.Length) return slots[index]; return null; } /// /// Найти первый слот с указанным предметом /// public Slot FindSlotWithItem(string itemId) { foreach (var slot in slots) { if (slot.item != null && slot.item.id == itemId) return slot; } return null; } /// /// Найти все слоты с указанным предметом /// public List FindAllSlotsWithItem(string itemId) { var result = new List(); foreach (var slot in slots) { if (slot.item != null && slot.item.id == itemId) result.Add(slot); } return result; } /// /// Найти первый пустой слот /// public Slot FindEmptySlot() { foreach (var slot in slots) { if (slot.IsEmpty) return slot; } return null; } /// /// Поменять предметы между двумя слотами /// public void SwapSlots(int fromIndex, int toIndex) { if (fromIndex < 0 || fromIndex >= slots.Length || toIndex < 0 || toIndex >= slots.Length) return; var fromSlot = slots[fromIndex]; var toSlot = slots[toIndex]; // Сохраняем данные var tempItem = fromSlot.item; var tempAmount = fromSlot.amount; // Меняем if (toSlot.IsEmpty) { fromSlot.Clear(); toSlot.SetItem(tempItem, tempAmount); } else { fromSlot.SetItem(toSlot.item, toSlot.amount); toSlot.SetItem(tempItem, tempAmount); } } /// /// Очистить инвентарь /// public void ClearInventory() { foreach (var slot in slots) { slot?.Clear(); } } /// /// Вызывается когда слот очищен (внутренний метод) /// public void OnSlotCleared(int slotIndex) { // Можно добавить дополнительную логику } // ==================== ТЕСТОВЫЕ МЕТОДЫ ==================== /// /// Создать тестовый предмет в мире (для отладки) /// [ContextMenu("Spawn Test Item")] public void SpawnTestItem() { if (itemDatabase == null || itemDatabase.Count == 0) { Debug.LogWarning("No items in database!"); return; } // Создаём предмет напротив игрока Vector3 spawnPos = Vector3.zero; var player = GameObject.FindGameObjectWithTag("Player"); if (player != null) { spawnPos = player.transform.position + player.transform.forward * 2f; spawnPos.y = 0.5f; } var item = itemDatabase[0]; if (InventoryItemFactory.Instance != null) { InventoryItemFactory.Instance.CreateWorldItem(item, 5, spawnPos); Debug.Log($"Spawned test item: {item.itemName}"); } } // ==================== РАБОТА С JSON ==================== public string ExportToJson() { var inventoryData = new InventorySaveData(); inventoryData.slots = new SlotData[slots.Length]; for (int i = 0; i < slots.Length; i++) { inventoryData.slots[i] = new SlotData { itemId = slots[i].item?.id, amount = slots[i].amount }; } return JsonUtility.ToJson(inventoryData, true); } /// /// Сохранить инвентарь в файл (Application.persistentDataPath) /// public void SaveToFile() { try { string json = ExportToJson(); string path = Path.Combine(Application.persistentDataPath, "inventory_save.json"); File.WriteAllText(path, json); Debug.Log($"[InventoryManager] Saved inventory to: {path}"); } catch (System.Exception ex) { Debug.LogError($"[InventoryManager] Failed to save inventory: {ex}"); } } /// /// Загрузить инвентарь из файла (если есть) /// public void LoadFromFile() { try { string path = Path.Combine(Application.persistentDataPath, "inventory_save.json"); if (File.Exists(path)) { string json = File.ReadAllText(path); ImportFromJson(json); Debug.Log($"[InventoryManager] Loaded inventory from: {path}"); } else { Debug.Log("[InventoryManager] No save file found."); } } catch (System.Exception ex) { Debug.LogError($"[InventoryManager] Failed to load inventory: {ex}"); } } public void ImportFromJson(string json) { var inventoryData = JsonUtility.FromJson(json); for (int i = 0; i < Mathf.Min(inventoryData.slots.Length, slots.Length); i++) { var slotData = inventoryData.slots[i]; if (!string.IsNullOrEmpty(slotData.itemId) && _itemDict.TryGetValue(slotData.itemId, out ItemData item)) { slots[i].SetItem(item, slotData.amount); } else { slots[i].Clear(); } } } [System.Serializable] class InventorySaveData { public SlotData[] slots; } [System.Serializable] class SlotData { public string itemId; public int amount; } void OnApplicationQuit() { SaveToFile(); } }