284 lines
9.8 KiB
C#
284 lines
9.8 KiB
C#
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));
|
||
}
|
||
|
||
// Если у ItemData нет связанной модели — создаём простой визуал (куб)
|
||
if (itemData != null && itemData.modelPrefab == null && worldItem != null)
|
||
{
|
||
var visual = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||
visual.name = "Visual";
|
||
visual.transform.SetParent(worldItem.transform, false);
|
||
visual.transform.localPosition = Vector3.zero;
|
||
visual.transform.localScale = Vector3.one * 0.3f;
|
||
// Удаляем лишний коллайдер на визуале
|
||
var col = visual.GetComponent<Collider>();
|
||
if (col != null) GameObject.Destroy(col);
|
||
}
|
||
|
||
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 |