using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.VFX;
namespace RR.Game2D.Behaviour
{
///
/// Пул объектов визуальных эффектов для переиспользования VFX и оптимизации производительности.
///
public class VFXPool : MonoBehaviour
{
public static VFXPool Instance { get; private set; }
[SerializeField] private int poolSize = 32;
[SerializeField] private float defaultLifetime = 2f; // время жизни эффекта по умолчанию (сек)
private Queue _pool;
private void Awake()
{
// ---- Singleton ----
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
// -------------------
// Инициализируем очередь для хранения свободных VFX объектов
_pool = new Queue();
// Предварительно создаем VFX объекты для пула
for (int i = 0; i < poolSize; i++)
{
GameObject vfxGameObject = new GameObject($"VFX_{i}");
vfxGameObject.AddComponent();
// Созданные объекты сразу не активны в пуле
vfxGameObject.SetActive(false);
vfxGameObject.transform.SetParent(transform);
_pool.Enqueue(vfxGameObject);
}
}
private void OnDestroy()
{
// Если уничтожается текущий активный экземпляр, сбрасываем ссылку
if (Instance == this)
{
Instance = null;
}
}
///
/// Получить VFX объект из пула (без автоматического запуска)
///
/// Ассет визуального эффекта для применения
/// GameObject с компонентом VisualEffect или null, если не удалось получить
public GameObject Get(VisualEffectAsset vfxAsset)
{
GameObject vfxInstance;
// Проверяем, есть ли свободные объекты в пуле
if (_pool.Count > 0)
{
vfxInstance = _pool.Dequeue();
vfxInstance.SetActive(true);
VisualEffect currentVFX = vfxInstance.GetComponent();
// Если текущий ассет не совпадает с запрашиваемым, заменяем его и реинициализируем
if (currentVFX.visualEffectAsset != vfxAsset)
{
currentVFX.Stop();
currentVFX.visualEffectAsset = vfxAsset;
currentVFX.Reinit();
}
}
else
{
// Пул пуст, создаем новый VFX объект
vfxInstance = new GameObject($"VFX_{_pool.Count}");
VisualEffect vfx = vfxInstance.AddComponent();
vfx.visualEffectAsset = vfxAsset;
vfxInstance.transform.SetParent(transform);
vfxInstance.SetActive(true);
}
// Сбрасываем поворот
if (vfxInstance)
{
vfxInstance.transform.rotation = Quaternion.identity;
}
return vfxInstance;
}
///
/// Воспроизвести VFX из пула и автоматически вернуть его позже.
///
/// Ассет визуального эффекта
/// Позиция для эффекта
/// Время жизни эффекта в секундах (если null — будет использовано defaultLifetime)
public GameObject Spawn(VisualEffectAsset vfxAsset, Vector3 position, float? lifetime = null)
{
float life = lifetime ?? defaultLifetime;
GameObject vfxInstance = Get(vfxAsset);
if (vfxInstance == null) return null;
vfxInstance.transform.position = position;
VisualEffect visualEffect = vfxInstance.GetComponent();
// Удостоверимся, что эффект корректно инициализирован и запущен
visualEffect.Reinit();
visualEffect.Play();
// Запускаем корутину, которая вернёт объект в пул после окончания времени жизни
StartCoroutine(ReturnAfterCoroutine(vfxInstance, life));
return vfxInstance;
}
private IEnumerator ReturnAfterCoroutine(GameObject vfxInstance, float seconds)
{
// Ждём указанное время. Можно заменить на более точный механизм при наличии событий от VFXGraph.
yield return new WaitForSeconds(seconds);
// Перед возвратом уверенно останавливаем эффект
var ve = vfxInstance.GetComponent();
if (ve) ve.Stop();
// Возвращаем в пул
Return(vfxInstance);
}
///
/// Вернуть VFX объект обратно в пул для повторного использования
///
/// VFX объект для возврата в пул
public void Return(GameObject vfxInstance)
{
if (vfxInstance == null) return;
VisualEffect visualEffect = vfxInstance.GetComponent();
if (visualEffect) visualEffect.Stop();
// Если пул не переполнен, деактивируем и кладём в очередь
if (_pool.Count < poolSize)
{
vfxInstance.SetActive(false);
_pool.Enqueue(vfxInstance);
}
else
{
// Пул переполнен, уничтожаем объект
Debug.Log("VFX Pool is full, destroying returned VFX instance.");
Destroy(vfxInstance);
}
}
}
}