using UnityEngine; using System.Collections; using System.Collections.Generic; public class HS_ParticleCollisionInstance : MonoBehaviour { public GameObject[] EffectsOnCollision; public float DestroyTimeDelay =5f; public bool UseWorldSpacePosition; public float Offset =0f; public Vector3 rotationOffset = new Vector3(0,0,0); public bool useOnlyRotationOffset = true; public bool UseFirePointRotation; public bool DestoyMainEffect = false; [Tooltip("Enable pooling to avoid Instantiate/Destroy spikes")] public bool UsePooling = true; [Tooltip("Maximum number of spawned effects processed per particle collision event (per OnParticleCollision call)")] public int MaxSpawnsPerCollisionCall =50; private ParticleSystem part; private List collisionEvents = new List(); // Pooling private static Transform poolRoot; private Dictionary> pools = new Dictionary>(); // Track instances that were spawned by this emitter and not yet returned to pool private HashSet activeInstances = new HashSet(); void OnValidate() { if (DestroyTimeDelay <0f) DestroyTimeDelay =0f; if (MaxSpawnsPerCollisionCall <1) MaxSpawnsPerCollisionCall =1; } void Start() { part = GetComponent(); if (poolRoot == null) { var rootGO = GameObject.Find("[PS_Effect_Pool]"); if (rootGO == null) { rootGO = new GameObject("[PS_Effect_Pool]"); DontDestroyOnLoad(rootGO); } poolRoot = rootGO.transform; } } void OnParticleCollision(GameObject other) { if (part == null) part = GetComponent(); if (part == null) return; // nothing to do without particle system if (EffectsOnCollision == null || EffectsOnCollision.Length ==0) return; int numCollisionEvents = part.GetCollisionEvents(other, collisionEvents); int spawned =0; for (int i =0; i < numCollisionEvents; i++) { if (spawned >= MaxSpawnsPerCollisionCall) break; // throttle var hitPos = collisionEvents[i].intersection + collisionEvents[i].normal * Offset; foreach (var effect in EffectsOnCollision) { if (effect == null) continue; if (spawned >= MaxSpawnsPerCollisionCall) break; GameObject instance = null; if (UsePooling) instance = GetPooledInstance(effect); else instance = Instantiate(effect, hitPos, Quaternion.identity) as GameObject; if (instance == null) continue; // Track as active so we can clean up if this emitter is destroyed activeInstances.Add(instance); // Position & rotation logic if (UseWorldSpacePosition) { instance.transform.position = hitPos; } else { // Keep world position consistent but do not parent to emitter to avoid accidental destruction. // Compute local position relative to emitter and set world position accordingly. var localPos = transform.InverseTransformPoint(hitPos); instance.transform.position = transform.TransformPoint(localPos); } if (UseFirePointRotation) { instance.transform.LookAt(transform.position); } else if (rotationOffset != Vector3.zero && useOnlyRotationOffset) { instance.transform.rotation = Quaternion.Euler(rotationOffset); } else { instance.transform.LookAt(collisionEvents[i].intersection + collisionEvents[i].normal); instance.transform.rotation *= Quaternion.Euler(rotationOffset); } // Activate and play particle systems inside the effect instance.SetActive(true); PlayParticleSystemsRecursive(instance.transform); // Return to pool after a delay (or destroy if pooling disabled) if (UsePooling) StartCoroutine(ReturnToPoolAfterDelay(effect, instance, DestroyTimeDelay)); else Destroy(instance, DestroyTimeDelay); spawned++; } } if (DestoyMainEffect == true) { Destroy(gameObject, DestroyTimeDelay +0.5f); } } // Play all particle systems in the spawned effect (in case of pooled ones they might be stopped) private void PlayParticleSystemsRecursive(Transform root) { var systems = root.GetComponentsInChildren(true); foreach (var s in systems) { // Restart the system try { s.Clear(); s.Play(); } catch { } } } // Pool helpers private GameObject GetPooledInstance(GameObject prefab) { if (prefab == null) return null; Queue q; if (!pools.TryGetValue(prefab, out q) || q == null) { q = new Queue(); pools[prefab] = q; } GameObject go = null; while (q.Count >0) { var candidate = q.Dequeue(); if (candidate != null) { go = candidate; break; } } if (go == null) { go = Instantiate(prefab, poolRoot); } // ensure under pool root so it survives emitter destruction go.transform.SetParent(poolRoot, true); return go; } private IEnumerator ReturnToPoolAfterDelay(GameObject prefab, GameObject instance, float delay) { if (instance == null) yield break; // clamp delay if (delay <0f) delay =0f; yield return new WaitForSeconds(delay); if (instance == null) yield break; // stop particle systems var systems = instance.GetComponentsInChildren(true); foreach (var s in systems) { try { s.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); } catch { } } // deactivate and return to pool instance.SetActive(false); // Remove from active tracking for this emitter if (activeInstances.Contains(instance)) activeInstances.Remove(instance); if (prefab == null) { Destroy(instance); yield break; } if (!pools.TryGetValue(prefab, out var q) || q == null) pools[prefab] = new Queue(); pools[prefab].Enqueue(instance); } void OnDestroy() { // Destroy all active instances that were spawned by this emitter if (activeInstances != null) { foreach (var inst in activeInstances) { if (inst != null) Destroy(inst); } activeInstances.Clear(); } // Destroy all pooled objects created by this emitter if (pools != null) { foreach (var kv in pools) { var q = kv.Value; if (q == null) continue; while (q.Count >0) { var go = q.Dequeue(); if (go != null) Destroy(go); } } pools.Clear(); } } }