Governor Technology Blog
17 February 2009 by John Mannix
Click here to get
the code for this post.
Introduction
The term particle effects describes a computer
animation technique where many small particles are combined to
create realistic special effects that would otherwise be difficult
to achieve using standard animation techniques. Typical examples of
the visual effects that are achieved with this approach include:
fire, water, smoke, fog, snow and sparkles.
In this post we look at a simple implementation of a particle
system in Silverlight and use it to create a realistic smoke
effect. The finished animation is shown below:
Creating the Particle
The first step is to create a single particle which will form
the basis of the smoke effect. This will be a soft grey ellipse
which oscillates back and forth horizontally. When many of these
particles are combined and animated together they will create the
smoke effect.
To start, create a new Silverlight application in Microsoft
Visual Studio 2008. Accept the default option to create a web site
to host the application.
Create a user control to represent the particle and call this
SmokeParticle.xaml. Set the user control to a canvas layout, change
the size to 60x40 and make the background transparent.
<UserControl x:Class="ParticleEffectsDemo.SmokeParticle"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="60" Height="40" Background="#FF000000">
<Canvas x:Name="LayoutRoot" Background="Transparent">
<⁄Canvas>
<⁄UserControl>
Switch to expression blend and draw an ellipse 40 pixels by 40
pixels. Fill it with a radial gradient brush from #FFA0A0A0 in the
centre to #00FFFFFF at the edge. Name this ellipse "Particle".

Fig 1: The smoke particle in Expression Blend
We are now going to add the oscillating animation to the smoke
particle. Create a new storyboard called "drift". Create keyframes
at 0s, 0.5s and 1s, then add an X transform at each keyframe
so that the particle goes from 0px to 10px and finally 20px. Select
the storyboard and set it to auto reverse and repeat forever. If
you play this animation the particle moves from left to right and
back again at uniform speed.
The next step is to add some easing to vary the speed
at which the particle moves. To do this we edit the keyframe
splines so that the middle keyframe has x1=0.5 and the final
keyframe has x2=0.5. If you play the animation now you will see
that the particle speeds up nearer the middle of the animation.

Fig 2: Example of easing on the final keyframe
The Particle Generator
Having created a user control for the particle, we now need an
emitter to generate multiple particles on the display surface.
Rather than generate endless new instances of the particle control
and leave it to the garbage collector to clean up, it is good
practice to maintain a pool of particles and reuse them when they
expire. To help us do this we create a class called
ParticleGenerator, which maintains a pool of particles in a queue
and has methods to get a particle from the queue and return a
particle to the queue when it is no longer needed. The code for the
class is below. As you can see this is a generic class, which
allows us to maintain pools of any type of particle.
public class ParticleGenerator where T : new()
{
const int DEFAULT_SIZE = 10;
Queue particles;
public ParticleGenerator()
{
particles = new Queue();
CreateParticles(DEFAULT_SIZE);
}
private void CreateParticles(int n)
{
for (int i = 0; i < n; i++)
particles.Enqueue(new T());
}
public T GetParticle()
{
if (particles.Count == 0)
{
CreateParticles(DEFAULT_SIZE);
}
return particles.Dequeue();
}
public void ReturnParticle(T p)
{
particles.Enqueue(p);
}
}
The Animation Loop
The main animation look is coded in Page.xaml.cs. We use a
DispatcherTimer to control the animation loop and call function
that creates new particles, moves each particle through its
lifecycle and expires them at the end. To start with we define some
variables that control the smoke effect:
private const double fadeSpeed = 0.015D;
private const double floatUpSpeed = 2D;
private const int driftDelay = 1000;
private DispatcherTimer timerLoop;
private ParticleGenerator<SmokeParticle> particles =
new ParticleGenerator<SmokeParticle>();
fadeSpeed: controls how quickly particles fade
from view and are retired from the canvas. The higher this value
the shorter the smoke trail.
floatUpSpeed: controls the speed at which
particles float up the screen.
driftDelay: controls the random delay of the drift
animation of when particles are created, which prevents the drift
effect being too uniform.
Notice that we also instantiate an instance of the
ParticleGenerator class here, which will be used in the creation of
new particles during the animation loop.
In the page constructor, we initialise the timerLoop to start
calling the animation loop event handler timerLoop_Tick at
regular intevals of fifty milliseconds:
timerLoop = new DispatcherTimer();
timerLoop.Interval = TimeSpan.FromMilliseconds(50);
timerLoop.Tick += new EventHandler(timerLoop_Tick);
timerLoop.Start();
The next step is to write the function that will create new
particles on the display surface. This function uses the
ParticleGenerator class to get new particles from the pool rather
than creating them directly.
private void CreateParticle(Point pt)
{
// Create a new particle
SmokeParticle p;
ScaleTransform st;
p = particles.GetParticle();
// Scale it to the correct size
st = new ScaleTransform();
st.CenterX = 20;
st.CenterY = 20;
st.ScaleX = 0.1;
st.ScaleY = 0.1;
p.RenderTransform = st;
// Set opacity to visible
p.Opacity = 1;
// Set the particle start position with a slight random offset
p.SetValue(Canvas.LeftProperty, new Random().Next(5) + pt.X);
p.SetValue(Canvas.TopProperty, pt.Y);
// Start the drift animation on the particle
p.Drift.BeginTime = TimeSpan.FromMilliseconds(new Random().Next(driftDelay));
p.Drift.Begin();
// Add to the canvas
LayoutRoot.Children.Add(p);
}
This function first gets a particle from the
ParticleGenerator. Then scales it down to the correct size
by adding a ScaleTransform to the control. The opacity is reset to
one because expired particles that have been reused will have an
opacity of zero. The particle is then positioned with a random
offset to introduce a degree of variation to the smoke, and the
drift animation is started with a random delay. Finally, the new
particle is placed on the canvas to be displayed.
The final step is to animate all the particles on the display
surface each time around the animation loop so that they drift
upwards together to create the realistic smoke effect. This is done
in the timerLoop_Tick event handler:
void timerLoop_Tick(object sender, EventArgs e)
{
SmokeParticle p;
ScaleTransform st;
CreateParticle(new Point(this.Width / 2, this.Height - 20));
// Update position and opacity of all existing particles
for (int i = 0; i < LayoutRoot.Children.Count; i++)
{
p = LayoutRoot.Children[i] as SmokeParticle;
if (p != null)
{
st = p.RenderTransform as ScaleTransform;
if (st != null)
{
st.ScaleX += fadeSpeed;
st.ScaleY += fadeSpeed;
p.Opacity -= fadeSpeed;
double y = (double)p.GetValue(Canvas.TopProperty);
p.SetValue(Canvas.TopProperty, y - floatUpSpeed);
if (st.ScaleX >= 1)
{
LayoutRoot.Children.Remove(p);
p.Drift.Stop();
particles.ReturnParticle(p);
}
}
}
}
}
The first thing we do each time around the animation loop is
call our function CreateParticle to create a new particle.
We then iterate over all of the particles already on the canvas
adjusting the position, scale and opacity of each one so that they
drify upwards, grow larger and gradually fade out as they go. Once
the particle reaches its full size (ScaleX >=1) it is removed
from the canvas and returned to the ParticleGenerator for
reuse. And that is it - you have a realistic particle smoke effect
in Silverlight!
Leave comment: