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