using System.Collections.Generic; using UnityEngine; namespace Magisterka.BulletHell { /// /// Lightweight visual feedback hub for hits, explosions, and screen-clears. /// public class EffectManager : MonoBehaviour { public static EffectManager Instance { get; private set; } [SerializeField] private int warmCount = 64; [SerializeField] private Color playerColor = new Color(0.35f, 0.85f, 1f, 1f); [SerializeField] private Color enemyColor = new Color(1f, 0.45f, 0.25f, 1f); private readonly Queue _pool = new Queue(); private Transform _poolRoot; private CameraShaker _cameraShaker; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } public void Initialize(Transform parent) { if (parent != null) { transform.SetParent(parent, false); } _cameraShaker = FindFirstObjectByType(); if (_cameraShaker == null && Camera.main != null) { _cameraShaker = Camera.main.gameObject.AddComponent(); } _poolRoot = new GameObject("FX_Pool").transform; _poolRoot.SetParent(transform, false); Warmup(warmCount); } public void SpawnHitEffect(Vector2 position, Faction hitFaction, float intensity) { var color = hitFaction == Faction.Player ? playerColor : enemyColor; var endSize = Mathf.Lerp(0.6f, 1.8f, Mathf.Clamp01(intensity + 0.2f)); PlayPulse(position, color, 0.25f, endSize, 0.18f); } public void SpawnExplosion(Vector2 position, Faction faction, float size = 2.5f) { var color = faction == Faction.Player ? playerColor : enemyColor; for (int i = 0; i < 4; i++) { float start = 0.4f + i * 0.1f; float end = size + i * 0.35f; float duration = 0.22f + i * 0.06f; PlayPulse(position, color, start, end, duration); } _cameraShaker?.Shake(0.18f, 0.45f * size); } public void SpawnScreenClear(Vector2 position, float radius) { for (int i = 0; i < 6; i++) { float start = 0.8f + i * 0.5f; float end = radius + i * 1.2f; float duration = 0.35f + i * 0.12f; var blend = Mathf.Clamp01(i / 5f); var color = Color.Lerp(playerColor, enemyColor, blend); PlayPulse(position, color, start, end, duration); } _cameraShaker?.Shake(0.4f, 1.2f * radius); } private void Warmup(int count) { for (int i = 0; i < count; i++) { _pool.Enqueue(CreatePulse()); } } private EffectPulse CreatePulse() { var go = new GameObject("EffectPulse"); go.transform.SetParent(_poolRoot, false); var pulse = go.AddComponent(); go.SetActive(false); return pulse; } private void PlayPulse(Vector2 position, Color color, float startSize, float endSize, float duration) { var pulse = _pool.Count > 0 ? _pool.Dequeue() : CreatePulse(); pulse.transform.SetParent(null, false); pulse.Play(position, color, startSize, endSize, duration, Recycle); } private void Recycle(EffectPulse pulse) { if (pulse == null) { return; } pulse.transform.SetParent(_poolRoot, false); _pool.Enqueue(pulse); } } }