Files
ichni_Creator_Studio/Assets/Dreamteck/Splines/Components/ParticleController.cs
SoulliesOfficial 87e11b6c8a 特效改
2025-08-09 05:39:32 -04:00

319 lines
14 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;
[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 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;
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 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;
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);
HashSet<uint> activeSeeds = new HashSet<uint>();
bool isLocal = _particleSystem.main.simulationSpace == ParticleSystemSimulationSpace.Local;
Transform particleSystemTransform = _particleSystem.transform;
for (int i = 0; i < _particleCount; i++)
{
uint seed = _particles[i].randomSeed; // 获取粒子的唯一ID
activeSeeds.Add(seed); // 记录存活的粒子
if (isLocal) TransformParticle(ref _particles[i], particleSystemTransform);
// 使用字典来检查粒子是否是新生儿这是100%可靠的
if (!_particleDataMap.ContainsKey(seed))
{
OnParticleBorn(i, seed);
}
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)
{
if (!activeSeeds.Contains(key)) keysToRemove.Add(key);
}
foreach (var key in keysToRemove)
{
_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, uint seed)
{
if (!_particleDataMap.TryGetValue(seed, out Particle particleData)) return; // 安全检查
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);
Vector3 resultRight = evalResult.right;
if (!is3D)
{
_particles[index].position = evalResult.position + extendDirection * particleData.initialOffset;
}
else
{
_particles[index].position = evalResult.position + 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;
}
}
_particles[index].position += resultRight * (finalOffset.x * evalResult.size)
+ evalResult.up * (finalOffset.y * evalResult.size);
_particles[index].velocity = evalResult.forward;
_particles[index].startColor = particleData.startColor * evalResult.color;
}
}
private void OnParticleBorn(int index, uint seed)
{
Particle newParticleData = 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);
}
else
{
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))
{
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;
}
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);
}
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 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;
}
}
}
}