fack
This commit is contained in:
202
Assets/Inventory/Scripts/CraftingManager.cs
Normal file
202
Assets/Inventory/Scripts/CraftingManager.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class CraftingManager : MonoBehaviour
|
||||
{
|
||||
public static CraftingManager Instance;
|
||||
|
||||
[Header("UI References")]
|
||||
public GameObject craftingPanel;
|
||||
public Slot[] inputSlots; // 9 слотов для входа (как в крафтинге)
|
||||
public Slot outputSlot; // 1 слот для результата
|
||||
public Text recipeNameText;
|
||||
public Button craftButton;
|
||||
|
||||
[Header("Recipe Database")]
|
||||
public List<CraftingRecipe> recipes;
|
||||
|
||||
private Dictionary<string, CraftingRecipe> _recipeDict;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
Instance = this;
|
||||
else
|
||||
Destroy(gameObject);
|
||||
|
||||
_recipeDict = new Dictionary<string, CraftingRecipe>();
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
string key = GetRecipeKey(recipe);
|
||||
_recipeDict[key] = recipe;
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (craftingPanel != null)
|
||||
craftingPanel.SetActive(false);
|
||||
|
||||
if (craftButton != null)
|
||||
craftButton.onClick.AddListener(Craft);
|
||||
}
|
||||
|
||||
public void ToggleCrafting()
|
||||
{
|
||||
if (craftingPanel != null)
|
||||
{
|
||||
craftingPanel.SetActive(!craftingPanel.activeSelf);
|
||||
if (craftingPanel.activeSelf)
|
||||
CheckRecipe();
|
||||
}
|
||||
}
|
||||
|
||||
// Собираем ключ из текущих ингредиентов в слотах
|
||||
string GetCurrentIngredientsKey()
|
||||
{
|
||||
var ingredients = new List<string>();
|
||||
foreach (var slot in inputSlots)
|
||||
{
|
||||
if (!slot.IsEmpty)
|
||||
{
|
||||
ingredients.Add(slot.item.id);
|
||||
}
|
||||
}
|
||||
ingredients.Sort();
|
||||
return string.Join(",", ingredients);
|
||||
}
|
||||
|
||||
// Проверяем рецепт
|
||||
public void CheckRecipe()
|
||||
{
|
||||
string key = GetCurrentIngredientsKey();
|
||||
|
||||
if (_recipeDict.TryGetValue(key, out CraftingRecipe recipe))
|
||||
{
|
||||
// Проверяем хватает ли количества
|
||||
bool canCraft = true;
|
||||
int slotIndex = 0;
|
||||
|
||||
foreach (var ingredientId in recipe.ingredients)
|
||||
{
|
||||
int required = recipe.amounts[System.Array.IndexOf(recipe.ingredients, ingredientId)];
|
||||
int have = 0;
|
||||
|
||||
for (int i = 0; i < inputSlots.Length; i++)
|
||||
{
|
||||
if (!inputSlots[i].IsEmpty && inputSlots[i].item.id == ingredientId)
|
||||
{
|
||||
have += inputSlots[i].amount;
|
||||
}
|
||||
}
|
||||
|
||||
if (have < required)
|
||||
{
|
||||
canCraft = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (canCraft)
|
||||
{
|
||||
// Показываем результат
|
||||
if (InventoryManager.Instance.itemDatabase.Find(x => x.id == recipe.resultId) is ItemData resultItem)
|
||||
{
|
||||
outputSlot.SetItem(resultItem, recipe.resultAmount);
|
||||
if (recipeNameText != null)
|
||||
recipeNameText.text = resultItem.itemName;
|
||||
|
||||
if (craftButton != null)
|
||||
craftButton.interactable = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outputSlot.Clear();
|
||||
if (recipeNameText != null)
|
||||
recipeNameText.text = "Unknown recipe";
|
||||
if (craftButton != null)
|
||||
craftButton.interactable = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
outputSlot.Clear();
|
||||
if (recipeNameText != null)
|
||||
recipeNameText.text = "";
|
||||
if (craftButton != null)
|
||||
craftButton.interactable = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Выполнить крафтинг
|
||||
public void Craft()
|
||||
{
|
||||
string key = GetCurrentIngredientsKey();
|
||||
|
||||
if (_recipeDict.TryGetValue(key, out CraftingRecipe recipe))
|
||||
{
|
||||
// Удаляем ингредиенты
|
||||
foreach (var ingredientId in recipe.ingredients)
|
||||
{
|
||||
int required = recipe.amounts[System.Array.IndexOf(recipe.ingredients, ingredientId)];
|
||||
InventoryManager.Instance.RemoveItem(ingredientId, required);
|
||||
}
|
||||
|
||||
// Добавляем результат
|
||||
int remaining = InventoryManager.Instance.AddItem(recipe.resultId, recipe.resultAmount);
|
||||
if (remaining > 0)
|
||||
{
|
||||
Debug.LogWarning($"Inventory full! Couldn't add {remaining} items.");
|
||||
}
|
||||
|
||||
// Очищаем слоты ввода
|
||||
foreach (var slot in inputSlots)
|
||||
{
|
||||
slot.Clear();
|
||||
}
|
||||
|
||||
// Пересматриваем рецепт
|
||||
CheckRecipe();
|
||||
|
||||
// Обновляем инвентарь
|
||||
foreach (var slot in InventoryManager.Instance.slots)
|
||||
{
|
||||
slot.UpdateSlotUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Добавить рецепт
|
||||
public void AddRecipe(string resultId, int resultAmount, string[] ingredients, int[] amounts)
|
||||
{
|
||||
var recipe = new CraftingRecipe
|
||||
{
|
||||
resultId = resultId,
|
||||
resultAmount = resultAmount,
|
||||
ingredients = ingredients,
|
||||
amounts = amounts
|
||||
};
|
||||
|
||||
recipes.Add(recipe);
|
||||
string key = GetRecipeKey(recipe);
|
||||
_recipeDict[key] = recipe;
|
||||
}
|
||||
|
||||
string GetRecipeKey(CraftingRecipe recipe)
|
||||
{
|
||||
var sorted = new List<string>(recipe.ingredients);
|
||||
sorted.Sort();
|
||||
return string.Join(",", sorted);
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class CraftingRecipe
|
||||
{
|
||||
public string resultId;
|
||||
public int resultAmount = 1;
|
||||
public string[] ingredients;
|
||||
public int[] amounts;
|
||||
}
|
||||
}
|
||||
2
Assets/Inventory/Scripts/CraftingManager.cs.meta
Normal file
2
Assets/Inventory/Scripts/CraftingManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3c944af921361347b5bcb564f23e76d
|
||||
236
Assets/Inventory/Scripts/InventoryDragDrop.cs
Normal file
236
Assets/Inventory/Scripts/InventoryDragDrop.cs
Normal file
@@ -0,0 +1,236 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Обработка Drag & Drop предметов между слотами и выброс за пределы инвентаря
|
||||
/// </summary>
|
||||
public class InventoryDragDrop : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IPointerEnterHandler, IPointerExitHandler
|
||||
{
|
||||
public Slot slot;
|
||||
public int slotIndex;
|
||||
|
||||
[Header("Drag Visual")]
|
||||
[SerializeField] private RectTransform dragIcon;
|
||||
[SerializeField] private Image dragImage;
|
||||
[SerializeField] private float dragIconScale = 1.2f;
|
||||
|
||||
[Header("Drop Settings")]
|
||||
[SerializeField] private bool allowDropOutside = true;
|
||||
[SerializeField] private float dropThreshold = 100f; // пикселей за пределами инвентаря
|
||||
|
||||
private Canvas _canvas;
|
||||
private CanvasGroup _canvasGroup;
|
||||
private bool _isDragging;
|
||||
private Vector2 _dragStartPosition;
|
||||
private RectTransform _inventoryPanelRect;
|
||||
|
||||
void Start()
|
||||
{
|
||||
_canvas = GetComponentInParent<Canvas>();
|
||||
_canvasGroup = GetComponent<CanvasGroup>();
|
||||
|
||||
if (dragIcon == null)
|
||||
{
|
||||
CreateDragIcon();
|
||||
}
|
||||
|
||||
// Находим панель инвентаря
|
||||
var inventoryManager = FindObjectOfType<InventoryManager>();
|
||||
if (inventoryManager?.inventoryPanel != null)
|
||||
{
|
||||
_inventoryPanelRect = inventoryManager.inventoryPanel.GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
if (dragIcon != null)
|
||||
dragIcon.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
private void CreateDragIcon()
|
||||
{
|
||||
GameObject canvasGO = new GameObject("DragIconCanvas");
|
||||
canvasGO.transform.SetParent(_canvas != null ? _canvas.transform : transform.root);
|
||||
|
||||
var dragCanvas = canvasGO.AddComponent<Canvas>();
|
||||
dragCanvas.overrideSorting = true;
|
||||
dragCanvas.sortingOrder = 1000;
|
||||
dragCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvasGO.AddComponent<GraphicRaycaster>();
|
||||
|
||||
dragIcon = new GameObject("DragIcon").AddComponent<RectTransform>();
|
||||
dragIcon.SetParent(canvasGO.transform, false);
|
||||
dragIcon.sizeDelta = new Vector2(48, 48);
|
||||
|
||||
dragImage = dragIcon.gameObject.AddComponent<Image>();
|
||||
dragImage.raycastTarget = false;
|
||||
|
||||
dragIcon.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (slot == null || slot.IsEmpty) return;
|
||||
|
||||
_isDragging = true;
|
||||
_dragStartPosition = eventData.position;
|
||||
|
||||
if (_canvasGroup != null)
|
||||
_canvasGroup.blocksRaycasts = false;
|
||||
|
||||
if (dragIcon != null && slot.item?.icon != null)
|
||||
{
|
||||
dragIcon.gameObject.SetActive(true);
|
||||
dragImage.sprite = slot.item.icon;
|
||||
dragIcon.localScale = Vector3.one * dragIconScale;
|
||||
dragIcon.position = eventData.position;
|
||||
}
|
||||
|
||||
var bg = GetComponent<Image>();
|
||||
if (bg != null)
|
||||
bg.color = new Color(1f, 1f, 1f, 0.5f);
|
||||
}
|
||||
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!_isDragging || dragIcon == null) return;
|
||||
dragIcon.position = eventData.position;
|
||||
}
|
||||
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!_isDragging) return;
|
||||
|
||||
_isDragging = false;
|
||||
|
||||
if (dragIcon != null)
|
||||
{
|
||||
dragIcon.gameObject.SetActive(false);
|
||||
dragIcon.localScale = Vector3.one;
|
||||
}
|
||||
|
||||
if (_canvasGroup != null)
|
||||
_canvasGroup.blocksRaycasts = true;
|
||||
|
||||
var bg = GetComponent<Image>();
|
||||
if (bg != null)
|
||||
bg.color = slot.IsEmpty ? new Color(0.25f, 0.25f, 0.25f, 0.8f) : new Color(0.35f, 0.35f, 0.35f, 0.95f);
|
||||
|
||||
// Проверяем - выбросили за пределы инвентаря?
|
||||
if (allowDropOutside && IsOutsideInventory(eventData.position))
|
||||
{
|
||||
DropItemOutside();
|
||||
return;
|
||||
}
|
||||
|
||||
// Иначе - пробуем переместить в другой слот
|
||||
var raycastResults = new System.Collections.Generic.List<RaycastResult>();
|
||||
EventSystem.current.RaycastAll(eventData, raycastResults);
|
||||
|
||||
Slot targetSlot = null;
|
||||
|
||||
foreach (var result in raycastResults)
|
||||
{
|
||||
var slotComponent = result.gameObject.GetComponent<Slot>();
|
||||
if (slotComponent != null && slotComponent != slot)
|
||||
{
|
||||
targetSlot = slotComponent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetSlot != null && InventoryManager.Instance != null)
|
||||
{
|
||||
InventoryManager.Instance.SwapSlots(slotIndex, targetSlot.slotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsOutsideInventory(Vector2 screenPosition)
|
||||
{
|
||||
if (_inventoryPanelRect == null) return false;
|
||||
|
||||
// Получаем границы панели инвентаря в экранных координатах
|
||||
Vector3[] corners = new Vector3[4];
|
||||
_inventoryPanelRect.GetWorldCorners(corners);
|
||||
|
||||
// rect панели в экранных координатах
|
||||
float minX = corners[0].x;
|
||||
float maxX = corners[2].x;
|
||||
float minY = corners[0].y;
|
||||
float maxY = corners[2].y;
|
||||
|
||||
// Добавляем отступ
|
||||
float threshold = dropThreshold;
|
||||
|
||||
// Проверяем находится ли курсор за пределами панели + threshold
|
||||
bool outside = screenPosition.x < minX - threshold ||
|
||||
screenPosition.x > maxX + threshold ||
|
||||
screenPosition.y < minY - threshold ||
|
||||
screenPosition.y > maxY + threshold;
|
||||
|
||||
return outside;
|
||||
}
|
||||
|
||||
private void DropItemOutside()
|
||||
{
|
||||
if (slot == null || slot.IsEmpty) return;
|
||||
|
||||
var item = slot.item;
|
||||
int amount = slot.amount;
|
||||
|
||||
// Определяем позицию выброса
|
||||
Vector3 dropPos = Vector3.zero;
|
||||
if (Camera.main != null)
|
||||
{
|
||||
// Выбрасываем перед игроком
|
||||
dropPos = Camera.main.transform.position + Camera.main.transform.forward * 2f;
|
||||
}
|
||||
|
||||
// Создаём предмет в мире
|
||||
if (InventoryItemFactory.Instance != null)
|
||||
{
|
||||
InventoryItemFactory.Instance.DropItem(item, amount, dropPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback - просто создаём объект
|
||||
var go = new GameObject($"Dropped_{item.itemName}");
|
||||
go.transform.position = dropPos;
|
||||
|
||||
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
visual.transform.SetParent(go.transform);
|
||||
visual.transform.localPosition = Vector3.zero;
|
||||
visual.transform.localScale = Vector3.one * 0.3f;
|
||||
DestroyImmediate(visual.GetComponent<Collider>());
|
||||
|
||||
var worldItem = go.AddComponent<WorldItem>();
|
||||
worldItem.Setup(item, amount);
|
||||
}
|
||||
|
||||
// Очищаем слот
|
||||
slot.Clear();
|
||||
|
||||
Debug.Log($"[DragDrop] Выброшен: {item.itemName} x{amount}");
|
||||
}
|
||||
|
||||
public void OnPointerEnter(PointerEventData eventData)
|
||||
{
|
||||
if (!_isDragging && slot != null && !slot.IsEmpty)
|
||||
{
|
||||
var bg = GetComponent<Image>();
|
||||
if (bg != null)
|
||||
bg.color = new Color(0.5f, 0.5f, 0.5f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPointerExit(PointerEventData eventData)
|
||||
{
|
||||
if (!_isDragging && slot != null)
|
||||
{
|
||||
var bg = GetComponent<Image>();
|
||||
if (bg != null)
|
||||
bg.color = slot.IsEmpty ?
|
||||
new Color(0.25f, 0.25f, 0.25f, 0.8f) :
|
||||
new Color(0.35f, 0.35f, 0.35f, 0.95f);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Inventory/Scripts/InventoryDragDrop.cs.meta
Normal file
2
Assets/Inventory/Scripts/InventoryDragDrop.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3bc6c48bea19c74ea4d056cb2471292
|
||||
66
Assets/Inventory/Scripts/InventoryEvents.cs
Normal file
66
Assets/Inventory/Scripts/InventoryEvents.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
/// <summary>
|
||||
/// События инвентаря - используй UnityEvent для подписки из любого скрипта
|
||||
/// </summary>
|
||||
public class InventoryEvents : MonoBehaviour
|
||||
{
|
||||
public static InventoryEvents Instance { get; private set; }
|
||||
|
||||
[Header("Item Events")]
|
||||
/// <param name="itemId">ID предмета</param>
|
||||
/// <param name="amount">Количество</param>
|
||||
public UnityEvent<string, int> OnItemAdded;
|
||||
|
||||
/// <param name="itemId">ID предмета</param>
|
||||
/// <param name="amount">Количество</param>
|
||||
public UnityEvent<string, int> OnItemRemoved;
|
||||
|
||||
/// <param name="itemId">ID предмета</param>
|
||||
/// <param name="amount">Новое количество</param>
|
||||
public UnityEvent<string, int> OnItemCountChanged;
|
||||
|
||||
[Header("Slot Events")]
|
||||
/// <param name="slotIndex">Индекс слота</param>
|
||||
/// <param name="itemId">ID предмета (null если пусто)</param>
|
||||
public UnityEvent<int, string> OnSlotChanged;
|
||||
|
||||
[Header("Inventory State")]
|
||||
public UnityEvent OnInventoryOpened;
|
||||
public UnityEvent OnInventoryClosed;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// Удобные методы для вызова событий
|
||||
public void InvokeItemAdded(string itemId, int amount)
|
||||
{
|
||||
OnItemAdded?.Invoke(itemId, amount);
|
||||
}
|
||||
|
||||
public void InvokeItemRemoved(string itemId, int amount)
|
||||
{
|
||||
OnItemRemoved?.Invoke(itemId, amount);
|
||||
}
|
||||
|
||||
public void InvokeItemCountChanged(string itemId, int newAmount)
|
||||
{
|
||||
OnItemCountChanged?.Invoke(itemId, newAmount);
|
||||
}
|
||||
|
||||
public void InvokeSlotChanged(int slotIndex, string itemId)
|
||||
{
|
||||
OnSlotChanged?.Invoke(slotIndex, itemId);
|
||||
}
|
||||
}
|
||||
2
Assets/Inventory/Scripts/InventoryEvents.cs.meta
Normal file
2
Assets/Inventory/Scripts/InventoryEvents.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 92259c03301739842b087d3e9d9b0c5f
|
||||
113
Assets/Inventory/Scripts/InventoryExampleItems.cs
Normal file
113
Assets/Inventory/Scripts/InventoryExampleItems.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Пример конфигурации предметов
|
||||
/// </summary>
|
||||
public class InventoryExampleItems : MonoBehaviour
|
||||
{
|
||||
[Header("Item Database")]
|
||||
public List<ItemData> items;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Создаём примеры предметов если база пустая
|
||||
if (items == null || items.Count == 0)
|
||||
{
|
||||
items = CreateExampleItems();
|
||||
}
|
||||
|
||||
// Передаём в InventoryManager
|
||||
var im = FindObjectOfType<InventoryManager>();
|
||||
if (im != null)
|
||||
{
|
||||
im.itemDatabase = items;
|
||||
im.RebuildItemDictionary();
|
||||
|
||||
// Сразу добавляем тестовые предметы
|
||||
im.AddItem("stone", 32);
|
||||
im.AddItem("dirt", 16);
|
||||
im.AddItem("wood", 8);
|
||||
im.AddItem("apple", 5);
|
||||
im.AddItem("sword", 1);
|
||||
im.AddItem("pickaxe", 1);
|
||||
|
||||
Debug.Log("[InventoryExampleItems] Added test items");
|
||||
}
|
||||
}
|
||||
|
||||
List<ItemData> CreateExampleItems()
|
||||
{
|
||||
return new List<ItemData>
|
||||
{
|
||||
CreateItem("stone", "Камень", "Обычный камень", ItemType.Block, 64),
|
||||
CreateItem("dirt", "Земля", "Блок земли", ItemType.Block, 64),
|
||||
CreateItem("wood", "Дерево", "Деревянный блок", ItemType.Block, 64),
|
||||
CreateItem("plank", "Доски", "Деревянные доски", ItemType.Block, 64),
|
||||
CreateItem("sword", "Меч", "Острый меч", ItemType.Weapon, 1),
|
||||
CreateItem("apple", "Яблоко", "Вкусное яблоко", ItemType.Consumable, 16),
|
||||
CreateItem("pickaxe", "Кирка", "Инструмент для добычи", ItemType.Tool, 1),
|
||||
};
|
||||
}
|
||||
|
||||
ItemData CreateItem(string id, string name, string desc, ItemType type, int stack)
|
||||
{
|
||||
var item = ScriptableObject.CreateInstance<ItemData>();
|
||||
item.id = id;
|
||||
item.itemName = name;
|
||||
item.description = desc;
|
||||
item.type = type;
|
||||
item.maxStack = stack;
|
||||
item.icon = CreatePlaceholderIcon(name);
|
||||
return item;
|
||||
}
|
||||
|
||||
Sprite CreatePlaceholderIcon(string text)
|
||||
{
|
||||
// Создаём текстуру с текстом
|
||||
var tex = new Texture2D(64, 64);
|
||||
var colors = new Color[64 * 64];
|
||||
|
||||
// Заливаем цветом (серый фон)
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
colors[i] = new Color(0.5f, 0.5f, 0.5f, 1f);
|
||||
}
|
||||
|
||||
tex.SetPixels(colors);
|
||||
tex.Apply();
|
||||
|
||||
// Создаём спрайт
|
||||
return Sprite.Create(tex, new Rect(0, 0, 64, 64), new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет предметы в инвентарь при старте (для тестирования)
|
||||
/// </summary>
|
||||
public class InventoryTester : MonoBehaviour
|
||||
{
|
||||
void Start()
|
||||
{
|
||||
// Ждём кадр чтобы InventoryManager точно инициализировался
|
||||
StartCoroutine(AddTestItems());
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator AddTestItems()
|
||||
{
|
||||
yield return null; // ждём один кадр
|
||||
|
||||
// Добавляем тестовые предметы
|
||||
var im = InventoryManager.Instance;
|
||||
if (im != null && im.GetItemCount("stone") == 0) // только если пусто
|
||||
{
|
||||
Debug.Log("[InventoryTester] Adding test items...");
|
||||
im.AddItem("stone", 32);
|
||||
im.AddItem("dirt", 16);
|
||||
im.AddItem("wood", 8);
|
||||
im.AddItem("apple", 5);
|
||||
im.AddItem("sword", 1);
|
||||
im.AddItem("pickaxe", 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Inventory/Scripts/InventoryExampleItems.cs.meta
Normal file
2
Assets/Inventory/Scripts/InventoryExampleItems.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad1628cce096628409729c00123006be
|
||||
271
Assets/Inventory/Scripts/InventoryItemFactory.cs
Normal file
271
Assets/Inventory/Scripts/InventoryItemFactory.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Конфигурация для фабрики предметов - создать в Assets > Create > Inventory > Item Factory Config
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "ItemFactoryConfig", menuName = "Inventory/Item Factory Config")]
|
||||
public class ItemFactoryConfig : ScriptableObject
|
||||
{
|
||||
[Header("World Item Prefabs")]
|
||||
public GameObject worldItemPrefab; // Префаб предмета в мире
|
||||
|
||||
[Header("Spawner Prefab")]
|
||||
public GameObject itemSpawnerPrefab; // Префаб спавнера
|
||||
|
||||
[Header("Pickup Settings")]
|
||||
public float pickupRadius = 1.5f;
|
||||
public float despawnTime = 300f; // 5 минут
|
||||
|
||||
[Header("Drop Settings")]
|
||||
public float dropForce = 2f;
|
||||
public float dropRandomness = 0.5f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Фабрика для создания префабов предметов - заменяет прямой Instantiate и Resources.Load
|
||||
/// </summary>
|
||||
public class InventoryItemFactory : MonoBehaviour
|
||||
{
|
||||
public static InventoryItemFactory Instance { get; private set; }
|
||||
|
||||
[Header("Configuration")]
|
||||
[Tooltip("Создать в Assets > Create > Inventory > Item Factory Config")]
|
||||
public ItemFactoryConfig config;
|
||||
|
||||
[Header("Debug")]
|
||||
public bool logSpawns = false;
|
||||
|
||||
// Кэшированные ссылки для быстрого доступа
|
||||
private Dictionary<string, GameObject> _prefabCache = new Dictionary<string, GameObject>();
|
||||
|
||||
void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создать предмет в мире (WorldItem)
|
||||
/// </summary>
|
||||
/// <param name="itemData">Данные предмета</param>
|
||||
/// <param name="amount">Количество</param>
|
||||
/// <param name="position">Позиция спавна</param>
|
||||
/// <param name="rotation"> rotation ( Quaternion.identity по умолчанию)</param>
|
||||
public WorldItem CreateWorldItem(ItemData itemData, int amount, Vector3 position, Quaternion rotation = default)
|
||||
{
|
||||
GameObject go;
|
||||
|
||||
if (config == null || config.worldItemPrefab == null)
|
||||
{
|
||||
// Fallback - создаём без префаба
|
||||
go = new GameObject($"WorldItem_{itemData.itemName}");
|
||||
go.transform.position = position;
|
||||
go.transform.rotation = rotation;
|
||||
|
||||
// Добавляем компоненты для физики
|
||||
var collider = go.AddComponent<SphereCollider>();
|
||||
collider.radius = 0.3f;
|
||||
collider.isTrigger = true;
|
||||
var rb = go.AddComponent<Rigidbody>();
|
||||
rb.isKinematic = true;
|
||||
rb.useGravity = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
go = Instantiate(config.worldItemPrefab, position, rotation);
|
||||
}
|
||||
var worldItem = go.GetComponent<WorldItem>();
|
||||
|
||||
if (worldItem == null)
|
||||
worldItem = go.AddComponent<WorldItem>();
|
||||
|
||||
worldItem.Setup(itemData, amount);
|
||||
|
||||
if (logSpawns)
|
||||
Debug.Log($"[Factory] Created WorldItem: {itemData.itemName} x{amount} at {position}");
|
||||
|
||||
return worldItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создать предмет в мире с настраиваемым временем жизни
|
||||
/// </summary>
|
||||
public WorldItem CreateWorldItem(ItemData itemData, int amount, Vector3 position, float lifetime, Quaternion rotation = default)
|
||||
{
|
||||
var worldItem = CreateWorldItem(itemData, amount, position, rotation);
|
||||
if (worldItem != null && lifetime > 0)
|
||||
{
|
||||
Destroy(worldItem.gameObject, lifetime);
|
||||
}
|
||||
return worldItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создать спавнер предмета (точка появления)
|
||||
/// </summary>
|
||||
public ItemSpawner CreateItemSpawner(ItemData itemData, int amount, Vector3 position)
|
||||
{
|
||||
if (config == null || config.itemSpawnerPrefab == null)
|
||||
{
|
||||
// Создаём базовый спавнер без префаба
|
||||
return CreateSpawnerFallback(itemData, amount, position);
|
||||
}
|
||||
|
||||
var go = Instantiate(config.itemSpawnerPrefab, position, Quaternion.identity);
|
||||
var spawner = go.GetComponent<ItemSpawner>();
|
||||
|
||||
if (spawner == null)
|
||||
spawner = go.AddComponent<ItemSpawner>();
|
||||
|
||||
spawner.Setup(itemData, amount, config.despawnTime);
|
||||
|
||||
if (logSpawns)
|
||||
Debug.Log($"[Factory] Created Spawner: {itemData.itemName} x{amount}");
|
||||
|
||||
return spawner;
|
||||
}
|
||||
|
||||
private ItemSpawner CreateSpawnerFallback(ItemData itemData, int amount, Vector3 position)
|
||||
{
|
||||
var go = new GameObject($"Spawner_{itemData.itemName}");
|
||||
go.transform.position = position;
|
||||
var spawner = go.AddComponent<ItemSpawner>();
|
||||
spawner.Setup(itemData, amount, config != null ? config.despawnTime : 300f);
|
||||
return spawner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выбросить предмет из инвентаря (drop)
|
||||
/// </summary>
|
||||
public WorldItem DropItem(ItemData itemData, int amount, Vector3 dropPosition, Vector3? velocity = null)
|
||||
{
|
||||
// Добавляем небольшое случайное смещение
|
||||
Vector3 offset = Vector3.zero;
|
||||
if (config != null)
|
||||
{
|
||||
offset = new Vector3(
|
||||
Random.Range(-config.dropRandomness, config.dropRandomness),
|
||||
0.5f,
|
||||
Random.Range(-config.dropRandomness, config.dropRandomness)
|
||||
);
|
||||
}
|
||||
|
||||
Vector3 spawnPos = dropPosition + offset;
|
||||
Quaternion rotation = Quaternion.Euler(
|
||||
Random.Range(0, 360),
|
||||
Random.Range(0, 360),
|
||||
Random.Range(0, 360)
|
||||
);
|
||||
|
||||
var worldItem = CreateWorldItem(itemData, amount, spawnPos, rotation);
|
||||
|
||||
// Добавляем начальную скорость (для выброса из инвентаря)
|
||||
if (velocity.HasValue && worldItem != null)
|
||||
{
|
||||
var rb = worldItem.GetComponent<Rigidbody>();
|
||||
if (rb == null)
|
||||
rb = worldItem.gameObject.AddComponent<Rigidbody>();
|
||||
|
||||
rb.linearVelocity = velocity.Value + (Vector3.up * (config?.dropForce ?? 2f));
|
||||
}
|
||||
|
||||
return worldItem;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Предмет подбирается игроком - вызывается из WorldItem
|
||||
/// </summary>
|
||||
public bool PickupItem(WorldItem worldItem, MonoBehaviour picker)
|
||||
{
|
||||
if (worldItem == null || worldItem.itemData == null)
|
||||
return false;
|
||||
|
||||
int remaining = InventoryManager.Instance.AddItem(worldItem.itemData, worldItem.amount);
|
||||
|
||||
if (remaining <= 0)
|
||||
{
|
||||
if (logSpawns)
|
||||
Debug.Log($"[Factory] Picked up: {worldItem.itemData.itemName} x{worldItem.amount}");
|
||||
Destroy(worldItem.gameObject);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Частичный подбор
|
||||
worldItem.amount = remaining;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создать префаб из GameObject в рантайме (динамические предметы)
|
||||
/// </summary>
|
||||
public GameObject CreateItemModel(GameObject modelPrefab, Transform parent)
|
||||
{
|
||||
if (modelPrefab == null)
|
||||
return null;
|
||||
|
||||
return Instantiate(modelPrefab, parent);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Кастомный редактор для быстрой настройки фабрики
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(InventoryItemFactory))]
|
||||
public class InventoryItemFactoryEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
var factory = (InventoryItemFactory)target;
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (factory.config == null)
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
"Создайте ItemFactoryConfig: Assets > Create > Inventory > Item Factory Config",
|
||||
MessageType.Warning);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Создать ItemFactoryConfig"))
|
||||
{
|
||||
var config = ScriptableObject.CreateInstance<ItemFactoryConfig>();
|
||||
AssetDatabase.CreateAsset(config, "Assets/Inventory/Configs/ItemFactoryConfig.asset");
|
||||
AssetDatabase.SaveAssets();
|
||||
factory.config = config;
|
||||
Debug.Log("Создан ItemFactoryConfig");
|
||||
}
|
||||
|
||||
GUILayout.Space(5);
|
||||
|
||||
if (GUILayout.Button("Создать тестовый предмет в сцене"))
|
||||
{
|
||||
if (InventoryManager.Instance?.itemDatabase?.Count > 0)
|
||||
{
|
||||
var item = InventoryManager.Instance.itemDatabase[0];
|
||||
factory.CreateWorldItem(item, 5, Vector3.zero + Vector3.forward * 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Сначала добавьте предметы в InventoryManager");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
2
Assets/Inventory/Scripts/InventoryItemFactory.cs.meta
Normal file
2
Assets/Inventory/Scripts/InventoryItemFactory.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e734a4abaff54d45b8294cb6e023bea
|
||||
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;
|
||||
}
|
||||
}
|
||||
2
Assets/Inventory/Scripts/InventoryManager.cs.meta
Normal file
2
Assets/Inventory/Scripts/InventoryManager.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4c4603259067dc4e96f741653843b92
|
||||
294
Assets/Inventory/Scripts/InventorySceneSetup.cs
Normal file
294
Assets/Inventory/Scripts/InventorySceneSetup.cs
Normal file
@@ -0,0 +1,294 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
public class InventorySceneSetup
|
||||
{
|
||||
[MenuItem("Tools/Create Player")]
|
||||
public static void CreatePlayer()
|
||||
{
|
||||
var go = new GameObject("Player");
|
||||
|
||||
var cc = go.AddComponent<CharacterController>();
|
||||
cc.height = 1.8f;
|
||||
cc.radius = 0.5f;
|
||||
cc.center = new Vector3(0, 0.9f, 0);
|
||||
|
||||
var character = go.AddComponent<charecter>();
|
||||
character.walkSpeed = 5f;
|
||||
character.runSpeed = 9f;
|
||||
character.crouchSpeed = 2.5f;
|
||||
character.jumpHeight = 1.5f;
|
||||
character.gravity = -9.81f;
|
||||
|
||||
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
|
||||
body.name = "Body";
|
||||
body.transform.SetParent(go.transform);
|
||||
body.transform.localPosition = Vector3.zero;
|
||||
GameObject.Destroy(body.GetComponent<Collider>());
|
||||
|
||||
GameObject head = new GameObject("Head");
|
||||
head.transform.SetParent(go.transform);
|
||||
head.transform.localPosition = new Vector3(0, 0.8f, 0);
|
||||
character.head = head.transform;
|
||||
|
||||
GameObject camObj = new GameObject("PlayerCamera");
|
||||
camObj.transform.SetParent(head.transform);
|
||||
camObj.transform.localPosition = new Vector3(0, 0.1f, 0);
|
||||
var cam = camObj.AddComponent<Camera>();
|
||||
cam.tag = "MainCamera";
|
||||
|
||||
var existingCam = GameObject.FindGameObjectWithTag("MainCamera");
|
||||
if (existingCam != null && existingCam != camObj)
|
||||
{
|
||||
GameObject.Destroy(existingCam);
|
||||
}
|
||||
|
||||
go.tag = "Player";
|
||||
go.transform.position = new Vector3(0, 2, -5);
|
||||
|
||||
Selection.activeGameObject = go;
|
||||
Debug.Log("[Tools] Создан игрок!");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Create Item Prefab")]
|
||||
public static void CreateItemPrefab()
|
||||
{
|
||||
var itemGO = new GameObject("NewItem");
|
||||
itemGO.AddComponent<WorldItem>();
|
||||
|
||||
var sphere = itemGO.AddComponent<SphereCollider>();
|
||||
sphere.radius = 0.5f;
|
||||
sphere.isTrigger = true;
|
||||
|
||||
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
visual.name = "Visual";
|
||||
visual.transform.SetParent(itemGO.transform);
|
||||
visual.transform.localPosition = Vector3.zero;
|
||||
visual.transform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
|
||||
GameObject.Destroy(visual.GetComponent<Collider>());
|
||||
|
||||
Selection.activeGameObject = itemGO;
|
||||
Debug.Log("[Tools] Создан префаб предмета!");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Create Test Item in Scene")]
|
||||
public static void CreateTestItem()
|
||||
{
|
||||
// Создаём тестовый предмет в сцене
|
||||
var itemGO = new GameObject("TestItem");
|
||||
itemGO.transform.position = new Vector3(0, 0.5f, 3);
|
||||
|
||||
// Визуал
|
||||
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
visual.name = "Visual";
|
||||
visual.transform.SetParent(itemGO.transform);
|
||||
visual.transform.localPosition = Vector3.zero;
|
||||
visual.transform.localScale = Vector3.one * 0.3f;
|
||||
GameObject.Destroy(visual.GetComponent<Collider>());
|
||||
|
||||
// Компонент предмета
|
||||
var worldItem = itemGO.AddComponent<WorldItem>();
|
||||
worldItem.pickupDelay = 0f; // мгновенный подбор
|
||||
worldItem.useFactory = false;
|
||||
|
||||
// Тестовые данные
|
||||
var testItem = ScriptableObject.CreateInstance<ItemData>();
|
||||
testItem.id = "test_item";
|
||||
testItem.itemName = "Тестовый предмет";
|
||||
testItem.description = "Просто тестовый предмет";
|
||||
testItem.type = ItemType.Material;
|
||||
testItem.maxStack = 64;
|
||||
|
||||
worldItem.itemData = testItem;
|
||||
worldItem.amount = 5;
|
||||
|
||||
// Коллайдер для подбора (trigger для обычных коллайдеров)
|
||||
var box = itemGO.AddComponent<BoxCollider>();
|
||||
box.isTrigger = true;
|
||||
box.size = Vector3.one;
|
||||
|
||||
Selection.activeGameObject = itemGO;
|
||||
Debug.Log("[Tools] Создан тестовый предмет! Подойдите к нему игроком.");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Setup Inventory Scene")]
|
||||
public static void SetupScene()
|
||||
{
|
||||
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
||||
var rootObjects = scene.GetRootGameObjects();
|
||||
|
||||
foreach (var root in rootObjects)
|
||||
{
|
||||
if (root.GetComponent<InventoryManager>() != null)
|
||||
{
|
||||
Debug.LogWarning("InventoryManager уже существует!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Создаём GameManager
|
||||
var gameManager = new GameObject("GameManager");
|
||||
var im = gameManager.AddComponent<InventoryManager>();
|
||||
gameManager.AddComponent<InventoryExampleItems>();
|
||||
gameManager.AddComponent<InventoryTester>();
|
||||
|
||||
// Создаём Canvas
|
||||
var canvasGO = new GameObject("InventoryCanvas");
|
||||
var canvas = canvasGO.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 100;
|
||||
canvasGO.AddComponent<GraphicRaycaster>();
|
||||
|
||||
// Создаём панель инвентаря
|
||||
var inventoryPanel = CreatePanel(canvasGO.transform, "InventoryPanel", new Color(0.15f, 0.15f, 0.15f, 0.95f));
|
||||
var inventoryRect = inventoryPanel.GetComponent<RectTransform>();
|
||||
SetRect(inventoryRect, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f), Vector2.zero, new Vector2(500, 300));
|
||||
|
||||
// Заголовок
|
||||
CreateTitle(inventoryPanel.transform, "Инвентарь", new Vector2(0, 130));
|
||||
|
||||
// Сетка инвентаря (4x9 = 36 слотов)
|
||||
var inventoryGrid = CreateGrid(inventoryPanel.transform, "InventoryGrid", 9, 4, 50, 4, new Vector2(10, 40));
|
||||
im.slots = CreateSlots(inventoryGrid, "InventorySlot_", 0, 36);
|
||||
|
||||
// Создаём хотбар
|
||||
var hotbarPanel = CreatePanel(canvasGO.transform, "HotbarPanel", new Color(0.1f, 0.1f, 0.1f, 0.9f));
|
||||
var hotbarRect = hotbarPanel.GetComponent<RectTransform>();
|
||||
SetRect(hotbarRect, new Vector2(0.5f, 0f), new Vector2(0.5f, 0f), new Vector2(0, 15), new Vector2(460, 60));
|
||||
|
||||
var hotbarGrid = CreateGrid(hotbarPanel.transform, "HotbarGrid", 9, 1, 50, 4, new Vector2(10, 10));
|
||||
im.hotbarSlots = CreateSlots(hotbarGrid, "HotbarSlot_", 0, 9);
|
||||
|
||||
// Подключаем ссылку на панель
|
||||
im.inventoryPanel = inventoryPanel;
|
||||
im.inventoryCanvas = canvas;
|
||||
|
||||
// Скрываем панель
|
||||
inventoryPanel.SetActive(false);
|
||||
|
||||
Debug.Log("[Tools] Инвентарь настроен! (36 слотов + 9 хотбар)");
|
||||
}
|
||||
|
||||
static GameObject CreatePanel(Transform parent, string name, Color color)
|
||||
{
|
||||
var panel = new GameObject(name);
|
||||
panel.transform.SetParent(parent, false);
|
||||
panel.AddComponent<RectTransform>();
|
||||
panel.AddComponent<Image>().color = color;
|
||||
return panel;
|
||||
}
|
||||
|
||||
static void SetRect(RectTransform rect, Vector2 anchorMin, Vector2 anchorMax, Vector2 anchoredPosition, Vector2 sizeDelta)
|
||||
{
|
||||
rect.anchorMin = anchorMin;
|
||||
rect.anchorMax = anchorMax;
|
||||
rect.pivot = new Vector2(0.5f, 0.5f);
|
||||
rect.anchoredPosition = anchoredPosition;
|
||||
rect.sizeDelta = sizeDelta;
|
||||
}
|
||||
|
||||
static void CreateTitle(Transform parent, string text, Vector2 position)
|
||||
{
|
||||
var titleObj = new GameObject("Title");
|
||||
titleObj.transform.SetParent(parent, false);
|
||||
var rect = titleObj.AddComponent<RectTransform>();
|
||||
rect.anchorMin = new Vector2(0, 1);
|
||||
rect.anchorMax = new Vector2(1, 1);
|
||||
rect.pivot = new Vector2(0.5f, 1);
|
||||
rect.anchoredPosition = position;
|
||||
rect.sizeDelta = new Vector2(-20, 30);
|
||||
|
||||
var tmp = titleObj.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = text;
|
||||
tmp.fontSize = 18;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.color = Color.white;
|
||||
}
|
||||
|
||||
static Transform CreateGrid(Transform parent, string name, int cols, int rows, float cellSize, float spacing, Vector2 offset)
|
||||
{
|
||||
var gridObj = new GameObject(name);
|
||||
gridObj.transform.SetParent(parent, false);
|
||||
var rect = gridObj.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = offset;
|
||||
rect.offsetMax = -offset;
|
||||
|
||||
var grid = gridObj.AddComponent<GridLayoutGroup>();
|
||||
grid.cellSize = new Vector2(cellSize, cellSize);
|
||||
grid.spacing = new Vector2(spacing, spacing);
|
||||
grid.startCorner = GridLayoutGroup.Corner.UpperLeft;
|
||||
grid.startAxis = GridLayoutGroup.Axis.Horizontal;
|
||||
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||||
grid.constraintCount = cols;
|
||||
|
||||
return gridObj.transform;
|
||||
}
|
||||
|
||||
static Slot[] CreateSlots(Transform parent, string prefix, int startIndex, int count)
|
||||
{
|
||||
var slots = new Slot[count];
|
||||
var slotSize = 50f;
|
||||
var emptyColor = new Color(0.25f, 0.25f, 0.25f, 0.8f);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var slotObj = new GameObject(prefix + (startIndex + i));
|
||||
slotObj.transform.SetParent(parent, false);
|
||||
slotObj.AddComponent<RectTransform>().sizeDelta = new Vector2(slotSize, slotSize);
|
||||
|
||||
var bg = slotObj.AddComponent<Image>();
|
||||
bg.color = emptyColor;
|
||||
|
||||
var slot = slotObj.AddComponent<Slot>();
|
||||
slot.slotIndex = startIndex + i;
|
||||
|
||||
// Icon
|
||||
var iconObj = new GameObject("Icon");
|
||||
iconObj.transform.SetParent(slotObj.transform, false);
|
||||
var iconRect = iconObj.AddComponent<RectTransform>();
|
||||
iconRect.anchorMin = Vector2.zero;
|
||||
iconRect.anchorMax = Vector2.one;
|
||||
iconRect.offsetMin = new Vector2(4, 4);
|
||||
iconRect.offsetMax = new Vector2(-4, -4);
|
||||
slot.iconImage = iconObj.AddComponent<Image>();
|
||||
slot.iconImage.preserveAspect = true;
|
||||
slot.iconImage.enabled = false;
|
||||
|
||||
// Amount panel
|
||||
var amountObj = new GameObject("AmountPanel");
|
||||
amountObj.transform.SetParent(slotObj.transform, false);
|
||||
var amountRect = amountObj.AddComponent<RectTransform>();
|
||||
amountRect.anchorMin = Vector2.one;
|
||||
amountRect.anchorMax = Vector2.one;
|
||||
amountRect.pivot = new Vector2(1, 0);
|
||||
amountRect.anchoredPosition = new Vector2(-2, 2);
|
||||
amountRect.sizeDelta = new Vector2(22, 22);
|
||||
slot.amountPanel = amountObj;
|
||||
slot.amountPanel.SetActive(false);
|
||||
|
||||
amountObj.AddComponent<Image>().color = new Color(0, 0, 0, 0.7f);
|
||||
|
||||
var amountTextObj = new GameObject("Text");
|
||||
amountTextObj.transform.SetParent(amountObj.transform, false);
|
||||
amountTextObj.AddComponent<RectTransform>().sizeDelta = new Vector2(20, 20);
|
||||
slot.amountText = amountTextObj.AddComponent<TextMeshProUGUI>();
|
||||
slot.amountText.fontSize = 14;
|
||||
slot.amountText.alignment = TextAlignmentOptions.Right;
|
||||
slot.amountText.color = Color.white;
|
||||
slot.amountText.fontStyle = FontStyles.Bold;
|
||||
|
||||
// Drag & Drop
|
||||
var dragDrop = slotObj.AddComponent<InventoryDragDrop>();
|
||||
dragDrop.slot = slot;
|
||||
dragDrop.slotIndex = slot.slotIndex;
|
||||
|
||||
slots[i] = slot;
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
}
|
||||
2
Assets/Inventory/Scripts/InventorySceneSetup.cs.meta
Normal file
2
Assets/Inventory/Scripts/InventorySceneSetup.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70e234cedfb31844792350a7206e4bad
|
||||
101
Assets/Inventory/Scripts/InventorySetup.cs
Normal file
101
Assets/Inventory/Scripts/InventorySetup.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Автоматически настраивает всю систему инвентаря на сцене
|
||||
/// Добавить на пустой GameObject "GameManager" в сцене
|
||||
/// </summary>
|
||||
public class InventorySetup : MonoBehaviour
|
||||
{
|
||||
[Header("Настройки")]
|
||||
public bool createUI = true;
|
||||
public bool createManagers = true;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Не создаём если уже есть на сцене (после ручной настройки)
|
||||
if (FindObjectOfType<InventoryManager>() != null)
|
||||
{
|
||||
Debug.Log("[InventorySetup] Inventory already exists, skipping creation");
|
||||
return;
|
||||
}
|
||||
|
||||
if (createManagers)
|
||||
SetupManagers();
|
||||
|
||||
if (createUI)
|
||||
SetupUI();
|
||||
}
|
||||
|
||||
void SetupManagers()
|
||||
{
|
||||
var imGO = new GameObject("InventoryManager");
|
||||
imGO.transform.SetParent(transform);
|
||||
imGO.AddComponent<InventoryManager>();
|
||||
|
||||
var itemsGO = new GameObject("InventoryItems");
|
||||
itemsGO.transform.SetParent(transform);
|
||||
itemsGO.AddComponent<InventoryExampleItems>();
|
||||
|
||||
var testerGO = new GameObject("InventoryTester");
|
||||
testerGO.transform.SetParent(transform);
|
||||
testerGO.AddComponent<InventoryTester>();
|
||||
|
||||
Debug.Log("[InventorySetup] Managers created");
|
||||
}
|
||||
|
||||
void SetupUI()
|
||||
{
|
||||
var canvasGO = new GameObject("InventoryCanvas");
|
||||
canvasGO.transform.SetParent(transform);
|
||||
|
||||
var canvas = canvasGO.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 100;
|
||||
canvasGO.AddComponent<GraphicRaycaster>();
|
||||
|
||||
canvasGO.AddComponent<InventoryUIBuilder>();
|
||||
|
||||
Debug.Log("[InventorySetup] UI created");
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Кнопка в инспекторе для быстрой настройки сцены
|
||||
/// </summary>
|
||||
[CustomEditor(typeof(InventorySetup))]
|
||||
public class InventorySetupEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
if (GUILayout.Button("Настроить сцену для инвентаря"))
|
||||
{
|
||||
var target = (InventorySetup)this.target;
|
||||
|
||||
if (FindObjectOfType<InventoryManager>() == null)
|
||||
{
|
||||
target.createManagers = true;
|
||||
target.createUI = true;
|
||||
Debug.Log("Инвентарь будет настроен при запуске");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("InventoryManager уже существует на сцене!");
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("Открыть сцену в Unity"))
|
||||
{
|
||||
EditorApplication.ExecuteMenuItem("Window/General/Scene");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
2
Assets/Inventory/Scripts/InventorySetup.cs.meta
Normal file
2
Assets/Inventory/Scripts/InventorySetup.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a832ca7ed419e6479e1cc308a222f33
|
||||
291
Assets/Inventory/Scripts/InventoryUIBuilder.cs
Normal file
291
Assets/Inventory/Scripts/InventoryUIBuilder.cs
Normal file
@@ -0,0 +1,291 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт UI инвентаря программно с использованием TextMeshPro
|
||||
/// </summary>
|
||||
public class InventoryUIBuilder : MonoBehaviour
|
||||
{
|
||||
[Header("Settings")]
|
||||
public int hotbarSlots = 9;
|
||||
public int inventoryRows = 4;
|
||||
public int inventoryCols = 9;
|
||||
|
||||
[Header("Visual Settings")]
|
||||
public float slotSize = 50f;
|
||||
public float slotSpacing = 4f;
|
||||
public float inventoryPadding = 10f;
|
||||
public float hotbarPadding = 10f;
|
||||
|
||||
[Header("Colors")]
|
||||
[SerializeField] private Color inventoryPanelColor = new Color(0.15f, 0.15f, 0.15f, 0.95f);
|
||||
[SerializeField] private Color hotbarPanelColor = new Color(0.1f, 0.1f, 0.1f, 0.9f);
|
||||
[SerializeField] private Color slotEmptyColor = new Color(0.25f, 0.25f, 0.25f, 0.8f);
|
||||
[SerializeField] private Color slotFilledColor = new Color(0.35f, 0.35f, 0.35f, 0.95f);
|
||||
|
||||
[Header("Container References")]
|
||||
public RectTransform inventoryPanelRect;
|
||||
public RectTransform hotbarPanelRect;
|
||||
public Canvas inventoryCanvas;
|
||||
|
||||
void Start()
|
||||
{
|
||||
// Проверяем, не запущен ли уже BuildInventoryUI
|
||||
if (InventoryManager.Instance != null &&
|
||||
InventoryManager.Instance.slots != null &&
|
||||
InventoryManager.Instance.slots.Length > 0)
|
||||
{
|
||||
Debug.Log("[InventoryUIBuilder] UI already built, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
BuildInventoryUI();
|
||||
}
|
||||
|
||||
public void BuildInventoryUI()
|
||||
{
|
||||
// Создаём или получаем Canvas
|
||||
var canvas = GetComponent<Canvas>();
|
||||
if (canvas == null)
|
||||
{
|
||||
canvas = gameObject.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = 100;
|
||||
gameObject.AddComponent<GraphicRaycaster>();
|
||||
}
|
||||
inventoryCanvas = canvas;
|
||||
|
||||
// Настраиваем EventSystem если нет
|
||||
if (FindObjectOfType<TMPro.TMP_InputField>() != null || FindObjectsOfType<TMPro.TextMeshProUGUI>().Length > 0)
|
||||
{
|
||||
// EventSystem уже есть
|
||||
}
|
||||
|
||||
// Создаём панель инвентаря
|
||||
GameObject inventoryPanel = CreatePanel("InventoryPanel", transform, inventoryPanelColor);
|
||||
inventoryPanelRect = inventoryPanel.GetComponent<RectTransform>();
|
||||
|
||||
float inventoryWidth = inventoryCols * (slotSize + slotSpacing) + inventoryPadding * 2;
|
||||
float inventoryHeight = inventoryRows * (slotSize + slotSpacing) + inventoryPadding * 2 + 40; // +40 для заголовка
|
||||
|
||||
inventoryPanelRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
inventoryPanelRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
inventoryPanelRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
inventoryPanelRect.anchoredPosition = Vector2.zero;
|
||||
inventoryPanelRect.sizeDelta = new Vector2(inventoryWidth, inventoryHeight);
|
||||
|
||||
// Заголовок
|
||||
CreateTitle(inventoryPanel.transform, "Инвентарь", new Vector2(0, inventoryHeight / 2 - 20));
|
||||
|
||||
// Сетка инвентаря (4x9 = 36 слотов)
|
||||
float gridHeight = inventoryRows * (slotSize + slotSpacing);
|
||||
var inventoryGrid = CreateGrid(
|
||||
inventoryPanel.transform,
|
||||
"InventoryGrid",
|
||||
inventoryCols,
|
||||
inventoryRows,
|
||||
slotSize,
|
||||
slotSpacing,
|
||||
new Vector2(inventoryPadding, inventoryPadding + 30)
|
||||
);
|
||||
InventoryManager.Instance.slots = CreateSlots(inventoryGrid, "InventorySlot_", 0, inventoryCols * inventoryRows);
|
||||
|
||||
// Создаём хотбар
|
||||
GameObject hotbarPanel = CreatePanel("HotbarPanel", transform, hotbarPanelColor);
|
||||
hotbarPanelRect = hotbarPanel.GetComponent<RectTransform>();
|
||||
|
||||
float hotbarWidth = hotbarSlots * (slotSize + slotSpacing) + hotbarPadding * 2;
|
||||
|
||||
hotbarPanelRect.anchorMin = new Vector2(0.5f, 0f);
|
||||
hotbarPanelRect.anchorMax = new Vector2(0.5f, 0f);
|
||||
hotbarPanelRect.pivot = new Vector2(0.5f, 0f);
|
||||
hotbarPanelRect.anchoredPosition = new Vector2(0, 15);
|
||||
hotbarPanelRect.sizeDelta = new Vector2(hotbarWidth, slotSize + hotbarPadding * 2);
|
||||
|
||||
var hotbarGrid = CreateGrid(
|
||||
hotbarPanel.transform,
|
||||
"HotbarGrid",
|
||||
hotbarSlots,
|
||||
1,
|
||||
slotSize,
|
||||
slotSpacing,
|
||||
new Vector2(hotbarPadding, hotbarPadding)
|
||||
);
|
||||
InventoryManager.Instance.hotbarSlots = CreateSlots(hotbarGrid, "HotbarSlot_", 0, hotbarSlots);
|
||||
|
||||
// Настраиваем InventoryManager
|
||||
InventoryManager.Instance.inventoryPanel = inventoryPanel;
|
||||
InventoryManager.Instance.inventoryCanvas = inventoryCanvas;
|
||||
|
||||
Debug.Log($"[InventoryUIBuilder] Created {inventoryCols * inventoryRows} inventory slots + {hotbarSlots} hotbar slots");
|
||||
}
|
||||
|
||||
GameObject CreatePanel(string name, Transform parent, Color color)
|
||||
{
|
||||
GameObject panel = new GameObject(name);
|
||||
panel.transform.SetParent(parent, false);
|
||||
|
||||
var rect = panel.AddComponent<RectTransform>();
|
||||
panel.AddComponent<Image>().color = color;
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
void CreateTitle(Transform parent, string text, Vector2 position)
|
||||
{
|
||||
GameObject titleObj = new GameObject("Title");
|
||||
titleObj.transform.SetParent(parent, false);
|
||||
|
||||
var rect = titleObj.AddComponent<RectTransform>();
|
||||
rect.anchorMin = new Vector2(0, 1);
|
||||
rect.anchorMax = new Vector2(1, 1);
|
||||
rect.pivot = new Vector2(0.5f, 1);
|
||||
rect.anchoredPosition = position;
|
||||
rect.sizeDelta = new Vector2(-20, 30);
|
||||
|
||||
var tmp = titleObj.AddComponent<TextMeshProUGUI>();
|
||||
tmp.text = text;
|
||||
tmp.fontSize = 18;
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.color = Color.white;
|
||||
}
|
||||
|
||||
Transform CreateGrid(Transform parent, string name, int cols, int rows, float cellSize, float spacing, Vector2 offset)
|
||||
{
|
||||
GameObject gridObj = new GameObject(name);
|
||||
gridObj.transform.SetParent(parent, false);
|
||||
|
||||
var rect = gridObj.AddComponent<RectTransform>();
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.offsetMin = offset;
|
||||
rect.offsetMax = -offset;
|
||||
|
||||
var grid = gridObj.AddComponent<GridLayoutGroup>();
|
||||
grid.cellSize = new Vector2(cellSize, cellSize);
|
||||
grid.spacing = new Vector2(spacing, spacing);
|
||||
grid.startCorner = GridLayoutGroup.Corner.UpperLeft;
|
||||
grid.startAxis = GridLayoutGroup.Axis.Horizontal;
|
||||
grid.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||||
grid.constraintCount = cols;
|
||||
grid.padding = new RectOffset(0, 0, 0, 0);
|
||||
|
||||
return gridObj.transform;
|
||||
}
|
||||
|
||||
Slot[] CreateSlots(Transform parent, string prefix, int startIndex, int count)
|
||||
{
|
||||
var slots = new Slot[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
GameObject slotObj = new GameObject(prefix + (startIndex + i));
|
||||
slotObj.transform.SetParent(parent, false);
|
||||
|
||||
var rect = slotObj.AddComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(slotSize, slotSize);
|
||||
|
||||
// Background
|
||||
var bg = slotObj.AddComponent<Image>();
|
||||
bg.color = slotEmptyColor;
|
||||
|
||||
// Slot компонент
|
||||
var slot = slotObj.AddComponent<Slot>();
|
||||
slot.slotIndex = startIndex + i;
|
||||
|
||||
// Icon
|
||||
GameObject iconObj = new GameObject("Icon");
|
||||
iconObj.transform.SetParent(slotObj.transform, false);
|
||||
var iconRect = iconObj.AddComponent<RectTransform>();
|
||||
iconRect.anchorMin = Vector2.zero;
|
||||
iconRect.anchorMax = Vector2.one;
|
||||
iconRect.offsetMin = new Vector2(4, 4);
|
||||
iconRect.offsetMax = new Vector2(-4, -4);
|
||||
slot.iconImage = iconObj.AddComponent<Image>();
|
||||
slot.iconImage.preserveAspect = true;
|
||||
slot.iconImage.enabled = false;
|
||||
|
||||
// Amount panel
|
||||
GameObject amountObj = new GameObject("AmountPanel");
|
||||
amountObj.transform.SetParent(slotObj.transform, false);
|
||||
var amountRect = amountObj.AddComponent<RectTransform>();
|
||||
amountRect.anchorMin = Vector2.one;
|
||||
amountRect.anchorMax = Vector2.one;
|
||||
amountRect.pivot = new Vector2(1, 0);
|
||||
amountRect.anchoredPosition = new Vector2(-2, 2);
|
||||
amountRect.sizeDelta = new Vector2(22, 22);
|
||||
slot.amountPanel = amountObj;
|
||||
|
||||
var amountBg = amountObj.AddComponent<Image>();
|
||||
amountBg.color = new Color(0, 0, 0, 0.7f);
|
||||
|
||||
GameObject amountTextObj = new GameObject("Text");
|
||||
amountTextObj.transform.SetParent(amountObj.transform, false);
|
||||
var amountTextRect = amountTextObj.AddComponent<RectTransform>();
|
||||
amountTextRect.anchorMin = Vector2.zero;
|
||||
amountTextRect.anchorMax = Vector2.one;
|
||||
slot.amountText = amountTextObj.AddComponent<TextMeshProUGUI>();
|
||||
slot.amountText.fontSize = 14;
|
||||
slot.amountText.alignment = TextAlignmentOptions.Right;
|
||||
slot.amountText.color = Color.white;
|
||||
slot.amountText.fontStyle = FontStyles.Bold;
|
||||
|
||||
// Применяем настройки через сеттер
|
||||
slot.amountPanel.SetActive(false);
|
||||
slot.slotBackground = bg;
|
||||
|
||||
// Drag & Drop
|
||||
var dragDrop = slotObj.AddComponent<InventoryDragDrop>();
|
||||
dragDrop.slot = slot;
|
||||
dragDrop.slotIndex = slot.slotIndex;
|
||||
|
||||
// Подписка на события слота
|
||||
slot.OnSlotClicked += OnSlotClicked;
|
||||
|
||||
slots[i] = slot;
|
||||
}
|
||||
|
||||
return slots;
|
||||
}
|
||||
|
||||
private int _selectedSlot = -1;
|
||||
|
||||
private void OnSlotClicked(Slot slot)
|
||||
{
|
||||
// Снимаем выделение с предыдущего
|
||||
if (_selectedSlot >= 0 && _selectedSlot < InventoryManager.Instance.slots.Length)
|
||||
{
|
||||
InventoryManager.Instance.slots[_selectedSlot].SetSelected(false);
|
||||
}
|
||||
|
||||
// Выделяем новый
|
||||
slot.SetSelected(true);
|
||||
_selectedSlot = slot.slotIndex;
|
||||
|
||||
Debug.Log($"[UI] Selected slot: {_selectedSlot} - {slot.GetItem()?.itemName ?? "Empty"}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Пересоздать UI (например, при изменении настроек)
|
||||
/// </summary>
|
||||
public void Rebuild()
|
||||
{
|
||||
// Удаляем старые панели
|
||||
if (inventoryPanelRect != null)
|
||||
Destroy(inventoryPanelRect.gameObject);
|
||||
if (hotbarPanelRect != null)
|
||||
Destroy(hotbarPanelRect.gameObject);
|
||||
|
||||
BuildInventoryUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Показать/скрыть инвентарь
|
||||
/// </summary>
|
||||
public void ToggleInventory(bool show)
|
||||
{
|
||||
if (inventoryPanelRect != null)
|
||||
inventoryPanelRect.gameObject.SetActive(show);
|
||||
}
|
||||
}
|
||||
2
Assets/Inventory/Scripts/InventoryUIBuilder.cs.meta
Normal file
2
Assets/Inventory/Scripts/InventoryUIBuilder.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9296b7da4cf11cf41909512f177d7434
|
||||
231
Assets/Inventory/Scripts/ItemBuilderTool.cs
Normal file
231
Assets/Inventory/Scripts/ItemBuilderTool.cs
Normal file
@@ -0,0 +1,231 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEditor;
|
||||
using TMPro;
|
||||
|
||||
public class ItemBuilderTool : EditorWindow
|
||||
{
|
||||
// Основные данные
|
||||
private string itemName = "NewItem";
|
||||
private string itemId = "";
|
||||
private string description = "";
|
||||
private ItemType itemType = ItemType.Material;
|
||||
|
||||
// Визуал
|
||||
private GameObject modelPrefab;
|
||||
private Sprite icon;
|
||||
private Color previewColor = Color.gray;
|
||||
|
||||
// Свойства
|
||||
private int maxStack = 64;
|
||||
private int value = 1;
|
||||
private float weight = 1f;
|
||||
private bool isDroppable = true;
|
||||
private bool isStackable = true;
|
||||
|
||||
// Префаб
|
||||
private GameObject worldItemPrefab;
|
||||
|
||||
[MenuItem("Tools/Item Builder")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
GetWindow<ItemBuilderTool>("Конструктор Предметов");
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
GUILayout.Label("Создание предмета", EditorStyles.boldLabel);
|
||||
|
||||
// Основные данные
|
||||
GUILayout.Label("Основное", EditorStyles.miniBoldLabel);
|
||||
itemName = EditorGUILayout.TextField("Название", itemName);
|
||||
itemId = EditorGUILayout.TextField("ID", itemId);
|
||||
description = EditorGUILayout.TextField("Описание", description);
|
||||
itemType = (ItemType)EditorGUILayout.EnumPopup("Тип предмета", itemType);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// Визуал
|
||||
GUILayout.Label("Визуал", EditorStyles.miniBoldLabel);
|
||||
modelPrefab = (GameObject)EditorGUILayout.ObjectField("3D модель", modelPrefab, typeof(GameObject), false);
|
||||
icon = (Sprite)EditorGUILayout.ObjectField("Иконка", icon, typeof(Sprite), false);
|
||||
|
||||
if (icon == null)
|
||||
{
|
||||
previewColor = EditorGUILayout.ColorField("Цвет иконки", previewColor);
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// Свойства
|
||||
GUILayout.Label("Свойства", EditorStyles.miniBoldLabel);
|
||||
maxStack = EditorGUILayout.IntField("Макс. стек", maxStack);
|
||||
value = EditorGUILayout.IntField("Цена", value);
|
||||
weight = EditorGUILayout.FloatField("Вес", weight);
|
||||
isDroppable = EditorGUILayout.Toggle("Можно выбросить", isDroppable);
|
||||
isStackable = EditorGUILayout.Toggle("Стек", isStackable);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// Префаб
|
||||
GUILayout.Label("Префаб предмета в мире", EditorStyles.miniBoldLabel);
|
||||
worldItemPrefab = (GameObject)EditorGUILayout.ObjectField("WorldItem Prefab", worldItemPrefab, typeof(GameObject), false);
|
||||
|
||||
GUILayout.Space(20);
|
||||
|
||||
// Кнопки
|
||||
GUI.color = Color.green;
|
||||
if (GUILayout.Button("Создать ItemData"))
|
||||
{
|
||||
CreateItemData();
|
||||
}
|
||||
|
||||
GUI.color = Color.cyan;
|
||||
if (GUILayout.Button("Создать WorldItem в сцене"))
|
||||
{
|
||||
CreateWorldItem();
|
||||
}
|
||||
|
||||
GUI.color = Color.yellow;
|
||||
if (GUILayout.Button("Создать префаб"))
|
||||
{
|
||||
CreateItemPrefab();
|
||||
}
|
||||
|
||||
GUI.color = Color.white;
|
||||
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.HelpBox(
|
||||
"1. Заполните данные предмета\n" +
|
||||
"2. Назначьте модель и иконку\n" +
|
||||
"3. Создайте ItemData или префаб",
|
||||
MessageType.Info);
|
||||
}
|
||||
|
||||
void CreateItemData()
|
||||
{
|
||||
if (string.IsNullOrEmpty(itemName))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Ошибка", "Введите название предмета", "OK");
|
||||
return;
|
||||
}
|
||||
|
||||
var item = ScriptableObject.CreateInstance<ItemData>();
|
||||
|
||||
item.id = string.IsNullOrEmpty(itemId) ? GUID.Generate().ToString() : itemId;
|
||||
item.itemName = itemName;
|
||||
item.description = description;
|
||||
item.type = itemType;
|
||||
item.modelPrefab = modelPrefab;
|
||||
item.icon = icon != null ? icon : CreatePlaceholderIcon();
|
||||
item.maxStack = maxStack;
|
||||
item.value = value;
|
||||
item.weight = weight;
|
||||
item.isDroppable = isDroppable;
|
||||
item.isStackable = isStackable;
|
||||
|
||||
// Сохраняем
|
||||
string path = $"Assets/Inventory/Items/{itemName}.asset";
|
||||
string folder = System.IO.Path.GetDirectoryName(path);
|
||||
if (!System.IO.Directory.Exists(folder))
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(folder);
|
||||
}
|
||||
|
||||
AssetDatabase.CreateAsset(item, path);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
Selection.activeObject = item;
|
||||
Debug.Log($"[ItemBuilder] Создан: {itemName}");
|
||||
}
|
||||
|
||||
void CreateWorldItem()
|
||||
{
|
||||
if (modelPrefab == null)
|
||||
{
|
||||
// Создаём простой визуал
|
||||
var itemGO = new GameObject(itemName);
|
||||
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
visual.name = "Visual";
|
||||
visual.transform.SetParent(itemGO.transform);
|
||||
visual.transform.localPosition = Vector3.zero;
|
||||
visual.transform.localScale = Vector3.one * 0.3f;
|
||||
DestroyImmediate(visual.GetComponent<Collider>());
|
||||
|
||||
var worldItem = itemGO.AddComponent<WorldItem>();
|
||||
var col = itemGO.AddComponent<SphereCollider>();
|
||||
col.radius = 0.5f;
|
||||
col.isTrigger = true;
|
||||
|
||||
itemGO.transform.position = Vector3.zero;
|
||||
Selection.activeGameObject = itemGO;
|
||||
}
|
||||
else
|
||||
{
|
||||
var itemGO = Instantiate(modelPrefab);
|
||||
itemGO.name = itemName;
|
||||
itemGO.AddComponent<WorldItem>();
|
||||
itemGO.transform.position = Vector3.zero;
|
||||
Selection.activeGameObject = itemGO;
|
||||
}
|
||||
|
||||
Debug.Log($"[ItemBuilder] Создан WorldItem: {itemName}");
|
||||
}
|
||||
|
||||
void CreateItemPrefab()
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanelInProject(
|
||||
"Сохранить префаб",
|
||||
itemName,
|
||||
"prefab",
|
||||
"Выберите место");
|
||||
|
||||
if (string.IsNullOrEmpty(path)) return;
|
||||
|
||||
GameObject prefab;
|
||||
if (modelPrefab != null)
|
||||
{
|
||||
prefab = Instantiate(modelPrefab);
|
||||
prefab.name = itemName;
|
||||
}
|
||||
else
|
||||
{
|
||||
prefab = new GameObject(itemName);
|
||||
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
visual.name = "Visual";
|
||||
visual.transform.SetParent(prefab.transform);
|
||||
visual.transform.localPosition = Vector3.zero;
|
||||
visual.transform.localScale = Vector3.one * 0.3f;
|
||||
DestroyImmediate(visual.GetComponent<Collider>());
|
||||
}
|
||||
|
||||
prefab.AddComponent<WorldItem>();
|
||||
|
||||
var col = prefab.GetComponent<Collider>();
|
||||
if (col == null)
|
||||
{
|
||||
var sphere = prefab.AddComponent<SphereCollider>();
|
||||
sphere.radius = 0.5f;
|
||||
sphere.isTrigger = true;
|
||||
}
|
||||
|
||||
GameObject savedPrefab = PrefabUtility.SaveAsPrefabAsset(prefab, path);
|
||||
DestroyImmediate(prefab);
|
||||
|
||||
Selection.activeObject = savedPrefab;
|
||||
Debug.Log($"[ItemBuilder] Префаб сохранён: {path}");
|
||||
}
|
||||
|
||||
Sprite CreatePlaceholderIcon()
|
||||
{
|
||||
var tex = new Texture2D(64, 64);
|
||||
var colors = new Color[64 * 64];
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
colors[i] = previewColor;
|
||||
}
|
||||
tex.SetPixels(colors);
|
||||
tex.Apply();
|
||||
return Sprite.Create(tex, new Rect(0, 0, 64, 64), new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
}
|
||||
2
Assets/Inventory/Scripts/ItemBuilderTool.cs.meta
Normal file
2
Assets/Inventory/Scripts/ItemBuilderTool.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b29b6b4c7cab91429e66ed5012b12d7
|
||||
53
Assets/Inventory/Scripts/ItemData.cs
Normal file
53
Assets/Inventory/Scripts/ItemData.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using UnityEngine;
|
||||
|
||||
public enum ItemType
|
||||
{
|
||||
Block,
|
||||
Weapon,
|
||||
Armor,
|
||||
Consumable,
|
||||
Tool,
|
||||
Material
|
||||
}
|
||||
|
||||
[CreateAssetMenu(fileName = "NewItem", menuName = "Inventory/Item")]
|
||||
public class ItemData : ScriptableObject
|
||||
{
|
||||
public string id;
|
||||
public string itemName;
|
||||
public string description;
|
||||
public ItemType type;
|
||||
public GameObject modelPrefab;
|
||||
public Sprite icon;
|
||||
public int maxStack = 64;
|
||||
public bool isCraftable;
|
||||
public CraftingRecipe craftingRecipe;
|
||||
|
||||
[Header("Item Properties")]
|
||||
public float weight = 1f;
|
||||
public int value = 1;
|
||||
public bool isDroppable = true;
|
||||
public bool isStackable = true;
|
||||
|
||||
[System.Serializable]
|
||||
public class CraftingRecipe
|
||||
{
|
||||
public string[] ingredients;
|
||||
public int[] amounts;
|
||||
public string resultId;
|
||||
public int resultAmount = 1;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
id = System.Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemData FromJson(string json) => JsonUtility.FromJson<ItemData>(json);
|
||||
public string ToJson() => JsonUtility.ToJson(this, true);
|
||||
|
||||
public bool CanStackWith(ItemData other) => other != null && id == other.id && isStackable;
|
||||
}
|
||||
2
Assets/Inventory/Scripts/ItemData.cs.meta
Normal file
2
Assets/Inventory/Scripts/ItemData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5741b63764436354ebad192c9b71f06c
|
||||
262
Assets/Inventory/Scripts/ItemGenerator.cs
Normal file
262
Assets/Inventory/Scripts/ItemGenerator.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
public class ItemGenerator : MonoBehaviour
|
||||
{
|
||||
[Header("Item Template")]
|
||||
public string itemId = "";
|
||||
public string itemName = "";
|
||||
public string description = "";
|
||||
public ItemType itemType = ItemType.Material;
|
||||
public Sprite icon;
|
||||
public GameObject modelPrefab;
|
||||
public int maxStack = 64;
|
||||
public bool isCraftable;
|
||||
|
||||
[Header("Crafting (if craftable)")]
|
||||
public string[] craftIngredients;
|
||||
public int[] craftAmounts;
|
||||
public string craftResultId;
|
||||
public int craftResultAmount = 1;
|
||||
|
||||
public ItemData GenerateItemData()
|
||||
{
|
||||
if (string.IsNullOrEmpty(itemId))
|
||||
{
|
||||
itemId = System.Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
var itemData = ScriptableObject.CreateInstance<ItemData>();
|
||||
itemData.id = itemId;
|
||||
itemData.itemName = itemName;
|
||||
itemData.description = description;
|
||||
itemData.type = itemType;
|
||||
itemData.modelPrefab = modelPrefab;
|
||||
itemData.icon = icon;
|
||||
itemData.maxStack = maxStack;
|
||||
itemData.isCraftable = isCraftable;
|
||||
|
||||
if (isCraftable && craftIngredients != null && craftIngredients.Length > 0)
|
||||
{
|
||||
itemData.craftingRecipe = new ItemData.CraftingRecipe
|
||||
{
|
||||
ingredients = craftIngredients,
|
||||
amounts = craftAmounts,
|
||||
resultId = craftResultId,
|
||||
resultAmount = craftResultAmount
|
||||
};
|
||||
}
|
||||
|
||||
return itemData;
|
||||
}
|
||||
|
||||
public static List<ItemData> GenerateAll(List<ItemGenerator> generators)
|
||||
{
|
||||
var items = new List<ItemData>();
|
||||
foreach (var generator in generators)
|
||||
{
|
||||
if (generator != null)
|
||||
{
|
||||
items.Add(generator.GenerateItemData());
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Предмет в мире - подбирается игроком
|
||||
/// </summary>
|
||||
public class WorldItem : MonoBehaviour
|
||||
{
|
||||
public ItemData itemData;
|
||||
public int amount = 1;
|
||||
|
||||
[Header("Settings")]
|
||||
public float pickupDelay = 0f;
|
||||
public bool useFactory = true;
|
||||
|
||||
private bool _canPickup;
|
||||
|
||||
public void Setup(ItemData data, int count)
|
||||
{
|
||||
itemData = data;
|
||||
amount = count;
|
||||
|
||||
if (data.modelPrefab != null)
|
||||
{
|
||||
var model = Instantiate(data.modelPrefab, transform);
|
||||
model.transform.localPosition = Vector3.zero;
|
||||
}
|
||||
|
||||
// Задержка перед подбором
|
||||
if (pickupDelay > 0)
|
||||
Invoke(nameof(EnablePickup), pickupDelay);
|
||||
else
|
||||
_canPickup = true;
|
||||
}
|
||||
|
||||
private void EnablePickup() => _canPickup = true;
|
||||
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
TryPickup(other);
|
||||
}
|
||||
|
||||
void OnControllerColliderHit(ControllerColliderHit hit)
|
||||
{
|
||||
TryPickup(hit.controller);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Подобрать предмет вручную (по кнопке)
|
||||
/// </summary>
|
||||
public bool TryPickupByButton()
|
||||
{
|
||||
Debug.Log($"[TryPickup] _canPickup={_canPickup}, itemData={itemData?.itemName}, amount={amount}, Instance={InventoryManager.Instance != null}");
|
||||
|
||||
if (!_canPickup)
|
||||
{
|
||||
Debug.LogWarning("[TryPickup] Cannot pickup - not ready yet");
|
||||
return false;
|
||||
}
|
||||
if (itemData == null)
|
||||
{
|
||||
Debug.LogWarning("[TryPickup] itemData is null!");
|
||||
return false;
|
||||
}
|
||||
if (InventoryManager.Instance == null)
|
||||
{
|
||||
Debug.LogWarning("[TryPickup] InventoryManager.Instance is null!");
|
||||
return false;
|
||||
}
|
||||
|
||||
int remaining = InventoryManager.Instance.AddItem(itemData, amount);
|
||||
Debug.Log($"[TryPickup] AddItem returned remaining={remaining}");
|
||||
|
||||
if (remaining <= 0)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = remaining;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void TryPickup(Collider other)
|
||||
{
|
||||
if (!_canPickup) return;
|
||||
if (other == null) return;
|
||||
if (!other.CompareTag("Player")) return;
|
||||
|
||||
if (useFactory && InventoryItemFactory.Instance != null)
|
||||
{
|
||||
InventoryItemFactory.Instance.PickupItem(this, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback - старый способ
|
||||
int remaining = InventoryManager.Instance.AddItem(itemData, amount);
|
||||
if (remaining <= 0)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
amount = remaining;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выбросить предмет с силой
|
||||
/// </summary>
|
||||
public void Throw(Vector3 velocity)
|
||||
{
|
||||
var rb = GetComponent<Rigidbody>();
|
||||
if (rb == null) rb = gameObject.AddComponent<Rigidbody>();
|
||||
rb.linearVelocity = velocity;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Точка спавна предмета
|
||||
/// </summary>
|
||||
public class ItemSpawner : MonoBehaviour
|
||||
{
|
||||
public ItemData item;
|
||||
public int amount = 1;
|
||||
public float despawnTime = 300f;
|
||||
public bool spawnOnStart = false;
|
||||
public bool useFactory = true;
|
||||
|
||||
public void Setup(ItemData itemData, int itemAmount, float despawn)
|
||||
{
|
||||
item = itemData;
|
||||
amount = itemAmount;
|
||||
despawnTime = despawn;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
if (spawnOnStart) Spawn();
|
||||
}
|
||||
|
||||
public void Spawn()
|
||||
{
|
||||
if (useFactory && InventoryItemFactory.Instance != null)
|
||||
{
|
||||
var worldItem = InventoryItemFactory.Instance.CreateWorldItem(item, amount, transform.position);
|
||||
if (worldItem != null && despawnTime > 0)
|
||||
{
|
||||
Destroy(worldItem.gameObject, despawnTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback - старый способ
|
||||
var prefab = Resources.Load<GameObject>("Prefabs/WorldItem");
|
||||
if (prefab != null)
|
||||
{
|
||||
var worldItem = Instantiate(prefab, transform.position, Quaternion.identity).GetComponent<WorldItem>();
|
||||
worldItem.Setup(item, amount);
|
||||
Destroy(worldItem.gameObject, despawnTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Despawn()
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(ItemGenerator))]
|
||||
public class ItemGeneratorEditor : Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
base.OnInspectorGUI();
|
||||
|
||||
if (GUILayout.Button("Generate ItemData"))
|
||||
{
|
||||
var generator = (ItemGenerator)target;
|
||||
var itemData = generator.GenerateItemData();
|
||||
|
||||
string path = $"Assets/Inventory/Items/{itemData.itemName}.asset";
|
||||
AssetDatabase.CreateAsset(itemData, path);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
Debug.Log($"Created item: {itemData.itemName} at {path}");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
2
Assets/Inventory/Scripts/ItemGenerator.cs.meta
Normal file
2
Assets/Inventory/Scripts/ItemGenerator.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 912124eba177b3d4c8f4890d9b408b13
|
||||
286
Assets/Inventory/Scripts/Slot.cs
Normal file
286
Assets/Inventory/Scripts/Slot.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using TMPro;
|
||||
|
||||
/// <summary>
|
||||
/// Слот инвентаря - отображает предмет и его количество
|
||||
/// </summary>
|
||||
public class Slot : MonoBehaviour
|
||||
{
|
||||
public int slotIndex;
|
||||
public ItemData item;
|
||||
public int amount;
|
||||
|
||||
[Header("UI References")]
|
||||
public Image iconImage;
|
||||
public TextMeshProUGUI amountText;
|
||||
public GameObject amountPanel;
|
||||
public Image slotBackground;
|
||||
|
||||
[Header("Visual Settings")]
|
||||
[SerializeField] private Color emptySlotColor = new Color(0.2f, 0.2f, 0.2f, 0.5f);
|
||||
[SerializeField] private Color filledSlotColor = new Color(0.3f, 0.3f, 0.3f, 0.9f);
|
||||
[SerializeField] private Color hoverColor = new Color(0.5f, 0.5f, 0.5f, 1f);
|
||||
[SerializeField] private Color selectedColor = new Color(0.4f, 0.6f, 0.9f, 1f);
|
||||
|
||||
[Header("Drag & Drop")]
|
||||
[SerializeField] private bool allowDrag = true;
|
||||
|
||||
public bool IsEmpty => item == null || amount <= 0;
|
||||
public bool HasItem => !IsEmpty;
|
||||
|
||||
// События для внешних подписчиков
|
||||
public System.Action<Slot> OnSlotClicked;
|
||||
public System.Action<Slot> OnSlotRightClicked;
|
||||
public System.Action<Slot> OnSlotHovered;
|
||||
public System.Action<Slot> OnSlotDragStart;
|
||||
|
||||
private bool _isSelected;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Автоматическое получение компонентов если не назначены
|
||||
if (iconImage == null)
|
||||
iconImage = transform.Find("Icon")?.GetComponent<Image>();
|
||||
|
||||
if (amountText == null)
|
||||
amountText = transform.Find("AmountPanel/Text")?.GetComponent<TextMeshProUGUI>();
|
||||
|
||||
if (amountPanel == null)
|
||||
amountPanel = transform.Find("AmountPanel")?.gameObject;
|
||||
|
||||
if (slotBackground == null)
|
||||
slotBackground = GetComponent<Image>();
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
UpdateSlotUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Обновить визуальное отображение слота
|
||||
/// </summary>
|
||||
public void UpdateSlotUI()
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
ClearVisuals();
|
||||
return;
|
||||
}
|
||||
|
||||
// Иконка
|
||||
if (item.icon != null)
|
||||
{
|
||||
iconImage.sprite = item.icon;
|
||||
iconImage.enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
iconImage.enabled = false;
|
||||
}
|
||||
|
||||
// Количество
|
||||
if (amount > 1)
|
||||
{
|
||||
amountText.text = amount.ToString();
|
||||
amountPanel.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
amountPanel.SetActive(false);
|
||||
}
|
||||
|
||||
// Цвет фона
|
||||
if (slotBackground != null)
|
||||
slotBackground.color = filledSlotColor;
|
||||
}
|
||||
|
||||
private void ClearVisuals()
|
||||
{
|
||||
iconImage.sprite = null;
|
||||
iconImage.enabled = false;
|
||||
amountPanel?.SetActive(false);
|
||||
|
||||
if (slotBackground != null)
|
||||
slotBackground.color = emptySlotColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Очистить слот
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
item = null;
|
||||
amount = 0;
|
||||
UpdateSlotUI();
|
||||
|
||||
// Уведомляем менеджер
|
||||
if (InventoryManager.Instance != null)
|
||||
{
|
||||
InventoryManager.Instance.OnSlotCleared(slotIndex);
|
||||
}
|
||||
|
||||
// Вызываем событие
|
||||
InventoryEvents.Instance?.InvokeSlotChanged(slotIndex, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить предмет в слот
|
||||
/// </summary>
|
||||
public void SetItem(ItemData newItem, int newAmount)
|
||||
{
|
||||
item = newItem;
|
||||
amount = newAmount;
|
||||
UpdateSlotUI();
|
||||
|
||||
// Вызываем событие изменения
|
||||
InventoryEvents.Instance?.InvokeSlotChanged(slotIndex, item?.id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавить предметы в слот
|
||||
/// </summary>
|
||||
/// <returns>Количество не поместившихся предметов</returns>
|
||||
public int AddItems(ItemData newItem, int addAmount)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
// Пустой слот - просто кладём
|
||||
item = newItem;
|
||||
amount = Mathf.Min(addAmount, newItem.maxStack);
|
||||
UpdateSlotUI();
|
||||
|
||||
// Событие добавления
|
||||
InventoryEvents.Instance?.InvokeItemAdded(item.id, amount);
|
||||
InventoryEvents.Instance?.InvokeSlotChanged(slotIndex, item.id);
|
||||
|
||||
return addAmount - amount;
|
||||
}
|
||||
|
||||
if (item.id != newItem.id)
|
||||
{
|
||||
// Другой предмет - нельзя добавить
|
||||
return addAmount;
|
||||
}
|
||||
|
||||
// Тот же предмет - пытаемся добавить в стек
|
||||
int space = item.maxStack - amount;
|
||||
if (space <= 0) return addAmount;
|
||||
|
||||
int toAdd = Mathf.Min(space, addAmount);
|
||||
amount += toAdd;
|
||||
UpdateSlotUI();
|
||||
|
||||
// Событие изменения
|
||||
InventoryEvents.Instance?.InvokeItemCountChanged(item.id, amount);
|
||||
InventoryEvents.Instance?.InvokeSlotChanged(slotIndex, item.id);
|
||||
|
||||
return addAmount - toAdd;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Взять часть предметов из слота
|
||||
/// </summary>
|
||||
/// <returns>(предмет, взятое количество)</returns>
|
||||
public (ItemData, int) TakeItems(int takeAmount)
|
||||
{
|
||||
if (IsEmpty) return (null, 0);
|
||||
|
||||
int actualTake = Mathf.Min(takeAmount, amount);
|
||||
amount -= actualTake;
|
||||
|
||||
ItemData resultItem = item;
|
||||
|
||||
if (amount <= 0)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateSlotUI();
|
||||
// Событие изменения количества
|
||||
InventoryEvents.Instance?.InvokeItemCountChanged(item.id, amount);
|
||||
}
|
||||
|
||||
// Событие удаления
|
||||
InventoryEvents.Instance?.InvokeItemRemoved(resultItem.id, actualTake);
|
||||
|
||||
return (resultItem, actualTake);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Взять все предметы из слота
|
||||
/// </summary>
|
||||
public (ItemData, int) TakeAll()
|
||||
{
|
||||
return TakeItems(amount);
|
||||
}
|
||||
|
||||
// ==================== UI Events ====================
|
||||
|
||||
void OnMouseEnter()
|
||||
{
|
||||
if (slotBackground != null && !_isSelected)
|
||||
slotBackground.color = hoverColor;
|
||||
|
||||
OnSlotHovered?.Invoke(this);
|
||||
}
|
||||
|
||||
void OnMouseExit()
|
||||
{
|
||||
if (slotBackground != null && !_isSelected)
|
||||
slotBackground.color = IsEmpty ? emptySlotColor : filledSlotColor;
|
||||
}
|
||||
|
||||
void OnMouseDown()
|
||||
{
|
||||
if (Input.GetMouseButton(0))
|
||||
{
|
||||
OnSlotClicked?.Invoke(this);
|
||||
|
||||
if (allowDrag)
|
||||
{
|
||||
OnSlotDragStart?.Invoke(this);
|
||||
}
|
||||
}
|
||||
else if (Input.GetMouseButton(1))
|
||||
{
|
||||
OnSlotRightClicked?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Установить состояние выделения слота
|
||||
/// </summary>
|
||||
public void SetSelected(bool selected)
|
||||
{
|
||||
_isSelected = selected;
|
||||
if (slotBackground != null)
|
||||
{
|
||||
slotBackground.color = selected ? selectedColor :
|
||||
(IsEmpty ? emptySlotColor : filledSlotColor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Поменять содержимое с другим слотом
|
||||
/// </summary>
|
||||
public void SwapWith(Slot other)
|
||||
{
|
||||
if (other == null || other == this) return;
|
||||
|
||||
var tempItem = this.item;
|
||||
var tempAmount = this.amount;
|
||||
|
||||
this.SetItem(other.item, other.amount);
|
||||
other.SetItem(tempItem, tempAmount);
|
||||
}
|
||||
|
||||
// ==================== Getters ====================
|
||||
|
||||
public ItemData GetItem() => item;
|
||||
public int GetAmount() => amount;
|
||||
public string GetItemId() => item?.id;
|
||||
public bool IsSelected() => _isSelected;
|
||||
}
|
||||
2
Assets/Inventory/Scripts/Slot.cs.meta
Normal file
2
Assets/Inventory/Scripts/Slot.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 700406249e03c6646b1d535e57851f30
|
||||
Reference in New Issue
Block a user