Files
WhiteMan_Unity2D/Assets/Hovl Studio/HSFiles/Scripts/HS_ParticleCollisionInstance.cs

259 lines
7.9 KiB
C#

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<ParticleCollisionEvent> collisionEvents = new List<ParticleCollisionEvent>();
// Pooling
private static Transform poolRoot;
private Dictionary<GameObject, Queue<GameObject>> pools = new Dictionary<GameObject, Queue<GameObject>>();
// Track instances that were spawned by this emitter and not yet returned to pool
private HashSet<GameObject> activeInstances = new HashSet<GameObject>();
void OnValidate()
{
if (DestroyTimeDelay <0f) DestroyTimeDelay =0f;
if (MaxSpawnsPerCollisionCall <1) MaxSpawnsPerCollisionCall =1;
}
void Start()
{
part = GetComponent<ParticleSystem>();
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<ParticleSystem>();
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<ParticleSystem>(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<GameObject> q;
if (!pools.TryGetValue(prefab, out q) || q == null)
{
q = new Queue<GameObject>();
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<ParticleSystem>(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<GameObject>();
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();
}
}
}