// 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;
}
}