#region File Description //----------------------------------------------------------------------------- // ParticleSystem.cs // // Microsoft XNA Community Game Platform // Copyright (C) Microsoft Corporation. All rights reserved. //----------------------------------------------------------------------------- #endregion #region Using Statements using System; using System.Xml.Serialization; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Content; #endregion namespace NetRumble { /// /// A single-emitter particle system within a larger effect. /// public class ParticleSystem { #region Description Data /// /// The name of the particle system. /// private string name = "DefaultParticleSystem"; /// /// The number of particles in this system. /// private int particleCount = 256; #endregion #region Graphics Data /// /// The current position of the system, relative to the effect. /// private Vector2 position = Vector2.Zero; /// /// The tint of the particles in the system. /// private Vector4 color = Microsoft.Xna.Framework.Color.White.ToVector4(); /// /// The content name of the texture used with this system. /// private string textureName = "default_particle"; /// /// The texture used with this system. /// private Texture2D texture = null; /// /// The center of the texture used with this system. /// /// This is derived from the texture, calculated and stored. private Vector2 textureOrigin; /// /// The blending mode for this system. /// private SpriteBlendMode blendMode = SpriteBlendMode.AlphaBlend; #endregion #region Status Data /// /// If true, the particle system is currently active. /// private bool active = false; [XmlIgnore()] public bool Active { get { return (active || (timeRemaining > 0.0f)); } } /// /// The amount of time that the particle system generates particles. /// private float duration = float.MaxValue; /// /// The time remaining for the particle system's generation. /// private float timeRemaining = float.MaxValue; /// /// The initial delay before the particle system starts generating particles. /// /// Used for offsetting the start of particle systems. private float initialDelay = 0f; /// /// The amount of time left on the initial delay. /// private float initialDelayRemaining = 0f; #endregion #region Particle Creation Data /// /// The number of particles that this system releases per second. /// private int particlesPerSecond = 128; /// /// The number of seconds between particle releases. /// /// This is derived from particlesPerSecond. private float releaseRate = 0.25f; /// /// The amount of time since the last particle was emitted. /// private float releaseTimer = 0f; /// /// The minimum lifetime possible for new particles. /// private float durationMinimum = 1f; /// /// The maximum lifetime possible for new particles. /// private float durationMaximum = 1f; /// /// The minimum initial velocity of new particles. /// private float velocityMinimum = 16f; /// /// The maximum initial velocity of new particles. /// private float velocityMaximum = 32f; /// /// The minimum acceleration of new particles. /// private float accelerationMinimum = 0f; /// /// The maximum acceleration of new particles. /// private float accelerationMaximum = 0f; /// /// The minimum initial scale of new particles. /// private float scaleMinimum = 1f; /// /// The maximum initial scale of new particles. /// private float scaleMaximum = 1f; /// /// The minimum initial opacity of new particles. /// private float opacityMinimum = 1f; /// /// The maximum initial opacity of new particles. /// private float opacityMaximum = 1f; /// /// The minimum release angle of new particles from the center. /// private float releaseAngleMinimum = 0f; /// /// The maximum release angle of new particles from the center. /// private float releaseAngleMaximum = 360f; /// /// The minimum release distance of new particles from the center. /// private float releaseDistanceMinimum = 0f; /// /// The maximum release distance of new particles from the center. /// private float releaseDistanceMaximum = 0f; #endregion #region Particle Updating Data /// /// The angular velocity of particles in this system. /// private float angularVelocity = 0f; /// /// The change in the scale per second for particles in this system. /// private float scaleDeltaPerSecond = 0f; /// /// The change in the opacity per second for particles in this system. /// private float opacityDeltaPerSecond = 0f; #endregion #region Particle Cache /// /// The cache of all particle objects in this system. /// private ParticleCache particles; #endregion #region Initialization Methods /// /// Construct a new particle system. /// public ParticleSystem() { } /// /// Clone a particle system. /// /// A clone of this particle system. public ParticleSystem Clone() { ParticleSystem clone = new ParticleSystem(); clone.name = this.name; clone.particleCount = this.particleCount; clone.position = this.position; clone.color = this.color; clone.textureName = this.textureName; clone.blendMode = this.blendMode; clone.duration = this.duration; clone.initialDelay = this.initialDelay; clone.particlesPerSecond = this.particlesPerSecond; clone.releaseRate = this.releaseRate; clone.durationMinimum = this.durationMinimum; clone.durationMaximum = this.durationMaximum; clone.velocityMinimum = this.velocityMinimum; clone.velocityMaximum = this.velocityMaximum; clone.accelerationMinimum = this.accelerationMinimum; clone.accelerationMaximum = this.accelerationMaximum; clone.scaleMinimum = this.scaleMinimum; clone.scaleMaximum = this.scaleMaximum; clone.opacityMinimum = this.opacityMinimum; clone.opacityMaximum = this.opacityMaximum; clone.releaseAngleMinimum = this.releaseAngleMinimum; clone.releaseAngleMaximum = this.releaseAngleMaximum; clone.releaseDistanceMinimum = this.releaseDistanceMinimum; clone.releaseDistanceMaximum = this.releaseDistanceMaximum; clone.angularVelocity = this.angularVelocity; clone.scaleDeltaPerSecond = this.scaleDeltaPerSecond; clone.opacityDeltaPerSecond = this.opacityDeltaPerSecond; return clone; } /// /// Initialize the particle system. /// /// The content manager that owns the texture. public virtual void Initialize(ContentManager content) { // calculate the release rate releaseRate = 1.0f / (float)particlesPerSecond; // create the cache particles = new ParticleCache(particleCount); // load the texture try { texture = content.Load(textureName); } catch (ContentLoadException) { texture = content.Load("Textures/Particles/defaultParticle"); } // calculate the origin on the texture textureOrigin = new Vector2(texture.Width / 2f, texture.Height / 2f); // allow us to start updating and drawing active = true; } /// /// Resets the particle system. /// public virtual void Reset() { // reset the cache particles.Reset(); // reset the timers timeRemaining = duration; initialDelayRemaining = initialDelay; // allow us to start updating and drawing active = true; } #endregion #region Updating Methods /// /// Update the particle system. /// /// The amount of elapsed time, in seconds. public virtual void Update(float elapsedTime) { // if the system isn't active, dont' update at all if (!Active) return; // update the initial delay if (initialDelayRemaining > 0.0f) { initialDelayRemaining -= elapsedTime; return; } // generate new particles GenerateParticles(elapsedTime); // update the existing particles UpdateParticles(elapsedTime); // update the active flag, based on if there are any used particles active = particles.UsedCount > 0; } /// /// Generate new particles into the system. /// /// The amount of elapsed time, in seconds. private void GenerateParticles(float elapsedTime) { if (timeRemaining <= 0.0f) { return; } // update the timer timeRemaining -= elapsedTime; // release some particles if it's time releaseTimer += elapsedTime; while (releaseTimer >= releaseRate) { // only get new particles if you can Particle particle = particles.GetNextParticle(); if (particle == null) { break; } else { // initialize the new particle InitializeParticle(particle); // reduce the release timer for the release rate of a particle releaseTimer -= releaseRate; } } } /// /// Update all of the individual particles in this system. /// /// The amount of elapsed time, in seconds. private void UpdateParticles(float elapsedTime) { for (int i = 0; i < particles.Particles.Length; ++i) { if (particles.Particles[i].TimeRemaining > 0.0f) { // update the timer on the particle particles.Particles[i].TimeRemaining -= elapsedTime; // if the particle just timed out on this update, // add it to the freed-list. if (particles.Particles[i].TimeRemaining <= 0.0f) { particles.ReleaseParticle(particles.Particles[i]); continue; } // update the particle particles.Particles[i].Update(elapsedTime, angularVelocity, scaleDeltaPerSecond, opacityDeltaPerSecond); } } } /// /// Initialize a new particle using the values in this system. /// /// The particle to be initialized. private void InitializeParticle(Particle particle) { // safety-check the parameter if (particle == null) { throw new ArgumentNullException("particle"); } // set the time remaining on the new particle particle.TimeRemaining = RandomMath.RandomBetween(durationMinimum, durationMaximum); // generate a random direction Vector2 direction = RandomMath.RandomDirection(releaseAngleMinimum, releaseAngleMaximum); // set the graphics data on the new particle particle.Position = position + direction * RandomMath.RandomBetween(releaseDistanceMinimum, releaseDistanceMaximum); particle.Velocity = direction * RandomMath.RandomBetween(velocityMinimum, velocityMaximum); if (particle.Velocity.LengthSquared() > 0f) { particle.Acceleration = direction * RandomMath.RandomBetween(accelerationMinimum, accelerationMaximum); } else { particle.Acceleration = Vector2.Zero; } particle.Rotation = RandomMath.RandomBetween(0f, MathHelper.TwoPi); particle.Scale = RandomMath.RandomBetween(scaleMinimum, scaleMaximum); particle.Opacity = RandomMath.RandomBetween(opacityMinimum, opacityMaximum); } #endregion #region Drawing Methods /// /// Draw the particle system. /// /// The SpriteBatch object used to draw. public void Draw(SpriteBatch spriteBatch) { // only draw if we're active if (!Active) return; // draw each particle for (int p = 0; p < particles.Particles.Length; ++p) { Particle particle = particles.Particles[p]; if (particle.TimeRemaining > 0.0f) { color.W = particle.Opacity; spriteBatch.Draw(texture, particle.Position, null, new Color(color), particle.Rotation, textureOrigin, particle.Scale, SpriteEffects.None, 1f); } } } #endregion #region Control Methods /// /// Stop the particle system. /// /// /// If true, particles are no longer drawn or updated. /// Otherwise, only generation is halted. /// public void Stop(bool immediately) { // halt generation timeRemaining = 0.0f; // halt updating/drawing of the particles if requested if (immediately) { active = false; } } #endregion #region Serialization Interface public string Name { get { return name; } set { name = value; } } public int ParticleCount { get { return particleCount; } set { particleCount = value; } } public Vector2 Position { get { return position; } set { position = value; } } public Vector4 Color { get { return color; } set { color = value; } } public string TextureName { get { return textureName; } set { textureName = value; } } public SpriteBlendMode BlendMode { get { return blendMode; } set { blendMode = value; } } public float Duration { get { return duration; } set { duration = value; } } public float InitialDelay { get { return initialDelay; } set { initialDelay = value; } } public int ParticlesPerSecond { get { return particlesPerSecond; } set { particlesPerSecond = value; } } public float DurationMinimum { get { return durationMinimum; } set { durationMinimum = value; } } public float DurationMaximum { get { return durationMaximum; } set { durationMaximum = value; } } public float VelocityMinimum { get { return velocityMinimum; } set { velocityMinimum = value; } } public float VelocityMaximum { get { return velocityMaximum; } set { velocityMaximum = value; } } public float AccelerationMinimum { get { return accelerationMinimum; } set { accelerationMinimum = value; } } public float AccelerationMaximum { get { return accelerationMaximum; } set { accelerationMaximum = value; } } public float ScaleMinimum { get { return scaleMinimum; } set { scaleMinimum = value; } } public float ScaleMaximum { get { return scaleMaximum; } set { scaleMaximum = value; } } public float OpacityMinimum { get { return opacityMinimum; } set { opacityMinimum = value; } } public float OpacityMaximum { get { return opacityMaximum; } set { opacityMaximum = value; } } public float ReleaseAngleMinimum { get { return releaseAngleMinimum; } set { releaseAngleMinimum = value; } } public float ReleaseAngleMaximum { get { return releaseAngleMaximum; } set { releaseAngleMaximum = value; } } public float ReleaseDistanceMinimum { get { return releaseDistanceMinimum; } set { releaseDistanceMinimum = value; } } public float ReleaseDistanceMaximum { get { return releaseDistanceMaximum; } set { releaseDistanceMaximum = value; } } public float AngularVelocity { get { return angularVelocity; } set { angularVelocity = value; } } public float ScaleDeltaPerSecond { get { return scaleDeltaPerSecond; } set { scaleDeltaPerSecond = value; } } public float OpacityDeltaPerSecond { get { return opacityDeltaPerSecond; } set { opacityDeltaPerSecond = value; } } #endregion } }