fack
This commit is contained in:
601
Assets/Inventory/Scripts/InventoryManager.cs
Normal file
601
Assets/Inventory/Scripts/InventoryManager.cs
Normal file
@@ -0,0 +1,601 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user