// Copyright (c) Craftwork Games. All rights reserved. // Licensed under the MIT license. // See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Xna.Framework; using MonoGame.Extended.Particles.Primitives; namespace MonoGame.Extended.Particles; /// /// Represents a complete particle effect composed of multiple emitters. /// /// /// The class serves as a container for one or more instances, /// allowing for complex visual effects that combine different types of particle behaviors and appearances. /// /// Effects can be positioned, rotated, and scaled as a single unit, with all contained emitters being affected /// by these transformations. Effects also provide methods for triggering all emitters simultaneously. /// public class ParticleEffect : IDisposable { private float _nextAutoTrigger; /// /// Gets or sets the name of this effect, used for identification and debugging. /// public string Name; /// /// Gets or sets the position of this effect in 2D space. /// /// /// This position is used as the reference point for all emitters in the effect. /// When the effect is updated, this position is passed to each emitter's update method. /// public Vector2 Position; /// /// Gets or sets the rotation of this effect, in radians. /// /// /// This property can be used to rotate the entire effect around its position. /// Note that rotation is not automatically applied to emitters and must be handled by the rendering system. /// public float Rotation; /// /// Gets or sets the scale factor of this effect. /// /// /// This property can be used to uniformly or non-uniformly scale the entire effect. /// Note that scaling is not automatically applied to emitters and must be handled by the rendering system. /// public Vector2 Scale; /// /// A value indicating whether this particle effect should automatically trigger its particle emitters. /// /// /// When , all emitters of this will be triggered at the same /// based on the . When , users will need to manually call /// the method to trigger emitters. /// public bool AutoTrigger; /// /// The frequency, in seconds, at which this automatically triggers emitters. /// /// /// If is , this value is ignored. /// public float AutoTriggerFrequency; /// /// Gets or sets the collection of emitters that compose this effect. /// /// /// Each emitter in this collection contributes to the overall visual appearance of the effect, /// potentially with different behaviors, textures, and particle properties. /// public List Emitters; /// /// Gets a value indicating whether this has been disposed. /// /// if the effect has been disposed; otherwise, . public bool IsDisposed { get; private set; } /// /// Gets the total number of active particles across all emitters in this effect. /// /// The sum of for all emitters in the effect. public int ActiveParticles { get { return Emitters.Sum(t => t.ActiveParticles); } } /// /// Initializes a new instance of the class with the specified name. /// /// The name of the effect, used for identification and debugging. /// /// This constructor initializes the effect with default position, rotation, and scale, /// and creates an empty collection of emitters. /// public ParticleEffect(string name) { Name = name; Position = Vector2.Zero; Rotation = 0.0f; Scale = Vector2.One; Emitters = new List(); AutoTrigger = true; AutoTriggerFrequency = 1.0f; } /// /// Finalizes an instance of the class. /// ~ParticleEffect() { Dispose(false); } /// /// Advances the effect's state rapidly to simulate it having been active for a period of time. /// /// The position at which to simulate the effect. /// The total time, in seconds, to simulate. /// The time interval, in seconds, between simulated triggers. /// /// This method is useful for pre-filling a scene with particles that appear to have been emitted /// over time, rather than starting with an empty effect. /// public void FastForward(Vector2 position, float seconds, float triggerPeriod) { float time = 0.0f; while (time < seconds) { Update(triggerPeriod); Trigger(position); time += triggerPeriod; } } /// /// Updates the state of all emitters in this effect. /// /// The timing values for the current update cycle. /// /// Thrown if this method is called after the effect has been disposed. /// public void Update(GameTime gameTime) { Update((float)gameTime.ElapsedGameTime.TotalSeconds); } /// /// Updates the state of all emitters in this effect. /// /// The elapsed time, in seconds, since the last update. /// /// Thrown if this method is called after the effect has been disposed. /// public void Update(float elapsedSeconds) { ObjectDisposedException.ThrowIf(IsDisposed, typeof(ParticleBuffer)); if (AutoTrigger) { _nextAutoTrigger -= elapsedSeconds; if (_nextAutoTrigger <= 0) { Trigger(); _nextAutoTrigger += AutoTriggerFrequency; } } for (int i = 0; i < Emitters.Count; i++) { Emitters[i].Update(elapsedSeconds, Position); } } /// /// Triggers all emitters in this effect at the effect's current position. /// public void Trigger() { Trigger(Position); } /// /// Triggers all emitters in this effect at the specified position. /// /// The position in 2D space at which to trigger the emitters. /// The layer depth at which to render the emitted particles. /// /// Thrown if this method is called after the effect has been disposed. /// public void Trigger(Vector2 position, float layerDepth = 0) { ObjectDisposedException.ThrowIf(IsDisposed, typeof(ParticleBuffer)); for (int i = 0; i < Emitters.Count; i++) { Emitters[i].Trigger(position, layerDepth); } } /// /// Triggers all emitters in this effect along a line segment. /// /// The line segment along which to distribute triggered particles. /// The layer depth at which to render the emitted particles. /// /// Thrown if this method is called after the effect has been disposed. /// public void Trigger(LineSegment line, float layerDepth) { ObjectDisposedException.ThrowIf(IsDisposed, typeof(ParticleBuffer)); for (int i = 0; i < Emitters.Count; i++) { Emitters[i].Trigger(line, layerDepth); } } /// /// Creates a new instance of the class from a file. /// /// The path to the file containing the serialized effect data. /// A new instance with properties and emitters as defined in the file. /// /// This method is not yet implemented. /// /// /// This method is intended to deserialize effect data from a file, but has not been implemented in this version. /// public static ParticleEffect FromFile(string path) { throw new NotImplementedException(); } /// /// Creates a new instance of the class from a stream. /// /// The stream containing the serialized effect data. /// A new instance with properties and emitters as defined in the stream. /// /// This method is not yet implemented. /// /// /// This method is intended to deserialize effect data from a stream, but has not been implemented in this version. /// public static ParticleEffect FromStream(Stream stream) { throw new NotImplementedException(); } /// /// Releases all resources used by the . /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the resources used by the . /// /// to release both managed and unmanaged resources; /// to release only unmanaged resources. private void Dispose(bool disposing) { if (IsDisposed) { return; } if (disposing) { for (int i = 0; i < Emitters.Count; i++) { Emitters[i].Dispose(); } } IsDisposed = true; } /// /// Returns a string that represents the current effect. /// /// The of this effect. public override string ToString() { return Name; } }