601 lines
17 KiB
C#
601 lines
17 KiB
C#
using UnityEngine;
|
||
using System.Collections.Generic;
|
||
using UnityEngine.Events;
|
||
|
||
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()
|
||
{
|
||
// Инициализация слотов
|
||
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<InventoryEvents>();
|
||
}
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
// Toggle инвентаря на Tab
|
||
if (Input.GetKeyDown(KeyCode.Tab))
|
||
{
|
||
ToggleInventory();
|
||
}
|
||
|
||
// Закрыть на Escape
|
||
if (Input.GetKeyDown(KeyCode.Escape) && inventoryPanel != null && inventoryPanel.activeSelf)
|
||
{
|
||
ToggleInventory();
|
||
}
|
||
|
||
// Горячие клавиши 1-9 для хотбара
|
||
for (int i = 0; i < 9; i++)
|
||
{
|
||
if (Input.GetKeyDown(KeyCode.Alpha1 + i))
|
||
{
|
||
SelectHotbarSlot(i);
|
||
}
|
||
}
|
||
|
||
// Выброс предмета на Q
|
||
if (Input.GetKeyDown(KeyCode.Q))
|
||
{
|
||
DropSelectedItem();
|
||
}
|
||
}
|
||
|
||
public void ToggleInventory()
|
||
{
|
||
if (inventoryPanel != null)
|
||
{
|
||
bool wasActive = inventoryPanel.activeSelf;
|
||
inventoryPanel.SetActive(!wasActive);
|
||
|
||
if (!wasActive)
|
||
{
|
||
Cursor.lockState = CursorLockMode.None;
|
||
InventoryEvents.Instance?.OnInventoryOpened?.Invoke();
|
||
}
|
||
else
|
||
{
|
||
Cursor.lockState = CursorLockMode.Locked;
|
||
InventoryEvents.Instance?.OnInventoryClosed?.Invoke();
|
||
}
|
||
}
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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;
|
||
}
|
||
} |