using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; using Random = UnityEngine.Random; using AssetKits.ParticleImage.Enumerations; using UnityEngine.Serialization; using PlayMode = AssetKits.ParticleImage.Enumerations.PlayMode; namespace AssetKits.ParticleImage { [AddComponentMenu("UI/Particle Image/Particle Image")] [RequireComponent(typeof(CanvasRenderer))] public sealed class ParticleImage : MaskableGraphic { [SerializeField] private ParticleImage _main; [SerializeField] private ParticleImage[] _children; /// /// Child emitters of this emitter. /// public ParticleImage[] children { get { return _children; } private set { _children = value; } } /// /// Root emitter of this system. /// public ParticleImage main { get { if (_main == null) _main = GetMain(); return _main; } private set { _main = value; } } /// /// Returns true if this emitter is the root emitter of this system. /// public bool isMain { get { return main == this; } } private RectTransform _canvasRect; public RectTransform canvasRect { get { return _canvasRect; } set { _canvasRect = value; } } [SerializeField] private Simulation _space = Simulation.Local; public Simulation space { get => _space; set { _space = value; } } [SerializeField] private TimeScale _timeScale = TimeScale.Normal; public TimeScale timeScale { get => _timeScale; set { _timeScale = value; } } [SerializeField] private Module _emitterConstraintEnabled = new Module(false); public bool emitterConstraintEnabled { get { return _emitterConstraintEnabled.enabled; } set { _emitterConstraintEnabled.enabled = value; } } [SerializeField] private Transform _emitterConstraintTransform; public Transform emitterConstraintTransform { get => _emitterConstraintTransform; set { _emitterConstraintTransform = value; } } [SerializeField] private EmitterShape _shape = EmitterShape.Circle; /// /// The type of shape to emit particles from. /// public EmitterShape shape { get => _shape; set => _shape = value; } [SerializeField] private SpreadType _spread = SpreadType.Random; /// /// The type of spread to use when emitting particles. /// public SpreadType spreadType { get => _spread; set => _spread = value; } [SerializeField] private float _spreadLoop = 1; /// /// Loop count for spread. /// public float spreadLoop { get => _spreadLoop; set => _spreadLoop = value; } [SerializeField] private float _startDelay = 0; /// /// Start delay in seconds. /// public float startDelay { get => _startDelay; set => _startDelay = value; } [SerializeField] private float _radius = 50; /// /// Radius of the circle shape to emit particles from. /// public float circleRadius { get => _radius; set => _radius = value; } [SerializeField] private float _width = 100; /// /// Width of the rectangle shape to emit particles from. /// public float rectWidth { get => _width; set => _width = value; } [SerializeField] private float _height = 100; /// /// Height of the rectangle shape to emit particles from. /// public float rectHeight { get => _height; set => _height = value; } [SerializeField] private float _angle = 45; /// /// Angle of the directional shape to emit particles from. /// public float directionAngle { get => _angle; set => _angle = value; } [SerializeField] private float _length = 100f; /// /// Length of the line shape to emit particles from. /// public float lineLength { get => _length; set => _length = value; } [SerializeField] private bool _fitRect; public bool fitRect { get { return _fitRect; } set { _fitRect = value; if(value) FitRect(); } } [SerializeField] private bool _emitOnSurface = true; /// /// Emit on the whole surface of the current shape. /// public bool emitOnSurface { get => _emitOnSurface; set => _emitOnSurface = value; } [SerializeField] private float _emitterThickness; /// /// Thickness of the shape's edge from which to emit particles if emitOnSurface is disabled. /// public float emitterThickness { get => _emitterThickness; set => _emitterThickness = value; } [SerializeField] private bool _loop = true; /// /// Determines whether the Particle Image is looping. /// public bool loop { get => _loop; set => _loop = value; } [SerializeField] private float _duration = 5f; /// /// The duration of the Particle Image in seconds /// public float duration { get => _duration; set => _duration = value; } [SerializeField] private PlayMode _playMode = PlayMode.OnAwake; public PlayMode PlayMode { get { return _playMode; } set { _playMode = value; if (isMain && children != null) { foreach (var particleImage in children) { particleImage._playMode = value; } } else if(!isMain) { main.PlayMode = value; } } } [SerializeField] private SeparatedMinMaxCurve _startSize = new SeparatedMinMaxCurve(40f); public SeparatedMinMaxCurve startSize { get => _startSize; set => _startSize = value; } [SerializeField] private ParticleSystem.MinMaxGradient _startColor = new ParticleSystem.MinMaxGradient(Color.white); public ParticleSystem.MinMaxGradient startColor { get => _startColor; set => _startColor = value; } [SerializeField] private ParticleSystem.MinMaxCurve _lifetime = new ParticleSystem.MinMaxCurve(1f); public ParticleSystem.MinMaxCurve lifetime { get => _lifetime; set => _lifetime = value; } [SerializeField] private ParticleSystem.MinMaxCurve _startSpeed = new ParticleSystem.MinMaxCurve(2f); public ParticleSystem.MinMaxCurve startSpeed { get => _startSpeed; set => _startSpeed = value; } [SerializeField] private ParticleSystem.MinMaxGradient _colorOverLifetime = new ParticleSystem.MinMaxGradient(new Gradient()); public ParticleSystem.MinMaxGradient colorOverLifetime { get => _colorOverLifetime; set => _colorOverLifetime = value; } [SerializeField] private ParticleSystem.MinMaxGradient _colorBySpeed = new ParticleSystem.MinMaxGradient(new Gradient()); public ParticleSystem.MinMaxGradient colorBySpeed { get => _colorBySpeed; set => _colorBySpeed = value; } [SerializeField] private SpeedRange _colorSpeedRange = new SpeedRange(0f, 1f); public SpeedRange colorSpeedRange { get => _colorSpeedRange; set => _colorSpeedRange = value; } [SerializeField] private SeparatedMinMaxCurve _sizeOverLifetime = new SeparatedMinMaxCurve(new AnimationCurve(new []{new Keyframe(0f,1f), new Keyframe(1f,1f)})); public SeparatedMinMaxCurve sizeOverLifetime { get => _sizeOverLifetime; set => _sizeOverLifetime = value; } [SerializeField] private SeparatedMinMaxCurve _sizeBySpeed = new SeparatedMinMaxCurve(new AnimationCurve(new []{new Keyframe(0f,1f), new Keyframe(1f,1f)})); public SeparatedMinMaxCurve sizeBySpeed { get => _sizeBySpeed; set => _sizeBySpeed = value; } [SerializeField] private SpeedRange _sizeSpeedRange = new SpeedRange(0f, 1f); public SpeedRange sizeSpeedRange { get => _sizeSpeedRange; set => _sizeSpeedRange = value; } [SerializeField] private SeparatedMinMaxCurve _startRotation = new SeparatedMinMaxCurve(0f); public SeparatedMinMaxCurve startRotation { get => _startRotation; set => _startRotation = value; } [SerializeField] private SeparatedMinMaxCurve _rotationOverLifetime = new SeparatedMinMaxCurve(0f); public SeparatedMinMaxCurve rotationOverLifetime { get => _rotationOverLifetime; set => _rotationOverLifetime = value; } [SerializeField] private SeparatedMinMaxCurve _rotationBySpeed = new SeparatedMinMaxCurve(new AnimationCurve(new []{new Keyframe(0f,1f), new Keyframe(1f,1f)})); public SeparatedMinMaxCurve rotationBySpeed { get => _rotationBySpeed; set => _rotationBySpeed = value; } [SerializeField] private SpeedRange _rotationSpeedRange = new SpeedRange(0f, 1f); public SpeedRange rotationSpeedRange { get => _rotationSpeedRange; set => _rotationSpeedRange = value; } [SerializeField] private ParticleSystem.MinMaxCurve _speedOverLifetime = new ParticleSystem.MinMaxCurve(1f); public ParticleSystem.MinMaxCurve speedOverLifetime { get => _speedOverLifetime; set => _speedOverLifetime = value; } [SerializeField] private bool _alignToDirection; /// /// Align particles based on their direction of travel. /// public bool alignToDirection { get => _alignToDirection; set => _alignToDirection = value; } [SerializeField] private ParticleSystem.MinMaxCurve _gravity = new ParticleSystem.MinMaxCurve(-9.81f); public ParticleSystem.MinMaxCurve gravity { get => _gravity; set => _gravity = value; } [SerializeField] private Module _targetModule = new Module(false); public bool attractorEnabled { get { return _targetModule.enabled; } set { _targetModule.enabled = value; } } [SerializeField] private Transform _attractorTarget; public Transform attractorTarget { get => _attractorTarget; set => _attractorTarget = value; } [SerializeField] private ParticleSystem.MinMaxCurve _toTarget = new ParticleSystem.MinMaxCurve(1f, new AnimationCurve(new []{new Keyframe(0f,0f), new Keyframe(1f,1f)})); public ParticleSystem.MinMaxCurve attractorLerp { get => _toTarget; set => _toTarget = value; } [SerializeField] private AttractorType _targetMode = AttractorType.Pivot; public AttractorType attractorType { get => _targetMode; set => _targetMode = value; } [SerializeField] private Module _noiseModule = new Module(false); public bool noiseEnabled { get { return _noiseModule.enabled; } set { _noiseModule.enabled = value; } } [SerializeField] private Module _gravityModule = new Module(false); public bool gravityEnabled { get { return _gravityModule.enabled; } set { _gravityModule.enabled = value; } } [SerializeField] private Module _vortexModule = new Module(false); public bool vortexEnabled { get { return _vortexModule.enabled; } set { _vortexModule.enabled = value; } } [SerializeField] private Module _velocityModule = new Module(false); public bool velocityEnabled { get { return _velocityModule.enabled; } set { _velocityModule.enabled = value; } } [SerializeField] private Simulation _velocitySpace; public Simulation velocitySpace { get => _velocitySpace; set => _velocitySpace = value; } [SerializeField] private SeparatedMinMaxCurve _velocityOverLifetime = new SeparatedMinMaxCurve(0f, true, false); public SeparatedMinMaxCurve velocityOverLifetime { get => _velocityOverLifetime; set => _velocityOverLifetime = value; } [SerializeField] private ParticleSystem.MinMaxCurve _vortexStrength; public ParticleSystem.MinMaxCurve vortexStrength { get => _vortexStrength; set => _vortexStrength = value; } [SerializeField] private Module _sheetModule = new Module(false); public bool textureSheetEnabled { get { return _sheetModule.enabled; } set { _sheetModule.enabled = value; } } [SerializeField] private Vector2Int _textureTile = Vector2Int.one; public Vector2Int textureTile { get => _textureTile; set => _textureTile = value; } [SerializeField] private SheetType _sheetType = SheetType.FPS; public SheetType textureSheetType { get => _sheetType; set => _sheetType = value; } [SerializeField] private ParticleSystem.MinMaxCurve _frameOverTime; public ParticleSystem.MinMaxCurve textureSheetFrameOverTime { get => _frameOverTime; set => _frameOverTime = value; } [SerializeField] private ParticleSystem.MinMaxCurve _startFrame = new ParticleSystem.MinMaxCurve(0f); public ParticleSystem.MinMaxCurve textureSheetStartFrame { get => _startFrame; set => _startFrame = value; } [SerializeField] private SpeedRange _frameSpeedRange = new SpeedRange(0f, 1f); public SpeedRange textureSheetFrameSpeedRange { get => _frameSpeedRange; set => _frameSpeedRange = value; } [SerializeField] private int _textureSheetFPS = 25; public int textureSheetFPS { get => _textureSheetFPS; set => _textureSheetFPS = value; } [SerializeField] private int _textureSheetCycles = 1; public int textureSheetCycles { get => _textureSheetCycles; set => _textureSheetCycles = value; } private List _particles = new List(); /// /// List of particles in the system. /// public List particles => _particles; [SerializeField] private float _rate = 50; /// /// The rate at which the emitter spawns new particles per second. /// public float rateOverTime { get => _rate; set => _rate = value; } [SerializeField] private float _rateOverLifetime = 0; /// /// The rate at which the emitter spawns new particles over emitter duration. /// public float rateOverLifetime { get => _rateOverLifetime; set => _rateOverLifetime = value; } [SerializeField] private float _rateOverDistance = 0; /// /// The rate at which the emitter spawns new particles over distance per pixel. /// public float rateOverDistance { get => _rateOverDistance; set => _rateOverDistance = value; } [SerializeField] private List _bursts = new List(); [FormerlySerializedAs("_trailRenderer")] [SerializeField] private ParticleTrailRenderer _particleTrailRenderer; public ParticleTrailRenderer particleTrailRenderer { get { if (trailsEnabled) { if (!_particleTrailRenderer) { _particleTrailRenderer = GetComponentInChildren(); if (!_particleTrailRenderer) { GameObject tr = new GameObject("Trails"); tr.transform.parent = transform; tr.transform.localPosition = Vector3.zero; tr.transform.localScale = Vector3.one; tr.transform.localEulerAngles = Vector3.zero; tr.AddComponent(); ParticleTrailRenderer r = tr.AddComponent(); _particleTrailRenderer = r; } } return _particleTrailRenderer; } else { return null; } } set { _particleTrailRenderer = value; } } [SerializeField] private Module _trailModule; /// /// The trails enabled. /// public bool trailsEnabled { get => _trailModule.enabled; set => _trailModule.enabled = value; } [SerializeField] private ParticleSystem.MinMaxCurve _trailWidth = new ParticleSystem.MinMaxCurve(1f,new AnimationCurve(new []{new Keyframe(0f,1f), new Keyframe(1f,0f)})); /// /// The width of the trail in pixels. /// public ParticleSystem.MinMaxCurve trailWidth { get => _trailWidth; set => _trailWidth = value; } [SerializeField] private float _trailLifetime = 1f; /// /// Trail lifetime in seconds /// public float trailLifetime { get => _trailLifetime; set => _trailLifetime = value; } [SerializeField] private float _minimumVertexDistance = 10f; /// /// Vertex distance in canvas pixels /// public float minimumVertexDistance { get => _minimumVertexDistance; set => _minimumVertexDistance = value; } [SerializeField] private ParticleSystem.MinMaxGradient _trailColorOverLifetime = new ParticleSystem.MinMaxGradient(Color.white); /// /// The color of the trail over its lifetime. /// public ParticleSystem.MinMaxGradient trailColorOverLifetime { get => _trailColorOverLifetime; set => _trailColorOverLifetime = value; } [SerializeField] private ParticleSystem.MinMaxGradient _trailColorOverTrail = new ParticleSystem.MinMaxGradient(Color.white); /// /// The color of the trail over the lifetime of the trail. /// public ParticleSystem.MinMaxGradient trailColorOverTrail { get => _trailColorOverTrail; set => _trailColorOverTrail = value; } [SerializeField] private bool _inheritParticleColor; public bool inheritParticleColor { get => _inheritParticleColor; set => _inheritParticleColor = value; } [SerializeField] private bool _dieWithParticle = false; public bool dieWithParticle { get => _dieWithParticle; set => _dieWithParticle = value; } [Range(0f,1f)] [SerializeField] private float _trailRatio = 1f; public float trailRatio { get => _trailRatio; set { _trailRatio = Mathf.Clamp01(value); } } private float _time; public float time => _time; private float _loopTimer; private float _t; private float _t2; private float _burstTimer; private Vector2 _position; private Noise _noise = new Noise(); public Noise noise { get => _noise; set => _noise = value; } [SerializeField] private int _noiseOctaves = 1; public int noiseOctaves { get => _noiseOctaves; set { _noiseOctaves = value; _noise.SetFractalOctaves(_noiseOctaves); } } [SerializeField] private float _noiseFrequency = 1f; public float noiseFrequency { get => _noiseFrequency; set { _noiseFrequency = value; _noise.SetFrequency(_noiseFrequency); } } [SerializeField] private float _noiseStrength = 1f; public float noiseStrength { get => _noiseStrength; set => _noiseStrength = value; } private bool _emitting; /// /// Determines if the particle system is emitting. /// public bool isEmitting { get { return _emitting;} private set { _emitting = value; } } private bool _playing; /// /// Determines if the particle system is playing. /// public bool isPlaying { get { return _playing;} private set { _playing = value; } } private bool _stopped; /// /// Determines whether the Particle System is stopped. /// public bool isStopped { get { return _stopped;} private set { _stopped = value; } } private bool _paused; /// /// Determines whether the Particle System is paused. /// public bool isPaused { get { return _paused;} private set { _paused = value; } } [SerializeField] private UnityEvent _onStart = new UnityEvent(); /// /// Called when the particle system starts. /// public UnityEvent onStart => _onStart; [SerializeField] private UnityEvent _onFirstParticleFinish = new UnityEvent(); /// /// Called when the first piece of a particle finishes. /// public UnityEvent onFirstParticleFinish => _onFirstParticleFinish; [SerializeField] private UnityEvent _onParticleFinish = new UnityEvent(); /// /// Called when any piece of a particle finishes. /// public UnityEvent onParticleFinish => _onParticleFinish; [SerializeField] private UnityEvent _onLastParticleFinish = new UnityEvent(); /// /// Called when the last piece of a particle finishes. /// public UnityEvent onLastParticleFinish => _onLastParticleFinish; [SerializeField] private UnityEvent _onStop = new UnityEvent(); /// /// Called when the particle system is stopped. /// public UnityEvent onStop => _onStop; private Vector3 _lastPosition; private Vector3 _deltaPosition; /// /// Delta position of the particle system. /// public Vector3 deltaPosition => _deltaPosition; private Camera _camera; private Camera camera { get { if (_camera == null) { _camera = Camera.main; } return _camera; } } private bool _firstParticleFinished; private int _orderPerSec; private int _orderOverLife; private int _orderOverDistance; public bool moduleEmitterFoldout; public bool moduleParticleFoldout; public bool moduleMovementFoldout; public bool moduleEventsFoldout; void Awake() { _noise.SetNoiseType(Noise.NoiseType.OpenSimplex2); _noise.SetFrequency(_noiseFrequency / 100f); _noise.SetFractalOctaves(_noiseOctaves); Clear(); if (PlayMode == PlayMode.OnAwake && Application.isPlaying) { Play(); } } public void OnEnable() { if (isMain) { children = GetChildren(); } if (fitRect) { FitRect(); } main = GetMain(); main.children = main.GetChildren(); _lastPosition = transform.position; if (canvas) { canvasRect = canvas.gameObject.GetComponent(); } if (PlayMode == PlayMode.OnEnable && Application.isPlaying) { Stop(true); Clear(); Play(); } RecalculateMasking(); RecalculateClipping(); SetAllDirty(); } public ParticleImage GetMain() { if (transform.parent) { if (transform.parent.TryGetComponent(out ParticleImage p)) { return p.GetMain(); } } return this; } /// /// Get all children of this Particle Image. /// /// /// A list of all children ParticleImage. /// public ParticleImage[] GetChildren() { if (transform.childCount <= 0) return null; var ch = GetComponentsInChildren().Where(t => t != this); if (ch.Any()) { return ch.ToArray(); } return null; } private void OnTransformChildrenChanged() { main = GetMain(); if(isMain) children = GetChildren(); if (Application.isEditor) { Stop(true); Clear(); Play(); } } void OnTransformParentChanged() { main = GetMain(); if(isMain) children = GetChildren(); if (Application.isEditor) { Stop(true); Clear(); Play(); } } /// /// Starts the particle system. /// public void Play() { main.DoPlay(); } private void DoPlay() { if (isMain && children != null) { foreach (var particleImage in children) { particleImage.DoPlay(); } } onStart.Invoke(); _time = 0f; _burstTimer = 0f; for (int i = 0; i < _bursts.Count; i++) { _bursts[i].used = false; } isEmitting = true; isPlaying = true; isPaused = false; isStopped = false; } /// /// Pauses the particle system. /// public void Pause() { main.DoPause(); } private void DoPause() { if (isMain && children != null) { foreach (var particleImage in children) { particleImage.DoPause(); } } isEmitting = false; isPlaying = false; isPaused = true; } /// /// Stops playing the Particle System. /// public void Stop() { Stop(false); } /// /// Stops playing the Particle System using the supplied stop behaviour. /// /// /// If true, the particle system will be cleared and all emitted particles will be destroyed. /// public void Stop(bool stopAndClear) { main.DoStop(stopAndClear); } private void DoStop(bool stopAndClear) { if (isMain && children != null) { foreach (var particleImage in children) { particleImage.DoStop(stopAndClear); } } _orderPerSec = 0; _orderOverLife = 0; _orderOverDistance = 0; for (int i = 0; i < _bursts.Count; i++) { _bursts[i].used = false; } if (stopAndClear) { isStopped = true; isPlaying = false; Clear(); } isEmitting = false; if (isPaused) { isPaused = false; isStopped = true; isPlaying = false; Clear(); } for (int i = 0; i < _bursts.Count; i++) { _bursts[i].used = false; } _firstParticleFinished = false; SetVerticesDirty(); SetMaterialDirty(); if (particleTrailRenderer) { particleTrailRenderer.SetVerticesDirty(); particleTrailRenderer.SetMaterialDirty(); } } /// /// Remove all particles from the Particle System. /// public void Clear() { main.DoClear(); } private void DoClear() { if (isMain && children != null) { foreach (var particleImage in children) { particleImage.DoClear(); } } for (int i = 0; i < _bursts.Count; i++) { _bursts[i].used = false; } _time = 0; _burstTimer = 0; _particles.Clear(); SetVerticesDirty(); SetMaterialDirty(); if (particleTrailRenderer) { particleTrailRenderer.SetVerticesDirty(); particleTrailRenderer.SetMaterialDirty(); } } void Update() { Animate(); } private void Animate() { if (isPlaying) { _deltaPosition = transform.position - _lastPosition; if (_emitterConstraintTransform && _emitterConstraintEnabled.enabled) { if (_emitterConstraintTransform is RectTransform) { transform.position = _emitterConstraintTransform.position; } else { Vector3 canPos; Vector3 viewportPos = camera.WorldToViewportPoint(_emitterConstraintTransform.position); canPos = new Vector3(viewportPos.x.Remap(0.5f, 1.5f, 0f, canvasRect.rect.width), viewportPos.y.Remap(0.5f, 1.5f, 0f, canvasRect.rect.height), 0); canPos = canvasRect.transform.TransformPoint(canPos); canPos = transform.parent.InverseTransformPoint(canPos); transform.localPosition = canPos; } } if (isMain) { _time += _timeScale == TimeScale.Normal ? Time.deltaTime : Time.unscaledDeltaTime; } else { _time = main.time; } _loopTimer += _timeScale == TimeScale.Normal ? Time.deltaTime : Time.unscaledDeltaTime; _burstTimer += _timeScale == TimeScale.Normal ? Time.deltaTime : Time.unscaledDeltaTime; SetVerticesDirty(); SetMaterialDirty(); if (particleTrailRenderer) { particleTrailRenderer.SetVerticesDirty(); particleTrailRenderer.SetMaterialDirty(); } } if (isEmitting) { //Emit per second if(_rate > 0) { if ((_time < (_duration + _startDelay) || _loop) && _time > _startDelay) { float dur = 1f / _rate; _t += _timeScale == TimeScale.Normal ? Time.deltaTime : Time.unscaledDeltaTime; while(_t >= dur) { _t -= dur; _orderPerSec++; _particles.Insert(0,GenerateParticle(_orderPerSec,1, null)); } } } //Emit over lifetime if (_rateOverLifetime > 0) { if ((_time < (_duration + _startDelay) || _loop) && _time > _startDelay) { float dur = _duration / _rateOverLifetime; _t2 += _timeScale == TimeScale.Normal ? Time.deltaTime : Time.unscaledDeltaTime; while(_t2 >= dur) { _t2 -= dur; _orderOverLife++; _particles.Insert(0,GenerateParticle(_orderOverLife,2, null)); } } } //Emit over distance if (_rateOverDistance > 0) { if (_deltaPosition.magnitude > 1f / _rateOverDistance) { _orderOverDistance++; _particles.Insert(0,GenerateParticle(_orderOverDistance,3, null)); _lastPosition = transform.position; } } //Emit bursts if (_bursts != null) { for (int i = 0; i < _bursts.Count; i++) { if (_burstTimer > _bursts[i].time + _startDelay && _bursts[i].used == false) { for (int j = 0; j < _bursts[i].count; j++) { _particles.Insert(0,GenerateParticle(j,0, _bursts[i])); } _bursts[i].used = true; } } } if (_loop && _burstTimer >= _duration) { _burstTimer = 0; for (int i = 0; i < _bursts.Count; i++) { _bursts[i].used = false; } } if (_time >= _duration + _startDelay && !_loop) { isEmitting = false; } if(_loop && _loopTimer >= _duration + _startDelay) { _loopTimer = 0; _orderPerSec = 0; _orderOverLife = 0; _orderOverDistance = 0; } } if (isPlaying && _particles.Count <= 0 && !isEmitting && isMain) { if (canStop()) { onStop.Invoke(); Stop(true); } } } /// /// Add a burst to the particle system /// public void AddBurst(float time, int count) { _bursts.Add(new Burst(time, count)); } /// /// Remove burst at index /// public void RemoveBurst(int index) { _bursts.RemoveAt(index); } /// /// Set particle burst at index /// public void SetBurst(int index, float time, int count) { if (_bursts.Count > index) { _bursts[index] = new Burst(time, count); } } private bool canStop() { if (children != null) { return children.All(x => x.isEmitting == false && x._particles.Count <= 0); } else { return true; } } private Vector2 GetPointOnRect(float angle, float w, float h) { // Calculate the sine and cosine of the angle var sine = Mathf.Sin(angle); var cosine = Mathf.Cos(angle); // Calculate the x and y coordinates of the point // based on the sign of sine and cosine. // If sine is positive, the y coordinate is half the height of the rectangle. // If sine is negative, the y coordinate is negative half the height of the rectangle. // Similarly, if cosine is positive, the x coordinate is half the width of the rectangle. // If cosine is negative, the x coordinate is negative half the width of the rectangle. float dy = sine > 0 ? h / 2 : h / -2; float dx = cosine > 0 ? w / 2 : w / -2; // Check if the slope of the line between the origin and the point is steeper in // the x direction or in the y direction. If it is steeper in the x direction, // adjust the y coordinate so that the point is on the edge of the rectangle. // If it is steeper in the y direction, adjust the x coordinate instead. if (Mathf.Abs(dx * sine) < Mathf.Abs(dy * cosine)) { dy = (dx * sine) / cosine; } else { dx = (dy * cosine) / sine; } // Return the point as a Vector2 object return new Vector2(dx, dy); } private Particle GenerateParticle(int order, int source, Burst burst) { float angle = 0; if (source == 0)//Burst { angle = order * (360f / burst.count) * _spreadLoop; } else if(source == 1)//Rate per Sec { angle = order * (360f / (_rate)) / _duration * _spreadLoop; } else if(source == 2)//Rate over Life { angle = order * (360f / (_rateOverLifetime)) * _spreadLoop; } else if(source == 3)//Rate over Distance { angle = order * (360f / (_rateOverDistance)) / _duration * _spreadLoop; } // Create new particle at system's starting position Vector2 p = Vector2.zero; switch (_shape) { case EmitterShape.Point: p = _position; break; case EmitterShape.Circle: if (_emitOnSurface) { if (_spread == SpreadType.Random) { p = _position + (Random.insideUnitCircle * _radius); } else { p = (RotateOnAngle(new Vector3(0,Random.Range(0f,1f),0), angle) * _radius); } } else { if (_spread == SpreadType.Random) { Vector2 r = Random.insideUnitCircle.normalized; p = _position + Vector2.Lerp(r * _radius, r * (_radius - _emitterThickness), Random.value); } else { p = (RotateOnAngle(new Vector3(0,1f,0), angle) * (UnityEngine.Random.Range(_radius, _radius - _emitterThickness))); } } break; case EmitterShape.Rectangle: if (_emitOnSurface) { if(_spread == SpreadType.Uniform) { p = Vector2.Lerp(GetPointOnRect(angle*Mathf.Deg2Rad, _width, _height), Vector2.one, Random.value); } else { p = _position + new Vector2(Random.Range(-_width / 2, _width / 2), Random.Range(-_height / 2, _height / 2)); } } else { float a = Random.Range(0f, 360f); if(_spread == SpreadType.Uniform) { a = angle; } p = Vector2.Lerp(GetPointOnRect(a*Mathf.Deg2Rad, _width, _height), GetPointOnRect(a*Mathf.Deg2Rad, _width-_emitterThickness, _height-_emitterThickness), Random.value); } break; case EmitterShape.Line: if(_spread == SpreadType.Uniform) { p = _position + new Vector2(Mathf.Repeat(angle, 361).Remap(0,360,-_length/2, _length/2), 0); } else { p = _position + new Vector2(Random.Range(-_length/2, _length/2), 0); } break; case EmitterShape.Directional: p = _position; break; } if (space == Simulation.World) { p = Quaternion.Euler(transform.eulerAngles) * (p); } Vector2 v = Vector2.zero; switch (_shape) { case EmitterShape.Point: if (_spread == SpreadType.Uniform) { v = RotateOnAngle(new Vector3(0,1f,0), angle) * _startSpeed.Evaluate(Random.value, Random.value);; } else { v = Random.insideUnitCircle.normalized * _startSpeed.Evaluate(Random.value, Random.value); } break; case EmitterShape.Circle: v = p.normalized * _startSpeed.Evaluate(Random.value, Random.value); break; case EmitterShape.Rectangle: v = p.normalized * _startSpeed.Evaluate(Random.value, Random.value); break; case EmitterShape.Line: v = (space == Simulation.World ? transform.up : Vector3.up) * _startSpeed.Evaluate(Random.value, Random.value); break; case EmitterShape.Directional: float a = 0; if (space == Simulation.World) { if (_spread == SpreadType.Uniform) { a = Mathf.Repeat(angle, 361).Remap(0,360, -_angle / 2, _angle / 2) - transform.eulerAngles.z; } else { a = Random.Range(-_angle / 2, _angle / 2) - transform.eulerAngles.z; } } else { if (_spread == SpreadType.Uniform) { a = Mathf.Repeat(angle, 361).Remap(0, 360, -_angle / 2, _angle / 2); } else { a = Random.Range(-_angle/2, _angle/2); } } v = RotateOnAngle(a) * _startSpeed.Evaluate(Random.value, Random.value); break; } float sLerp = Random.value; Particle part = new Particle( this, p, _startRotation.separated ? new Vector3(_startRotation.xCurve.Evaluate(Random.value, Random.value), _startRotation.yCurve.Evaluate(Random.value, Random.value),_startRotation.zCurve.Evaluate(Random.value, Random.value)) : new Vector3(0, 0,_startRotation.mainCurve.Evaluate(Random.value, Random.value)), v, _startColor.Evaluate(Random.value, Random.value), _startSize.separated ? new Vector3(_startSize.xCurve.Evaluate(Random.value, Random.value), _startSize.yCurve.Evaluate(Random.value, Random.value),_startSize.zCurve.Evaluate(Random.value, Random.value)) : new Vector3(_startSize.mainCurve.Evaluate(sLerp, sLerp), _startSize.mainCurve.Evaluate(sLerp, sLerp),_startSize.mainCurve.Evaluate(sLerp, sLerp)), _lifetime.Evaluate(Random.value, Random.value)); return part; } public Material material { get { return m_Material; } set { if (m_Material == value) { return; } m_Material = value; SetVerticesDirty(); SetMaterialDirty(); } } [SerializeField] private Texture m_Texture; public Texture texture { get { return m_Texture; } set { if (m_Texture == value) { return; } m_Texture = value; SetVerticesDirty(); SetMaterialDirty(); } } protected override void OnRectTransformDimensionsChange() { base.OnRectTransformDimensionsChange(); if (fitRect) { FitRect(); } if (!_emitOnSurface) { switch (_shape) { case EmitterShape.Circle: _emitterThickness = Mathf.Clamp(_emitterThickness, 0f, _radius); break; case EmitterShape.Rectangle: _emitterThickness = Mathf.Clamp(_emitterThickness, 0f, rectTransform.sizeDelta.x rectTransform.rect.height) { _radius = rectTransform.rect.height/2; } else { _radius = rectTransform.rect.width/2; } break; // If the emitter has a rectangle shape, set the width and height of the emitter // to the width and height of the RectTransform. case EmitterShape.Rectangle: _width = rectTransform.rect.width; _height = rectTransform.rect.height; break; // If the emitter has a line shape, set the length of the emitter to the width // of the RectTransform. case EmitterShape.Line: _length = rectTransform.rect.width; break; } } public override Texture mainTexture { get { return m_Texture == null ? s_WhiteTexture : m_Texture; } } protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); for (int i = 0; i < _particles.Count; i++) { _particles[i].Animate(); _particles[i].Render(vh); } } void LateUpdate() { for (int i = 0; i < _particles.Count; i++) { if (_particles[i].TimeSinceBorn > _particles[i].Lifetime && _particles[i].trailPoints.Count <= 3) { onParticleFinish.Invoke(); _particles.RemoveAt(i); if (_firstParticleFinished == false) { _firstParticleFinished = true; onFirstParticleFinish.Invoke(); } if (particles.Count < 1) { onLastParticleFinish.Invoke(); } } } } private Vector3 RotateOnAngle(float angle){ float rad = angle * Mathf.Deg2Rad; Vector3 position = new Vector3(Mathf.Sin( rad ), Mathf.Cos( rad ), 0); return position * 1f; } private Vector3 RotateOnAngle(Vector3 p, float angle){ return Quaternion.Euler(new Vector3(0,0,angle)) * p; } /// /// Converts world position to viewport position using the current camera /// /// World position /// Viewport position public Vector3 WorldToViewportPoint(Vector3 position) { Vector3 pos = camera.WorldToViewportPoint(position); return pos; } } [Serializable] public class Burst { public float time = 0; public int count = 1; public bool used = false; public Burst(float time, int count) { this.time = time; this.count = count; } } [Serializable] public struct SpeedRange { public float from; public float to; public SpeedRange(float from, float to) { this.from = from; this.to = to; } } [Serializable] public struct Module { public bool enabled; public Module(bool enabled) { this.enabled = enabled; } } [Serializable] public struct SeparatedMinMaxCurve { [SerializeField] private bool separable; public bool separated; public ParticleSystem.MinMaxCurve mainCurve; public ParticleSystem.MinMaxCurve xCurve; public ParticleSystem.MinMaxCurve yCurve; public ParticleSystem.MinMaxCurve zCurve; public SeparatedMinMaxCurve(float startValue, bool separated = false, bool separable = true) { mainCurve = new ParticleSystem.MinMaxCurve(startValue); xCurve = new ParticleSystem.MinMaxCurve(startValue); yCurve = new ParticleSystem.MinMaxCurve(startValue); zCurve = new ParticleSystem.MinMaxCurve(startValue); this.separated = separated; this.separable = separable; } public SeparatedMinMaxCurve(AnimationCurve startValue, bool separated = false, bool separable = true) { mainCurve = new ParticleSystem.MinMaxCurve(1f,new AnimationCurve(startValue.keys)); xCurve = new ParticleSystem.MinMaxCurve(1f, new AnimationCurve(startValue.keys)); yCurve = new ParticleSystem.MinMaxCurve(1f,new AnimationCurve(startValue.keys)); zCurve = new ParticleSystem.MinMaxCurve(1f,new AnimationCurve(startValue.keys)); this.separated = separated; this.separable = separable; } } public static class Extensions { public static float Remap (this float value, float from1, float to1, float from2, float to2) { float v = (value - from1) / (to1 - from1) * (to2 - from2) + from2; if(float.IsNaN(v) || float.IsInfinity(v)) return 0; return v; } } } namespace AssetKits.ParticleImage.Enumerations { public enum EmitterShape { Point, Circle, Rectangle, Line, Directional } public enum SpreadType { Random, Uniform } public enum Simulation { Local, World } public enum AttractorType { Pivot, Surface } public enum PlayMode { None, OnEnable, OnAwake } public enum SheetType { Lifetime, Speed, FPS } public enum TimeScale { Unscaled, Normal } }