This commit is contained in:
jze9
2026-04-15 02:56:11 +05:00
parent 523bf475ca
commit 0837589fa2
1786 changed files with 236305 additions and 73082 deletions

View 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;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c3c944af921361347b5bcb564f23e76d

View 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);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d3bc6c48bea19c74ea4d056cb2471292

View 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);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 92259c03301739842b087d3e9d9b0c5f

View 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);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ad1628cce096628409729c00123006be

View 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

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 2e734a4abaff54d45b8294cb6e023bea

View 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;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a4c4603259067dc4e96f741653843b92

View 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;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 70e234cedfb31844792350a7206e4bad

View 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

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9a832ca7ed419e6479e1cc308a222f33

View 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);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9296b7da4cf11cf41909512f177d7434

View 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));
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5b29b6b4c7cab91429e66ed5012b12d7

View 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;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5741b63764436354ebad192c9b71f06c

View 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

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 912124eba177b3d4c8f4890d9b408b13

View 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;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 700406249e03c6646b1d535e57851f30