Files
ichni_Creator_Studio/Assets/Dreamteck/Splines/Components/ParticleController.cs
2026-07-29 22:27:21 +08:00

352 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace Dreamteck.Splines
{
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Unity.Profiling;
[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 }
public enum PathNodeSizeMode { LegacyOffset, ApplyPathNodeSize }
public bool is3D;
public float width = 10f;
public Vector3 extendDirection = Vector3.right;
[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;
[HideInInspector]
public PathNodeSizeMode pathNodeSizeMode = PathNodeSizeMode.LegacyOffset;
private Dictionary<uint, Particle> _particleDataMap = new Dictionary<uint, Particle>();
private ParticleSystem.Particle[] _particles = new ParticleSystem.Particle[0];
//private float[] _initialOffset = new float[0];
//private Particle[] _controllers = new Particle[0];
private int _particleCount = 0;
private int _birthIndex = 0;
private int _particleDataFrame = 0;
private List<Vector4> _customParticleData = new List<Vector4>();
private readonly List<uint> _keysToRemove = new List<uint>();
private readonly Stack<Particle> _particleDataPool = new Stack<Particle>();
private static readonly ProfilerMarker ParticleControllerLateRunMarker = new ProfilerMarker("ParticleController.LateRun");
protected override void LateRun()
{
if (_particleSystem == null) return;
using (ParticleControllerLateRunMarker.Auto())
{
if (pauseWhenNotVisible)
{
if (_renderer == null)
{
_renderer = _particleSystem.GetComponent<ParticleSystemRenderer>();
}
if (!_renderer.isVisible) return;
}
int maxParticles = _particleSystem.main.maxParticles;
if (_particles.Length != maxParticles)
{
_particles = new ParticleSystem.Particle[maxParticles];
if (maxParticles > _particleDataMap.Count)
{
_particleDataMap = new Dictionary<uint, Particle>(maxParticles);
}
}
_particleCount = _particleSystem.GetParticles(_particles);
_particleSystem.GetCustomParticleData(_customParticleData, ParticleSystemCustomData.Custom1);
_particleDataFrame++;
bool isLocal = _particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.Local;
Transform particleSystemTransform = _particleSystem.transform;
for (int i = 0; i < _particleCount; i++)
{
uint seed = _particles[i].randomSeed; // 获取粒子的唯一ID
if (isLocal) TransformParticle(ref _particles[i], particleSystemTransform);
// 使用字典来检查粒子是否是新生儿这是100%可靠的
if (!_particleDataMap.TryGetValue(seed, out Particle particleData))
{
particleData = OnParticleBorn(i, seed);
}
particleData.lastSeenFrame = _particleDataFrame;
HandleParticle(i, particleData);
if (isLocal) InverseTransformParticle(ref _particles[i], particleSystemTransform);
}
// 清理字典中已经死亡的粒子数据,防止内存无限增长
if (_particleCount < _particleDataMap.Count)
{
_keysToRemove.Clear();
foreach (var pair in _particleDataMap)
{
if (pair.Value.lastSeenFrame != _particleDataFrame) _keysToRemove.Add(pair.Key);
}
foreach (var key in _keysToRemove)
{
_particleDataPool.Push(_particleDataMap[key]);
_particleDataMap.Remove(key);
}
}
_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);
}
protected override void Reset()
{
base.Reset();
updateMethod = UpdateMethod.LateUpdate;
if (_particleSystem == null) _particleSystem = GetComponent<ParticleSystem>();
}
void HandleParticle(int index, Particle particleData)
{
float lifePercent = _particles[index].remainingLifetime / _particles[index].startLifetime;
if (motionType == MotionType.FollowBackward || motionType == MotionType.FollowForward || motionType == MotionType.None)
{
Evaluate(particleData.GetSplinePercent(wrapMode, _particles[index], motionType), ref evalResult);
if (!is3D)
{
Vector3 initialOffset = extendDirection * particleData.initialOffset;
_particles[index].position = evalResult.position + GetInitialOffset(evalResult, initialOffset);
}
else
{
_particles[index].position = evalResult.position + GetInitialOffset(evalResult, particleData.threeDOffset);
}
if (apply3DRotation)
{
_particles[index].rotation3D = evalResult.rotation.eulerAngles;
}
Vector2 finalOffset = offset;
if (volumetric)
{
if (motionType != MotionType.None)
{
finalOffset += Vector2.Lerp(particleData.startOffset, particleData.endOffset, 1f - lifePercent);
finalOffset.x *= scale.x;
finalOffset.y *= scale.y;
}
else
{
finalOffset += particleData.startOffset;
}
}
if (finalOffset != Vector2.zero)
{
_particles[index].position += GetSamplePlaneOffset(evalResult, finalOffset);
}
_particles[index].velocity = evalResult.forward;
_particles[index].startColor = particleData.startColor * evalResult.color;
}
}
private static Vector3 GetSampleSpaceOffset(SplineSample sample, Vector3 offset)
{
return sample.right * (offset.x * sample.size)
+ sample.up * (offset.y * sample.size)
+ sample.forward * (offset.z * sample.size);
}
private Vector3 GetInitialOffset(SplineSample sample, Vector3 offset)
{
return pathNodeSizeMode == PathNodeSizeMode.ApplyPathNodeSize
? GetSampleSpaceOffset(sample, offset)
: offset;
}
private static Vector3 GetSamplePlaneOffset(SplineSample sample, Vector2 offset)
{
return sample.right * (offset.x * sample.size) + sample.up * (offset.y * sample.size);
}
private Particle OnParticleBorn(int index, uint seed)
{
Particle newParticleData = _particleDataPool.Count > 0 ? _particleDataPool.Pop() : new Particle();
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);
newParticleData.startColor = _particles[index].startColor;
newParticleData.startPercent = percent;
newParticleData.cycleSpeed = Random.Range(minCycles, maxCycles);
Vector2 circle = Vector2.zero;
if (volumetric)
{
if (emitFromShell) circle = Quaternion.AngleAxis(Random.Range(0f, 360f), Vector3.forward) * Vector2.right;
else circle = Random.insideUnitCircle;
}
if (!is3D)
{
newParticleData.initialOffset = Random.Range(-width, width);
newParticleData.threeDOffset = Vector3.zero;
}
else
{
newParticleData.initialOffset = 0f;
newParticleData.threeDOffset = new Vector3(
Random.Range(-width, width) * extendDirection.x,
Random.Range(-width, width) * extendDirection.y,
Random.Range(-width, width) * extendDirection.z);
}
newParticleData.startOffset = circle * 0.5f;
newParticleData.endOffset = Random.insideUnitCircle * 0.5f;
_particleDataMap.Add(seed, newParticleData);
if (!(motionType == MotionType.FollowForward || motionType == MotionType.FollowBackward))
{
Vector2 scaledStartOffset = new Vector2(newParticleData.startOffset.x * scale.x, newParticleData.startOffset.y * scale.y);
_particles[index].position = evalResult.position + GetSamplePlaneOffset(evalResult, scaledStartOffset);
}
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;
}
//HandleParticle(index);
return newParticleData;
}
public class Particle
{
internal float initialOffset;
internal Vector3 threeDOffset = Vector3.zero;
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 int lastSeenFrame = 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;
}
}
}
}