Tutorial 4 - Particle System Events

Top  Previous  Next

In this tutorial we will look at Particle System Events, which are used to update the particle system properties at run-time.


Load the Tutorial 4 project and open up MyParticleSystem.cs.



What are Particle System Events?


Particle System Events are the exact same as Particle Events, except that they are used to update the Particle System's properties rather than the Particle's properties, and these events get added to the ParticleSystemEvents object, rather than the ParticleEvents object. The four types of Events that were used as Particle Events are also supported as Particle System Events, however there are some differences as is described in this tutorial.


Information on both Particle and Particle System Events can be found in the Events section.


Any functions that you define and intend to use as a Particle System Event functions must follow the UpdateParticleSystemDelegate prototype, which is:


       public void FunctionName(float fElapsedTimeInSeconds);


Because Particle System Update functions (used with Particle System Events) operate on the particle system's properties, not the particle's properties, no Particle is supplied in the function parameters; only the elapsed time since the last particle system update is provided.  As a guideline, any Particle System Update functions that you define should have their name begin with UpdateParticleSystem.  This is not mandatory, but it is the convention that DPSF uses for its Default classes, and it makes locating these Particle System Update functions in Visual Studio's Intellisense easier since they will be grouped together alphabetically.



Every Time Events


Like with Particle Events, Particle System Every Time Events fire every time the particle system is updated.  However, instead of firing the event on every active particle in the particle system, the event only fires once, since there is only one particle system to update.


So if there is some operation that you need performed every time the particle system is updated, you could put that code into a Particle System Update function and add it to the ParticleSystemEvents object as an Every Time Event.  An example would be updating the Emitter's Position and Rotation each frame; however the Emitter's Position and Rotation are updated automatically by DPSF so you do not need to.  Another example might be implementing some particle system logic, such as orienting the particle system towards some other object in the 3D world, or making the Emitter follow the object.


In MyParticleSystem.cs locate the UpdateParticleSystemChangeParticleColorRandomly() Particle System Update function:


       public void UpdateParticleSystemChangeParticleColorRandomly(float fElapsedTimeInSeconds)


          // If we randomly should change the color now

          if (RandomNumber.Next(0, 60) == 30)


               msNewParticleColor = DPSFHelper.RandomColor();




This function changes the Color variable sNewParticleColor at random intervals.  sNewParticleColor is defined near the top of the MyParticleSystem class as a class variable:


       Color msNewParticleColor = Color.Red;


When new particles are initialized (using the Particle Initialization Function), the Color of the new particle is set to whatever color is stored in the sNewParticleColor variable.  The Every Time Event pointing to this Particle System Update function is added in the LoadParticleSystem() function using the line:




So even though the UpdateParticleSystemChangeParticleColorRandomly() Particle System Update function is called every time the particle system is updated, the value of the msNewParticleColor variable is not changed every time.  Because the function randomly picks when the msNewParticleColor variable should be updated, the Color of the new particles changes at random intervals.



One Time Events


One Time Events are available as Particle System Events, but are really only useful if you want the update to happen the next time the particle system is updated (rather than immediately), or if you want the update to happen before or after another Event (i.e. specify the ExecutionOrder), or if you need to know how much time has elapsed since the last update.  If you don't care that the update happens immediately and don't need to know how much time has elapsed since the last update, then you could just call the function that the One Time Event would be calling directly, rather than setting up a One Time Event to call the function.


For example, if you wanted to double the particle system's Simulation Speed when the X key is pressed, you could create a Particle System Update function such as this:


       public void UpdateParticleSystemDoubleSimulationSpeed(float fElapsedTimeInSeconds)


           SimulationSpeed = 2.0f;



Then when the X key is pressed you would call this function:


       public void DoubleSimulationSpeed()





This function calls the UpdateParticleSystemDoubleSimulationSpeed() function the next time the particle system is updated by using a One Time Event.  Since changing the Simulation Speed doesn't require knowing how much time has elapsed since the last update, and we don't care when it is called in relation to the other Particle System Update functions, we could have just created a simple function to double the simulation speed and called it directly.  Let's use this method to reset the Simulation Speed to its default value of 1.0.


So all we would need to do to reset the Simulation Speed to 1.0 is call the following function when the C key is pressed:


       public void NormalSimulationSpeed()


           SimulationSpeed = 1.0f;



MyParticleSystem.cs shows these 3 functions, and they are called during the simulation when the X and C keys are pressed.


One thing to note is that when we double the Simulation Speed, the Every Time Events are not called twice as often; instead just their fElapsedTimeInSeconds parameter is doubled.  This is why the Color of the new Particles is not changed more often when the Simulation Speed is doubled; since the UpdateParticleSystemChangeParticleColorRandomly() function is called the same number of times regardless of the Simulation Speed.



Timed and Normalized Timed Particle System Events


For Particle Events, each Particle has Lifetime and Elapsed Time (and corresponding Normalized Elapsed Time) properties which are used to fire the Timed and Normalized Timed Events.  DPSF Particle Systems also have these properties, but they are found within the ParticleSystemEvents.LifetimeData property.  So to set a particle system's Lifetime to be one second, you would need to do:


       ParticleSystemEvents.LifetimeData.Lifetime = 1.0f;


The Particle System's Lifetime has one extra feature that the Particle's Lifetime data doesn't, which is the EndOfLifeOption that lets you specify what should happen when the particle system's Elapsed Time reaches the Lifetime.  Possible values for this property include Nothing (nothing happens, the particle system keeps aging as normal), Repeat (the particle system's Lifetime is reset to 0.0), and Destroy (the particle system's Destroy() function is called, killing the particle system).


Tutorial 4's LoadParticleSystem() function in MyParticleSystem.cs shows how to set this property:


       ParticleSystemEvents.LifetimeData.EndOfLifeOption = CParticleSystemEvents.EParticleSystemEndOfLifeOptions.Repeat;


The LoadParticleSystem() function then specifies that the emitter should toggle on and off every 0.5 seconds using the following code:


       ParticleSystemEvents.AddTimedEvent(0.0f, UpdateParticleSystemEmitParticlesAutomaticallyOn);

     ParticleSystemEvents.AddTimedEvent(0.5f, UpdateParticleSystemEmitParticlesAutomaticallyOff);


The UpdateParticleSystemEmitParticlesAutomaticallyOn() / Off() Particle System Update functions are provided by the Default Particle System classes, and they turn the Emitter's EmitParticlesAutomatically property on and off respectfully.  So what will happen here is that when the particle system simulation starts running it will have an ElapsedTime of 0.0, causing the first Event to fire and the emitter to be turned on.  Then after 0.5 seconds the second Event will fire, turning the emitter off.  Once the particle system's ElapsedTime reaches it's Lifetime (i.e. 1.0 second), the ElapsedTime will be reset to 0.0 (since we are using an EndOfLifeOption of Repeat), causing the first Event to fire again and the emitter to be turned back on.  This cycle will continue to repeat forever.  When you run Tutorial 4's code you should notice that particles are not emitted constantly, they are emitted in 0.5 second intervals.


The Normalized Timed Events work similarly.  Tutorial 4 defines the Particle System Update function:


       public void UpdateParticleSystemSwapTexture(float fElapsedTimeInSeconds)


          // If the Star Texture is being used

          if (Texture.Name.Equals("Textures/Star9"))




          // Else the Sun Texture is being used







This function will toggle the Texture being used to display the particles between the Sun3 texture and the Star9 texture.  Tutorial 4 adds this as a Normalized Timed Event in the LoadParticleSystem() function with the following code:


       ParticleSystemEvents.AddNormalizedTimedEvent(1.0f, UpdateParticleSystemSwapTexture);


This will cause the particle system's Texture to be changed every time the particle system's ElapsedTime reaches its Lifetime (i.e. every 1 second in this Tutorial example).



Removing all Events before adding any


In the LoadParticleSystem() function, you should notice that before adding any Particle or Particle System Events, all of the Particle and Particle System Events are removed using the lines:





This is done to ensure that Events are not added twice if the the LoadParticleSystem() function happens to be called again.


You may create several different functions to load the particle system in different ways, and may switch between them at run-time.  For example, the tutorial provides a LoadParticleSystem2() function that is called when the V key is pressed:


       public void LoadParticleSystem2()


          // Set the Particle Initialization Function

           ParticleInitializationFunction = InitializeParticleProperties;



          // Remove all Events first so that none are added twice if this function is called again





          // Allow the Particle's Position, Rotation, and Transparency to be updated each frame






          // Set the Particle System's Lifetime and what should happen when it reaches its Lifetime

           ParticleSystemEvents.LifetimeData.EndOfLifeOption = CParticleSystemEvents.EParticleSystemEndOfLifeOptions.Repeat;

           ParticleSystemEvents.LifetimeData.Lifetime = 2.0f;


          // Set the Particle System's Emitter to toggle on for 0.5 seconds and off for 1.5 seconds

           ParticleSystemEvents.AddTimedEvent(0.0f, UpdateParticleSystemEmitParticlesAutomaticallyOn);

           ParticleSystemEvents.AddTimedEvent(0.5f, UpdateParticleSystemEmitParticlesAutomaticallyOff);


          // Change Textures every time the Particle System's Lifetime is reached

           ParticleSystemEvents.AddNormalizedTimedEvent(1.0f, UpdateParticleSystemSwapTexture);



          // Setup the Emitter

           Emitter.ParticlesPerSecond = 50;

           Emitter.PositionData.Position = new Vector3(0, 0, 0);



This function removes all of the Events added by LoadParticleSystem() and then adds its own.  The original LoadParticleSystem() function will be called when the B key is pressed, restoring the particle system to its original behaviour.  Both of these functions use the same Particle Initialization Function, although you can specify different ones if you choose.  The differences between LoadParticleSystem() and LoadParticleSystem2() is that LoadParticleSystem() does not rotate the particles and has a Lifetime of 1 second, where LoadParticleSystem2() does rotate the particles and has a Lifetime of 2 seconds.  Also, the LoadParticleSystem() function uses the Every Time Event described above to randomly change the Color of new particles, and LoadParticleSystem2() doesn't.  Press the V and B keys during the simulation to switch between using these two functions to load different particle system behaviours.





There is nothing special about the name of the LoadParticleSystem() function; you can name it anything you want.  By specifying different LoadParticleSystem() type of functions you can create many different particle systems within a single particle system class.  For example, if you created a FireParticleSystem class, you could have LoadFireBallParticleSystem() and LoadFireWallParticleSystem() functions to create two different types of fire effects.  You would then just need to create two instances of the FireParticleSystem class and have them call the appropriate LoadXParticleSystem() function.


If you prefer though you could also create FireBallParticleSystem and FireWallParticleSystem classes, keeping them totally separate.  The first method may require less work since the particle systems could share the same Particle and Particle System Update functions.  However, if you have a single class capable of loading many different particle systems, the class may become very large and confusing; putting each type of particle system in its own class may help keep the particle systems more simple and coherent.  Which method you choose to use is personal preference.