Particle Systems
After completing this chapter, you will be able to:
This chapter shows you how to implement your own basic particle system so you can create various particle effects. In games, particle systems are used for various purposes, such as displaying fire, showing a spell or explosion effect, or creating a trail for a game object. In the example projects in this chapter, the particle system will be triggered by collisions; however, that does not need to be the only case. For example, in an underwater game, you might implement a particle system that attaches to a hero character and emits bubbles over time. The implementation in this chapter is intended to function as a basic example; however, the particle system you’ll implement is flexible enough that you should be able to customize it for your own games.
Particle systems
In this section, you will create a particle system that includes the basic functionality you need to achieve common effects, such as explosions and spell effects. Additionally, you can initialize your effect at a specific location or add it to a game object and trigger it when needed. Your implementation will also include the ability to use blending effects that display more smoothly.
The Particle System project
The gameplay functionality of this project is identical to that from the previous chapter. Here, however, you’ll add particle effects that appear at points of collision. You can see an example of this project running in Figure 8-1.
Figure 8-1. The Particle System project, with a particle effect showing at the point of collision
The project’s controls are as follows:
The goals of this project are as follows:
The steps for creating the project are as follows:
Add the following resource, which can be found in the Chapter08SourceCodeResources folder, into your content project before you begin:
Understanding particle systems
In its simplest form, a particle system is a collection of primitives. The primitives within a particle system are known as particles. All the particles in a particle system have a common set of properties, such as life span, size, rate of change in size, and speed. By manipulating these properties in different ways, you can achieve various effects. In general, each particle executes a behavior based on its properties until its life span is over.
Another important aspect for a particle system is randomness. To create an effect such as an explosion, game developers often use random values to initialize a particle’s properties. Without a random factor, patterns often become apparent as the particle system executes. Overall, you can think of a particle system as a group or collection of particles. By randomizing each particle’s properties, you can achieve engaging effects for a variety of purposes.
With the basic understanding of the components of a particle system, you are ready to begin adding support for the upcoming particle classes. The first modification you need to make is adding color-tinting support within the TexturedPrimitive class. Adding this allows you to tint your images.
Modifying the TexturedPrimitive class
protected Color mTintColor;
protected void InitPrimitive(String imageName, Vector2 position, Vector2 size, String label = null)
{
...
mTintColor = Color.White;
...
}
Game1.sSpriteBatch.Draw(mImage,
destRect, // Area to be drawn in pixel space
null, //
mTintColor, //
mRotateAngle, // Angle to rotate (clockwise)
org, // Image reference position
SpriteEffects.None, 0f);
Creating the ParticlePrimitive class
public class ParticlePrimitive : GameObject
{
private float kLifeSpanRandomness = 0.4f;
private float kSizeChangeRandomness = 0.5f;
private float kSizeRandomness = 0.3f;
private float kSpeedRandomness = 0.1f;
// Number of updates before a particle disappear
private int mLifeSpan;
// How fast does the particle changes size
private float mSizeChangeRate;
...
}
public ParticlePrimitive(Vector2 position, float size, int lifeSpan) :
base("ParticleImage", position, new Vector2(size, size))
{
mLifeSpan =(int)(lifeSpan * Game1.RandomNumber(-kLifeSpanRandomness,
kLifeSpanRandomness));
mVelocityDir.X = Game1.RandomNumber(-0.5f, 0.5f);
mVelocityDir.Y = Game1.RandomNumber(-0.5f, 0.5f);
mVelocityDir.Normalize();
mSpeed = Game1.RandomNumber(kSpeedRandomness);
mSizeChangeRate = Game1.RandomNumber(kSizeChangeRandomness);
mSize.X *= Game1.RandomNumber(1f-kSizeRandomness, 1+kSizeRandomness);
mSize.Y = mSize.X;
}
public override void Update()
{
base.Update();
mLifeSpan--; // Continue to approach expiration
// Change its size
mSize.X += mSizeChangeRate;
mSize.Y += mSizeChangeRate;
// Change the tintcolor randomly
Byte[] b = new Byte[3];
Game1.sRan.NextBytes(b);
mTintColor.R += b[0];
mTintColor.G += b[1];
mTintColor.B += b[2];
}
public bool Expired { get { return (mLifeSpan < 0); } }
Now that you have created the class for a particle, you can create the particle system itself.
Creating the ParticleSystem class
public class ParticleSystem
{
// Collection of particles
private List<ParticlePrimitive> mAllParticles;
public ParticleSystem()
{
mAllParticles = new List<ParticlePrimitive>();
}
...
}
public void AddParticleAt(Vector2 pos)
{
ParticlePrimitive particle = new ParticlePrimitive(pos, 2f, 50);
mAllParticles.Add(particle);
}
public void UpdateParticles()
{
int particleCounts = mAllParticles.Count;
for (int i = particleCounts- 1; i >= 0; i--)
{
mAllParticles[i].Update();
if (mAllParticles[i].Expired)
mAllParticles.RemoveAt(i); // Remove expired ones
}
}
public void DrawParticleSystem()
{
// 1. Switch blend mode to "Additive"
Game1.sSpriteBatch.End();
Game1.sSpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive);
// 2. Draw all particles
foreach (var particle in mAllParticles)
particle.Draw();
// 3. Switch blend mode back to AlphaBlend
Game1.sSpriteBatch.End();
Game1.sSpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
}
Understanding alpha and additive blending
Blending is a process of mixing overlapping colors to produce a new color. The blending process consists of three key components. First is the source color, which is the overlaying or top color. Second is the destination color, which is the bottom color—the color underneath the source color. The last color is the blended color, which is a color calculated from the source color and the destination color. Alpha blending and additive blending are two different ways of calculating a blended color.
Typically, alpha blending is achieved with the following equation:
By inspecting this equation, you can see that when the source’s alpha is equal to 1, the Output _ Color equals the Source _ Color. Alternatively, when the source’s alpha is equal to 0, the Output _ Color equals the Destination _ Color. Logically this makes sense. If the source is completely opaque (has an alpha of 1), then the background color will not show through. However, if the source is completely transparent (has an alpha of 0), then the background color will show through unchanged. An alpha value between 0 and 1 will compute an output color that is a linear combination (blend) of the source and destination colors.
MonoGame achieves additive blending using the following equation:
By inspecting this equation, you can see that the approach to additive blending is similar to alpha blending; however, there are some differences. The first difference is that Tint _ Color is included along with Source _ Alpha. Secondly, the destination color is added without the reference to Source _ Alpha. This means the bottom color is added to the top or overlaying color.
In addition to alpha and additive blending, there are other types, such as multiplicative blending. The type of blending you should use within your games depends upon the effect you’re trying to achieve. Figure 8-2 shows examples of the effects that alpha and additive blending produce.
Figure 8-2. The difference between alpha and additive blending
You can now add the GameState class modifications necessary to use the particle system within your game.
ParticleSystem mParticleSystem;
public GameState()
{
...
mParticleSystem = new ParticleSystem();
}
public void UpdateGame()
{
...
mParticleSystem.UpdateParticles();
}
private void CollisionUpdate()
{
...
#region Collide the hero with the flower
...
if (mHeroPixelCollision)
{
mParticleSystem.AddParticleAt(pixelCollisionPosition);
}
...
#endregion
#region Collide the hero with planes
...
if (mHeroPixelCollision)
{
mParticleSystem.AddParticleAt(pixelCollisionPosition);
}
...
#endregion
}
public void DrawGame()
{
mFlower.Draw();
foreach (var p in mPlane)
p.Draw();
mHero.Draw();
mParticleSystem.DrawParticleSystem();
...
}
The particle system you just created works great for creating particles; however, useful particle systems should include the ability to control the duration of particle creation, the location where particles are created, and the behaviors of the created particles. This is where the particle emitter comes in. With control over the duration, location, and behaviors of emitted particles, you can create interesting effects such as fire, explosions, and trails by simply modifying the way particles are emitted.
The Particle Emitter project
This project allows you to implement particle emitter for your particle system. The functionality of this project is identical to that in the previous section; however, you’ll use particle emitter to control the emission of particles after creating the particle system. You can see an example of this project running in Figure 8-3.
Figure 8-3. The Particle Emitter project with a large particle emission at the point of collision
The project’s controls are as follows:
The goals of this project are as follows:
The steps for creating the project are as follows:
Creating the ReddishParticlePrimitive class
public class ReddishParticlePrimitive : ParticlePrimitive
{
...
}
public ReddishParticlePrimitive(Vector2 position, float size, int lifeSpan) :
base(position, size, lifeSpan)
{
mVelocityDir.Y = 5f * Math.Abs(mVelocityDir.Y);
mVelocityDir.Normalize();
mSpeed *= 5.25f;
mSizeChangeRate *= 1.5f;
mSize.X *= 0.7f;
mSize.Y = mSize.X;
mTintColor = Color.DarkOrange;
}
public override void Update()
{
base.Update();
Color s = mTintColor;
if (s.R < 255)
s.R += 1;
if (s.G != 0)
s.G -= 1;
if (s.B != 0)
s.B -= 1;
mTintColor = s;
}
Now it is time to create the ParticleEmitter class. The emitter class gives you the ability to emit particles with a specific type of movement behavior over a finite amount of time.
Creating the ParticleEmitter class
public class ParticleEmitter
{
const int kMinToEmit = 5;
protected Vector2 mEmitPosition;
protected int mNumRemains;
...
}
public ParticleEmitter(Vector2 pos, int n)
{
mNumRemains = n;
mEmitPosition = pos;
}
public bool Expired { get { return (mNumRemains <= 0); } }
public void EmitParticles(List<ParticlePrimitive> allParticles)
{
int numToEmit = 0;
if (mNumRemains < kMinToEmit)
{
// If only a few are left, emits all of them
numToEmit = mNumRemains;
}
else
{
// Otherwise, emits about 20% of what's left
numToEmit = (int)Game1.RandomNumber(0.2f * mNumRemains);
}
// Left for future emitting.
mNumRemains -= numToEmit;
for (int i = 0; i < numToEmit; i++)
{
ParticlePrimitive particle;
// 40% chance emitting simple particle,
// 60% chance emitting the new reddish particle
if (Game1.RandomNumber(1.0f) > 0.6f)
particle = new ParticlePrimitive(mEmitPosition, 2f, 30);
else
particle = new ReddishParticlePrimitive(mEmitPosition, 2f, 80);
allParticles.Add(particle);
}
}
Modifying the ParticleSystem class
private List<ParticleEmitter> mAllEmitters;
public ParticleSystem()
{
...
mAllEmitters = new List<ParticleEmitter>();
}
public void AddEmitterAt(Vector2 pos)
{
ParticleEmitter e = new ParticleEmitter(pos, (int) Game1.RandomNumber(50, 100));
mAllEmitters.Add(e);
}
public void UpdateParticles()
{
int emittersCount = mAllEmitters.Count;
for (int i = emittersCount - 1; i >= 0; i--)
{
mAllEmitters[i].EmitParticles(mAllParticles);
if (mAllEmitters[i].Expired)
mAllEmitters.RemoveAt(i);
}
...
}
Finally, you can make a quick modification to the GameState class so you can use the new ParticleEmitter class.
private void CollisionUpdate()
{
...
#region Collide the hero with the flower
...
if (mHeroPixelCollision)
{
mParticleSystem.AddEmitterAt(pixelCollisionPosition);
}
...
#endregion
#region Collide the hero with planes
...
if (mHeroPixelCollision)
{
mParticleSystem.AddEmitterAt(pixelCollisionPosition);
}
...
#endregion
}
Summary
In this chapter, you saw how to implement a particle system in your game. This particle system consists of a collection of particles, each of which contains properties that determine its behavior over a specifiable life span. Additionally, you saw how you can apply blending effects to your particles to give a customized appearance to your particle system. Specifically, you were shown how to implement alpha and additive blending.
Finally, you learned how to implement a particle emitter to support continued particle emission. Continuous emission is useful for fire and other effects that need to exist for an extended period of time.
Quick reference
To |
Do this |
---|---|
Create an instantaneous bursting effect | Instantiate a ParticleSystem class and call the AddParticleAt() function to create the particle effect at the desired location. |
Create a longer-lasting effect, such as fire or an explosion | Instantiate a ParticleEmitter class with the desired number of particles to be emitted and duration upon which to emit. Then add the emitter to the ParticleSystem class. |
Create your own particle effect | 1. Subclass from the ParticlePrimitive class (as in the case of ReddishParticlePrimitive class) and implement customized initialization and update behavior to the color, travel direction, velocity, life span, and so on. 2. Create your own emitter class (for example, by subclassing from the ParticleEmitter class) and customize the creation and emitting behaviors. |