Files
ichni_Official/Assets/Dreamteck/Splines/Components/ParticleController.cs

314 lines
13 KiB
C#
Raw Normal View History

2025-06-03 02:42:28 -04:00
namespace Dreamteck.Splines
{
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[ExecuteInEditMode]
[AddComponentMenu("Dreamteck/Splines/Users/Particle Controller")]
public class ParticleController : SplineUser
{
public ParticleSystem particleSystemComponent
{
get { return _particleSystem; }
set {
_particleSystem = value;
_renderer = _particleSystem.GetComponent<ParticleSystemRenderer>();
}
}
[SerializeField]
[HideInInspector]
private ParticleSystem _particleSystem;
private ParticleSystemRenderer _renderer;
public enum EmitPoint { Beginning, Ending, Random, Ordered }
public enum MotionType { None, UseParticleSystem, FollowForward, FollowBackward, ByNormal, ByNormalRandomized }
public enum Wrap { Default, Loop }
2025-07-10 08:42:30 -04:00
public bool is3D;
public float width = 10f;
public Vector3 extendDirection = Vector3.right;
2025-06-03 02:42:28 -04:00
[HideInInspector]
public bool pauseWhenNotVisible = false;
[HideInInspector]
public Vector2 offset = Vector2.zero;
[HideInInspector]
public bool volumetric = false;
[HideInInspector]
public bool emitFromShell = false;
[HideInInspector]
public bool apply3DRotation = false;
[HideInInspector]
public Vector2 scale = Vector2.one;
[HideInInspector]
public EmitPoint emitPoint = EmitPoint.Beginning;
[HideInInspector]
public MotionType motionType = MotionType.UseParticleSystem;
[HideInInspector]
public Wrap wrapMode = Wrap.Default;
[HideInInspector]
public float minCycles = 1f;
[HideInInspector]
public float maxCycles = 1f;
2025-07-10 08:42:30 -04:00
private Dictionary<uint, Particle> _particleDataMap = new Dictionary<uint, Particle>();
2025-06-03 02:42:28 -04:00
private ParticleSystem.Particle[] _particles = new ParticleSystem.Particle[0];
2025-07-10 08:42:30 -04:00
//private float[] _initialOffset = new float[0];
//private Particle[] _controllers = new Particle[0];
2025-06-03 02:42:28 -04:00
private int _particleCount = 0;
private int _birthIndex = 0;
private List<Vector4> _customParticleData = new List<Vector4>();
protected override void LateRun()
{
if (_particleSystem == null) return;
if (pauseWhenNotVisible)
{
if (_renderer == null)
{
_renderer = _particleSystem.GetComponent<ParticleSystemRenderer>();
}
if (!_renderer.isVisible) return;
}
int maxParticles = _particleSystem.main.maxParticles;
2025-07-10 08:42:30 -04:00
2025-06-03 02:42:28 -04:00
if (_particles.Length != maxParticles)
{
_particles = new ParticleSystem.Particle[maxParticles];
2025-07-10 08:42:30 -04:00
if (maxParticles > _particleDataMap.Count)
2025-06-03 02:42:28 -04:00
{
2025-07-10 08:42:30 -04:00
_particleDataMap = new Dictionary<uint, Particle>(maxParticles);
2025-06-03 02:42:28 -04:00
}
}
2025-07-10 08:42:30 -04:00
2025-06-03 02:42:28 -04:00
_particleCount = _particleSystem.GetParticles(_particles);
_particleSystem.GetCustomParticleData(_customParticleData, ParticleSystemCustomData.Custom1);
2025-07-10 08:42:30 -04:00
HashSet<uint> activeSeeds = new HashSet<uint>();
2025-06-03 02:42:28 -04:00
bool isLocal = _particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.Local;
Transform particleSystemTransform = _particleSystem.transform;
for (int i = 0; i < _particleCount; i++)
{
2025-07-10 08:42:30 -04:00
uint seed = _particles[i].randomSeed; // 获取粒子的唯一ID
activeSeeds.Add(seed); // 记录存活的粒子
if (isLocal) TransformParticle(ref _particles[i], particleSystemTransform);
// 使用字典来检查粒子是否是新生儿这是100%可靠的
if (!_particleDataMap.ContainsKey(seed))
2025-06-03 02:42:28 -04:00
{
2025-07-10 08:42:30 -04:00
OnParticleBorn(i, seed);
2025-06-03 02:42:28 -04:00
}
2025-07-10 08:42:30 -04:00
HandleParticle(i, seed);
if (isLocal) InverseTransformParticle(ref _particles[i], particleSystemTransform);
}
// 清理字典中已经死亡的粒子数据,防止内存无限增长
if (activeSeeds.Count < _particleDataMap.Count)
{
List<uint> keysToRemove = new List<uint>();
foreach (var key in _particleDataMap.Keys)
2025-06-03 02:42:28 -04:00
{
2025-07-10 08:42:30 -04:00
if (!activeSeeds.Contains(key)) keysToRemove.Add(key);
2025-06-03 02:42:28 -04:00
}
2025-07-10 08:42:30 -04:00
foreach (var key in keysToRemove)
2025-06-03 02:42:28 -04:00
{
2025-07-10 08:42:30 -04:00
_particleDataMap.Remove(key);
2025-06-03 02:42:28 -04:00
}
}
_particleSystem.SetCustomParticleData(_customParticleData, ParticleSystemCustomData.Custom1);
_particleSystem.SetParticles(_particles, _particleCount);
}
void TransformParticle(ref ParticleSystem.Particle particle, Transform trs)
{
particle.position = trs.TransformPoint(particle.position);
if (apply3DRotation)
{
}
particle.velocity = trs.TransformDirection(particle.velocity);
}
void InverseTransformParticle(ref ParticleSystem.Particle particle, Transform trs)
{
particle.position = trs.InverseTransformPoint(particle.position);
particle.velocity = trs.InverseTransformDirection(particle.velocity);
}
2025-07-10 08:42:30 -04:00
2025-06-03 02:42:28 -04:00
protected override void Reset()
{
base.Reset();
updateMethod = UpdateMethod.LateUpdate;
if (_particleSystem == null) _particleSystem = GetComponent<ParticleSystem>();
}
2025-07-10 08:42:30 -04:00
void HandleParticle(int index, uint seed)
2025-06-03 02:42:28 -04:00
{
2025-07-10 08:42:30 -04:00
if (!_particleDataMap.TryGetValue(seed, out Particle particleData)) return; // 安全检查
2025-06-03 02:42:28 -04:00
float lifePercent = _particles[index].remainingLifetime / _particles[index].startLifetime;
if (motionType == MotionType.FollowBackward || motionType == MotionType.FollowForward || motionType == MotionType.None)
{
2025-07-10 08:42:30 -04:00
Evaluate(particleData.GetSplinePercent(wrapMode, _particles[index], motionType), ref evalResult);
2025-06-03 02:42:28 -04:00
Vector3 resultRight = evalResult.right;
2025-07-10 08:42:30 -04:00
if (!is3D)
{
_particles[index].position = evalResult.position + extendDirection * particleData.initialOffset;
}
else
{
_particles[index].position = evalResult.position + particleData.threeDOffset;
}
2025-06-03 02:42:28 -04:00
if (apply3DRotation)
{
_particles[index].rotation3D = evalResult.rotation.eulerAngles;
}
2025-07-10 08:42:30 -04:00
2025-06-03 02:42:28 -04:00
Vector2 finalOffset = offset;
if (volumetric)
{
if (motionType != MotionType.None)
{
2025-07-10 08:42:30 -04:00
finalOffset += Vector2.Lerp(particleData.startOffset, particleData.endOffset, 1f - lifePercent);
2025-06-03 02:42:28 -04:00
finalOffset.x *= scale.x;
finalOffset.y *= scale.y;
} else
{
2025-07-10 08:42:30 -04:00
finalOffset += particleData.startOffset;
2025-06-03 02:42:28 -04:00
}
}
2025-07-10 08:42:30 -04:00
_particles[index].position += resultRight * (finalOffset.x * evalResult.size)
+ evalResult.up * (finalOffset.y * evalResult.size);
2025-06-03 02:42:28 -04:00
_particles[index].velocity = evalResult.forward;
2025-07-10 08:42:30 -04:00
_particles[index].startColor = particleData.startColor * evalResult.color;
2025-06-03 02:42:28 -04:00
}
}
2025-07-10 08:42:30 -04:00
private void OnParticleBorn(int index, uint seed)
2025-06-03 02:42:28 -04:00
{
2025-07-10 08:42:30 -04:00
Particle newParticleData = new Particle();
2025-06-03 02:42:28 -04:00
Vector4 custom = _customParticleData[index];
custom.w = 1;
_customParticleData[index] = custom;
double percent = 0.0;
float emissionRate = Mathf.Lerp(_particleSystem.emission.rateOverTime.constantMin, _particleSystem.emission.rateOverTime.constantMax, 0.5f);
float expectedParticleCount = emissionRate * _particleSystem.main.startLifetime.constantMax;
_birthIndex++;
if (_birthIndex > expectedParticleCount)
{
_birthIndex = 0;
}
switch (emitPoint)
{
case EmitPoint.Beginning: percent = 0f; break;
case EmitPoint.Ending: percent = 1f; break;
case EmitPoint.Random: percent = Random.Range(0f, 1f); break;
case EmitPoint.Ordered: percent = expectedParticleCount > 0 ? (float)_birthIndex / expectedParticleCount : 0f; break;
}
Evaluate(percent, ref evalResult);
2025-07-10 08:42:30 -04:00
newParticleData.startColor = _particles[index].startColor;
newParticleData.startPercent = percent;
2025-06-03 02:42:28 -04:00
2025-07-10 08:42:30 -04:00
newParticleData.cycleSpeed = Random.Range(minCycles, maxCycles);
2025-06-03 02:42:28 -04:00
Vector2 circle = Vector2.zero;
if (volumetric)
{
if (emitFromShell) circle = Quaternion.AngleAxis(Random.Range(0f, 360f), Vector3.forward) * Vector2.right;
else circle = Random.insideUnitCircle;
}
2025-07-10 08:42:30 -04:00
if (!is3D)
{
newParticleData.initialOffset = Random.Range(-width, width);
}
else
{
newParticleData.threeDOffset = new Vector3(Random.Range(-width, width), Random.Range(-width, width), 0);
}
newParticleData.startOffset = circle * 0.5f;
newParticleData.endOffset = Random.insideUnitCircle * 0.5f;
_particleDataMap.Add(seed, newParticleData);
if (!(motionType == MotionType.FollowForward || motionType == MotionType.FollowBackward))
{
Vector3 right = Vector3.Cross(evalResult.forward, evalResult.up);
_particles[index].position = evalResult.position +
right * newParticleData.startOffset.x * evalResult.size * scale.x +
evalResult.up * newParticleData.startOffset.y * evalResult.size * scale.y;
}
2025-06-03 02:42:28 -04:00
float forceX = _particleSystem.forceOverLifetime.x.constantMax;
float forceY = _particleSystem.forceOverLifetime.y.constantMax;
float forceZ = _particleSystem.forceOverLifetime.z.constantMax;
if (_particleSystem.forceOverLifetime.randomized)
{
forceX = Random.Range(_particleSystem.forceOverLifetime.x.constantMin, _particleSystem.forceOverLifetime.x.constantMax);
forceY = Random.Range(_particleSystem.forceOverLifetime.y.constantMin, _particleSystem.forceOverLifetime.y.constantMax);
forceZ = Random.Range(_particleSystem.forceOverLifetime.z.constantMin, _particleSystem.forceOverLifetime.z.constantMax);
}
float time = _particles[index].startLifetime - _particles[index].remainingLifetime;
Vector3 forceDistance = new Vector3(forceX, forceY, forceZ) * 0.5f * (time * time);
float startSpeed = _particleSystem.main.startSpeed.constantMax;
if (motionType == MotionType.ByNormal)
{
_particles[index].position += evalResult.up * startSpeed * (_particles[index].startLifetime - _particles[index].remainingLifetime);
_particles[index].position += forceDistance;
_particles[index].velocity = evalResult.up * startSpeed + new Vector3(forceX, forceY, forceZ) * time;
}
else if (motionType == MotionType.ByNormalRandomized)
{
Vector3 normal = Quaternion.AngleAxis(Random.Range(0f, 360f), evalResult.forward) * evalResult.up;
_particles[index].position += normal * startSpeed * (_particles[index].startLifetime - _particles[index].remainingLifetime);
_particles[index].position += forceDistance;
_particles[index].velocity = normal * startSpeed + new Vector3(forceX, forceY, forceZ) * time;
}
2025-07-10 08:42:30 -04:00
//HandleParticle(index);
2025-06-03 02:42:28 -04:00
}
public class Particle
{
2025-07-10 08:42:30 -04:00
internal float initialOffset;
internal Vector3 threeDOffset = Vector3.zero;
2025-06-03 02:42:28 -04:00
internal Vector2 startOffset = Vector2.zero;
internal Vector2 endOffset = Vector2.zero;
internal float cycleSpeed = 0f;
internal Color startColor = Color.white;
internal double startPercent = 0.0;
internal double GetSplinePercent(Wrap wrap, ParticleSystem.Particle particle, MotionType motionType)
{
float lifePercent = particle.remainingLifetime / particle.startLifetime;
if(motionType == MotionType.FollowBackward)
{
lifePercent = 1f - lifePercent;
}
switch (wrap)
{
case Wrap.Default: return DMath.Clamp01(startPercent + (1f - lifePercent) * cycleSpeed);
case Wrap.Loop:
double loopPoint = startPercent + (1.0 - lifePercent) * cycleSpeed;
if(loopPoint > 1.0) loopPoint -= Mathf.FloorToInt((float)loopPoint);
return loopPoint;
}
return 0.0;
}
}
}
}