Files
my-project/Assets/Inventory/Scripts/InventoryManager.cs
2026-04-16 01:51:46 +05:00

630 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<ItemData> itemDatabase;
[Header("References")]
public GameObject inventoryPanel;
public Canvas inventoryCanvas;
[Header("Events")]
/// <param name="itemId">ID предмета</param>
/// <param name="amount">Количество</param>
public UnityEvent<string, int> OnItemAdded;
public UnityEvent<string, int> OnItemRemoved;
// Приватные поля
private Dictionary<string, ItemData> _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<InventoryItemFactory>();
DontDestroyOnLoad(factoryGO);
}
// Словарь создаём лениво
RebuildItemDictionary();
}
/// <summary>
/// Пересоздать словарь предметов из itemDatabase
/// </summary>
public void RebuildItemDictionary()
{
_itemDict = new Dictionary<string, ItemData>();
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()
{
// Слоты созданы editor-инструментом и назначены в Inspector
if (slots == null || slots.Length == 0)
Debug.LogWarning("[InventoryManager] Slots not assigned! Run Tools > Setup Complete Scene first.");
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 (PlayerInput.Instance != null)
{
PlayerInput.Instance.OnToggleInventory += ToggleInventory;
PlayerInput.Instance.OnDropPressed += DropSelectedItem;
PlayerInput.Instance.OnHotbarSelect += SelectHotbarSlot;
}
else
{
Debug.LogWarning("[InventoryManager] PlayerInput not found. Run Tools > Setup Complete Scene first.");
}
// Загрузить сохранённый инвентарь (слоты уже существуют)
LoadFromFile();
}
public void ToggleInventory()
{
if (inventoryPanel != null)
{
bool wasActive = inventoryPanel.activeSelf;
inventoryPanel.SetActive(!wasActive);
if (!wasActive)
{
// Открытие инвентаря
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
InventoryEvents.Instance?.OnInventoryOpened?.Invoke();
if (PlayerInput.Instance != null)
PlayerInput.Instance.Enabled = false;
}
else
{
// Закрытие инвентаря
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
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: Эвент для использования предмета
}
/// <summary>
/// Получить предмет в выбранном слоте хотбара
/// </summary>
public ItemData GetSelectedHotbarItem()
{
if (hotbarSlots != null && _selectedHotbarSlot >= 0 && _selectedHotbarSlot < hotbarSlots.Length)
{
return hotbarSlots[_selectedHotbarSlot].item;
}
return null;
}
/// <summary>
/// Выбросить выбранный предмет из хотбара
/// </summary>
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}");
}
// ==================== ОСНОВНЫЕ ОПЕРАЦИИ ====================
/// <summary>
/// Добавить предмет в инвентарь
/// </summary>
/// <returns>Количество не поместившихся предметов</returns>
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;
}
/// <summary>
/// Удалить предмет из инвентаря
/// </summary>
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;
}
/// <summary>
/// Получить количество предмета в инвентаре
/// </summary>
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;
}
/// <summary>
/// Есть ли предмет в инвентаре
/// </summary>
public bool HasItem(string itemId, int amount = 1)
{
return GetItemCount(itemId) >= amount;
}
/// <summary>
/// Получить слот по индексу
/// </summary>
public Slot GetSlot(int index)
{
if (index >= 0 && index < slots.Length)
return slots[index];
return null;
}
/// <summary>
/// Найти первый слот с указанным предметом
/// </summary>
public Slot FindSlotWithItem(string itemId)
{
foreach (var slot in slots)
{
if (slot.item != null && slot.item.id == itemId)
return slot;
}
return null;
}
/// <summary>
/// Найти все слоты с указанным предметом
/// </summary>
public List<Slot> FindAllSlotsWithItem(string itemId)
{
var result = new List<Slot>();
foreach (var slot in slots)
{
if (slot.item != null && slot.item.id == itemId)
result.Add(slot);
}
return result;
}
/// <summary>
/// Найти первый пустой слот
/// </summary>
public Slot FindEmptySlot()
{
foreach (var slot in slots)
{
if (slot.IsEmpty)
return slot;
}
return null;
}
/// <summary>
/// Поменять предметы между двумя слотами
/// </summary>
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);
}
}
/// <summary>
/// Очистить инвентарь
/// </summary>
public void ClearInventory()
{
foreach (var slot in slots)
{
slot?.Clear();
}
}
/// <summary>
/// Вызывается когда слот очищен (внутренний метод)
/// </summary>
public void OnSlotCleared(int slotIndex)
{
// Можно добавить дополнительную логику
}
// ==================== ТЕСТОВЫЕ МЕТОДЫ ====================
/// <summary>
/// Создать тестовый предмет в мире (для отладки)
/// </summary>
[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);
}
/// <summary>
/// Сохранить инвентарь в файл (Application.persistentDataPath).
/// Доступно через правый клик на компоненте в Inspector во время Play Mode.
/// </summary>
[ContextMenu("Save Inventory")]
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}");
}
}
/// <summary>
/// Загрузить инвентарь из файла (если есть).
/// Вызывается из InventoryUIBuilder после создания слотов.
/// Доступно через правый клик на компоненте в Inspector во время Play Mode.
/// </summary>
[ContextMenu("Load Inventory")]
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<InventorySaveData>(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();
}
}