Tutorial 3 - Particle Events
|Top Previous Next|
In this tutorial we will look at Particle Events, which are used to update the particle properties and control particles at run-time.
Load the Tutorial 3 project and open up MyParticleSystem.cs.
What are Particle Events?
Particle Events are basically function pointers, similar to the Particle Initialization Function, except that they are used to update a particle's properties during the particle's Lifetime, instead of when the particle is born and added to the simulation. So Particle Events control the updating of all of the particles' properties from one frame to the next.
There are 4 types of Events: Every Time Events, One Time Events, Timed Events, and Normalized Timed Events, each of which are described here.
Any functions that you define and intend to use as a Particle Event functions must follow the UpdateParticleDelegate prototype, which is:
public void FunctionName(Particle cParticle, float fElapsedTimeInSeconds); where Particle is the particle class that the particle system is using for its particles.
Every Time Events
Every Time Events are used to do updates that need to happen every frame. This includes things like updating a particle's position according to its velocity, and interpolating a particle's color between a birth color and death color as it ages. Here is an example of what a function used to update a particle's position according to its velocity might look like:
public void UpdateParticlePositionUsingVelocity(DefaultTexturedQuadParticle cParticle, float fElapsedTimeInSeconds)
cParticle.Position += cParticle.Velocity * fElapsedTimeInSeconds;
Like the Particle Initialization functions, Particle Event functions provide the particle to be updated as a parameter. In addition, Particle Event functions also supply the amount of time that has elapsed since the last time the particle system was updated. As a guideline, any Particle Update functions (used with Particle Events) that you define should have their name begin with UpdateParticle. This is not mandatory, but it is the convention that DPSF uses for its Default classes, and it makes locating these Particle Update functions in Visual Studio's Intellisense easier since they will be grouped together alphabetically.
You will notice here that this function assumes that our Particle class has both a Position and Velocity property defined. All of the Default Particle System classes define their particles to have a Position, Velocity, and Acceleration property, as well as several others. The Default classes also define several Particle Update functions to update these properties. For example, the UpdateParticlePositionUsingVelocity() function shown above is provided by the Default classes, so you don't have to write it yourself.
Now even though our Particles have Position and Velocity properties, and we have a function defined to update a particle's Position according to its Velocity, if we tried to run the particle system simulation the particles would not move. This is because we haven't told the particle system to use that function to update the particle properties yet. We do this by adding the function to the ParticleEvents object, which is located in the LoadParticleSystem() function. Also, because we want the particles' position to be updated every frame, we add it as an Every Time Event. So if you look in the LoadParticleSystem() function you should find the following line:
In Tutorial 2 if you tried setting the Friction or External Force property for the particles you would not have seen a change in the particles' behaviour. This would have been because in addition to setting the particle's Friction / External Force, you would have also needed to add the Particle Update function to the ParticleEvents object. So if you are ever making changes to particle properties, but not seeing a difference in the simulation, be sure that you have added the Particle Update function as a Particle Event.
Specifying the execution order of the Events
Often times several Particle Update function might operate on the same Particle properties, and you may require one Event to fire (i.e. execute the function it points to) before another. This can be accomplished by providing the ExecutionOrder of the Event when adding it to the ParticleEvents object.
The LoadParticleSystem() function in the tutorial shows this bit of code:
// Allow the Particle's Velocity, Rotational Velocity, Color, and Transparency to be updated each frame
// This function must be executed after the Color Lerp function as the Color Lerp will overwrite the Color's
// Transparency value, so we give this function an Execution Order of 100 to make sure it is executed last.
Here we can see that the last Particle Event added specifies an ExecutionOrder of 100. This is because both the UpdateParticleColorUsingLerp() function and UpdateParticleTransparencyToFadeOutUsingLerp() function modify a particle's Color property. When UpdateParticleColorUsingLerp() is executed it overwrites the particle's existing Color value, including the transparency component. UpdateParticleTransparencyToFadeOutUsingLerp() on the other hand only modifies the transparency component of the color, leaving the RGB values in tact. So in this example we want to make sure that UpdateParticleTransparencyToFadeOutUsingLerp() is executed after UpdateParticleColorUsingLerp(). If you don't supply an ExecutionOrder when adding Events they are given a default ExecutionOrder of zero. And since Events are fired from lowest to highest ExecutionOrder, giving UpdateParticleTransparencyToFadeOutUsingLerp() an ExecutionOrder of 100 will ensure that it is executed after UpdateParticleColorUsingLerp().
Try giving the UpdateParticleColorUsingLerp() Event a higher ExecutionOrder than UpdateParticleTransparencyToFadeOutUsingLerp(), such as 200, and see what happens. It should have the same effect as commenting out the line that adds UpdateParticleTransparencyToFadeOutUsingLerp() as an Event. You should also note that Events with the same ExecutionOrder are not guaranteed to execute in the same order they are added to the ParticleEvents object, so if you need one Event to fire after another one, specify it explicitly using the ExecutionOrder.
One Time Events
One Time Events are used to update every Active particle in the particle system once, and then they are removed automatically. So unlike Every Time Events that fire every frame, One Time Events only fire once and then are removed. One Time Events are useful for doing things like responding to in-game events or user input.
Locate the UpdateParticleVelocityToTravelRight() function in MyParticleSystem.cs:
public void UpdateParticleVelocityToTravelRight(DefaultTexturedQuadParticle cParticle, float fElapsedTimeInSeconds)
cParticle.Velocity = new Vector3(RandomNumber.Next(50, 100), 0, 0);
You can see that this Particle Update function will change the Velocity property of the particles to make them travel in the right direction (positive x-axis direction). Locate the MakeParticlesTravelRight() function:
public void MakeParticlesTravelRight()
When you press the X key during the tutorial's simulation, the MakeParticlesTravelRight() function will be called. This function adds a One Time Event that fires once and executes the UpdateParticleVelocityToTravelRight() Particle Update function on all active particles, changing their velocity.
Timed events are similar to One Time Events in that they only fire once. The difference however is that One Time Events fire as soon as they are added to the ParticleEvents object and are executed on all active particles, while Timed Events allow you to specify a time within a particle's Lifetime when the event should fire. Once a particle's ElapsedTime reaches the specified TimeToFire, the Timed Event will fire on that single particle, executing the Particle Update function that it points to only on that single particle. So Timed Events allow you to specify when certain actions should occur, relative to each individual particle's Lifetime.
Locate the UpdateParticleToUseRotationalVelocity() function in MyParticleSystem.cs:
public void UpdateParticleToUseRotationalVelocity(DefaultTexturedQuadParticle cParticle, float fElapsedTimeInSeconds)
cParticle.RotationalVelocity.Z = RandomNumber.Between(-MathHelper.TwoPi, MathHelper.TwoPi);
This Particle Update function will randomly the particle's Rotational Velocity. The tutorial specifies it as a One Time Event in the LoadParticleSystem() function using the following line:
This line of code specifies that the UpdateParticleToUseRotationalVelocity() Particle Update function should be called after a particle has existed for exactly 0.5 seconds. If you specified a TimeToFire greater than the particle's Lifetime, the Timed Event would not fire since the particle's ElapsedTime would never reach the specified TimeToFire.
Normalized Timed Events
Normalized Timed Events are basically the exact same as Timed Events, except that the specified TimeToFire is a normalized value between 0.0 and 1.0 corresponding to the particle's normalized lifetime; so 0.0 corresponds to the particle's birth and 1.0 corresponds to the particle's death. So if a particle was given a Lifetime of 5.0 seconds, then a Normalized Timed Event with a TimeToFire of 0.5 would cause the event to fire after the particle has existed for exactly 2.5 seconds (half of the particle's Lifetime).
Normalized Timed Events are useful when the particle's are initialized with random Lifetimes. For example, if we had a particle with a Lifetime of 4 seconds, and another particle with a Lifetime of 8 seconds, and we wanted an event to fire at exactly one quarter of the way through the particle's Lifetime, we would not be able to accomplish this using Timed Events, since we would need to be able to specify that the event fires after 1.0 second for the first particle, and after 2.0 seconds for the second particle. We can accomplish this using Normalized Timed Events however, by simply specifying a normalized TimeToFire of 0.25.
Locate the UpdateParticleSizeTo20() function in MyParticleSystem.cs:
public void UpdateParticleSizeTo20(DefaultTexturedQuadParticle cParticle, float fElapsedTimeInSeconds)
cParticle.Size = 20;
This Particle Update function simply changes the Size of a particle to be 20. The tutorial adds this function as an Event in the LoadParticleSystem() function with the line:
So this line specifies that exactly half way through a particle's Lifetime, the particle's Size should change to be 20.
Removing Events and using Groups
All Events can be added and removed at run-time, allowing for interaction with the particle system at run-time. Multiple events may be added to a particle system to achieve a desired effect. Because of this, DPSF allows you to specify a Group that the event should belong to, allow events belonging to a common group to be easily removed.
In MyParticleSystem.cs, locate the UpdateParticleVisibleOn() / Off() Particle Update functions:
public void UpdateParticleVisibleOn(DefaultTexturedQuadParticle cParticle, float fElapsedTimeInSeconds)
cParticle.Visible = true;
public void UpdateParticleVisibleOff(DefaultTexturedQuadParticle cParticle, float fElapsedTimeInSeconds)
cParticle.Visible = false;
Here we can see that these two functions simply turn a Particle's Visibility on and off. To make the Particles flash, the tutorial calls the AddMultipleEvents() function when the C key is pressed:
public void AddMultipleEvents()
// Make the Particles flash
ParticleEvents.AddNormalizedTimedEvent(0.05f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.1f, UpdateParticleVisibleOn, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.15f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.2f, UpdateParticleVisibleOn, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.25f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.3f, UpdateParticleVisibleOn, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.35f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.4f, UpdateParticleVisibleOn, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.45f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.5f, UpdateParticleVisibleOn, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.55f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.6f, UpdateParticleVisibleOn, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.65f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.7f, UpdateParticleVisibleOn, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.75f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.8f, UpdateParticleVisibleOn, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.85f, UpdateParticleVisibleOff, 0, 1);
ParticleEvents.AddNormalizedTimedEvent(0.9f, UpdateParticleVisibleOn, 0, 1);
Adding these events will cause the particles to flash on and off throughout their lifetime. Notice that all of the Events are given a Group number of 1 (and an ExecutionOrder of 0). To turn this effect off, the tutorial calls the RemoveMultipleEvents() function when the V key is pressed:
public void RemoveMultipleEvents()
You can see that instead of having to remove each of the Events individually, we can just specify to remove all Events that belong to Group 1.
Try commenting out the ParticleEvents in the LoadParticleSystem() function one at a time to see what effect each has on the final simulation.
To see the effects of each Event more clearly, also try reducing the Emitter's Particles Per Second (using the + / - keys) to 1, and increasing it to a higher value like 200.
Now that you have seen Particle Events you can now take advantage of using all of the Default Particle properties (i.e. friction, external forces, etc.). Just be sure to add the appropriate Particle Update function as an event in order for the properties to have an effect on the particles. DPSF provides Particle Update functions to use with the Default Particle properties, and their names all start with UpdateParticle... (e.g. UpdateParticleVelocityUsingFriction).