Intermission #7 / 2–Bring on the Fire
2D,XNA,Game DevelopmentIn the last post we set the groundwork with the particle system itself. Now we can move on to making use of this and adding / customising our effects.
This post will be keenly followed by another post about monitoring the performance of your game. I hadn’t originally planned on it but since I hit problems when applying the particle effects (completely my own fault but a good lesson to learn), I felt it was wise to impart some of this experience to you.
After that we’ll roll up this phase of intermissions with an update to the Windows Phone 7 project and add all the extra bits (in their WP7 flavour) to that project (cannot wait to run everything from there come XNA 4 full release).
Then back the the tutorial series itself by adding some sound!!
As usual all the code for this section can be found here on codeplex.
Source updated for Final combined update project for GS 4.0 project here on Codeplex (Windows and WP7)
The aims of our fireball effect
Below is a diagram of what we are aiming for with our replacement fireball effect, a bit more snazzy than our original sprite:
We need the emitter to be thrown up spewing out fire as it does and trailed with a smoke plume to give it an extra edge. We could just launch out particles together from our trooper for the effect but we would loose cohesion from our fireball and loose a level of control should we wish to add similar effects for other weapons.
Making use of the particle framework
Below is a diagram of the framework we have setup:
From here you can see our standard particle system framework. Within our framework we will define several emitter effect definitions based on the emitter control template. This gives us a wide array of capabilities and options for setting up our effects, we can implement either:
Static Controlled effect
Where the game controls how the effect is generated and updated, including when new particles are generated, this is what is used in the CC particle sample.
Self sustaining effect
Where an effect is launched and generates a pattern of particles but the game still controls the emitter. Which is what we will use in this tutorial
Self controlled effect
In this the game initiates the effect but after that has no direct control, the emitter is completely self controlled. Bit like a fire out of control (if you could program real fire)
So lets get on and get this started.
Finishing up from the last episode
First we will return to the Particle Manager add add the last few parts to make the Particle Engine functional.
Back in the ParticleManager.cs class, first we need the pool for the emitters and a place to store the textures we intend to use (this enables use to re-use textures across emitters without consuming more memory)
Add the following to the beginning of the Particle Manager:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: SwampLib.Pool<ParticleEmitter> m_emitters; |
1 |
2: Dictionary<String, Texture2D> m_textures; |
Next we need to update the initialise function to setup our manager:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">override</span> <span style="color: rgb(0, 0, 255);">void</span> Initialize() |
1 |
2: { |
1 |
3: <span style="color: rgb(0, 128, 0);">// TODO: Add your initialization code here</span> |
1 |
4: m_emitters = <span style="color: rgb(0, 0, 255);">new</span> SwampLib.Pool<ParticleEmitter>(1000); |
1 |
5: m_textures = <span style="color: rgb(0, 0, 255);">new</span> Dictionary<<span style="color: rgb(0, 0, 255);">string</span>, Texture2D>(); |
1 |
6: <span style="color: rgb(0, 0, 255);">base</span>.Initialize(); |
1 |
7: } |
There is nothing to do in the Load Resources or Unload Resources for now (since textures are added as individual emitters are created, you might want to change this to see what improvements it can make, but remember by moving it here you loose a fair amount of flexibility)
The main work horses of the manager are the Update and Draw loops, in the update loop below:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
2: <span style="color: rgb(128, 128, 128);">/// Allows the game component to update itself.</span> |
1 |
3: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
4: <span style="color: rgb(128, 128, 128);">/// <param name="gameTime">Provides a snapshot of timing values.</param></span> |
1 |
5: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">override</span> <span style="color: rgb(0, 0, 255);">void</span> Update(GameTime gameTime) |
1 |
6: { |
1 |
7: <span style="color: rgb(0, 128, 0);">// calculate dt, the change in the since the last frame. the particle</span> |
1 |
8: <span style="color: rgb(0, 128, 0);">// updates will use this value.</span> |
1 |
9: <span style="color: rgb(0, 0, 255);">float</span> dt = (<span style="color: rgb(0, 0, 255);">float</span>)gameTime.ElapsedGameTime.TotalSeconds; |
1 |
10: |
1 |
11: <span style="color: rgb(0, 128, 0);">// Go through all of the active particles for each emitter.</span> |
1 |
12: <span style="color: rgb(0, 128, 0);">// Note that because we will return any particle that has finished,</span> |
1 |
13: <span style="color: rgb(0, 128, 0);">// we need to use the iterator that returns nodes instead of particles.</span> |
1 |
14: <span style="color: rgb(0, 0, 255);">foreach</span> (SwampLib.Pool<ParticleEmitter>.Node penode <span style="color: rgb(0, 0, 255);">in</span> m_emitters.ActiveNodes) |
1 |
15: { |
1 |
16: |
1 |
17: <span style="color: rgb(0, 0, 255);">if</span> (penode.Item.Active) |
1 |
18: { |
1 |
19: penode.Item.Update(dt); |
1 |
20: |
1 |
21: <span style="color: rgb(0, 0, 255);">if</span> (!penode.Item.Active) |
1 |
22: { |
1 |
23: m_emitters.Return(penode); |
1 |
24: } |
1 |
25: } |
1 |
26: |
1 |
27: } |
1 |
28: <span style="color: rgb(0, 0, 255);">base</span>.Update(gameTime); |
1 |
29: } |
Here we simply loop through each emitter and update it (if it is active). If an emitter becomes inactive after it’s update, we then remove it from the pool and free up a slot.
The Draw loop is similar except it does al the draw work in one place:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
2: <span style="color: rgb(128, 128, 128);">/// This is called when the game should draw itself.</span> |
1 |
3: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
4: <span style="color: rgb(128, 128, 128);">/// <param name="gameTime">Provides a snapshot of timing values.</param></span> |
1 |
5: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">override</span> <span style="color: rgb(0, 0, 255);">void</span> Draw(GameTime gameTime) |
1 |
6: { |
1 |
7: <span style="color: rgb(0, 128, 0);">// TODO: Add your drawing code here</span> |
1 |
8: <span style="color: rgb(0, 128, 0);">// tell sprite batch to begin, using the spriteBlendMode specified in</span> |
1 |
9: <span style="color: rgb(0, 128, 0);">// initializeConstants</span> |
1 |
10: <span style="color: rgb(0, 0, 255);">foreach</span> (ParticleEmitter pe <span style="color: rgb(0, 0, 255);">in</span> m_emitters) |
1 |
11: { |
1 |
12: StarTrooperGame.spriteBatch.Begin(pe.SpriteBlendMode); |
1 |
13: <span style="color: rgb(0, 0, 255);">foreach</span> (Particle p <span style="color: rgb(0, 0, 255);">in</span> pe.particles) |
1 |
14: { |
1 |
15: <span style="color: rgb(0, 128, 0);">// normalized lifetime is a value from 0 to 1 and represents how far</span> |
1 |
16: <span style="color: rgb(0, 128, 0);">// a particle is through its life. 0 means it just started, .5 is half</span> |
1 |
17: <span style="color: rgb(0, 128, 0);">// way through, and 1.0 means it's just about to be finished.</span> |
1 |
18: <span style="color: rgb(0, 128, 0);">// this value will be used to calculate alpha and scale, to avoid </span> |
1 |
19: <span style="color: rgb(0, 128, 0);">// having particles suddenly appear or disappear.</span> |
1 |
20: <span style="color: rgb(0, 0, 255);">float</span> normalizedLifetime = p.TimeSinceStart / p.Lifetime; |
1 |
21: |
1 |
22: <span style="color: rgb(0, 128, 0);">// we want particles to fade in and fade out, so we'll calculate alpha</span> |
1 |
23: <span style="color: rgb(0, 128, 0);">// to be (normalizedLifetime) * (1-normalizedLifetime). this way, when</span> |
1 |
24: <span style="color: rgb(0, 128, 0);">// normalizedLifetime is 0 or 1, alpha is 0. the maximum value is at</span> |
1 |
25: <span style="color: rgb(0, 128, 0);">// normalizedLifetime = .5, and is</span> |
1 |
26: <span style="color: rgb(0, 128, 0);">// (normalizedLifetime) * (1-normalizedLifetime)</span> |
1 |
27: <span style="color: rgb(0, 128, 0);">// (.5) * (1-.5)</span> |
1 |
28: <span style="color: rgb(0, 128, 0);">// .25</span> |
1 |
29: <span style="color: rgb(0, 128, 0);">// since we want the maximum alpha to be 1, not .25, we'll scale the </span> |
1 |
30: <span style="color: rgb(0, 128, 0);">// entire equation by 4.</span> |
1 |
31: <span style="color: rgb(0, 0, 255);">float</span> alpha = 4 * normalizedLifetime * (1 - normalizedLifetime); |
1 |
32: Color color = <span style="color: rgb(0, 0, 255);">new</span> Color(<span style="color: rgb(0, 0, 255);">new</span> Vector4(1, 1, 1, alpha)); |
1 |
33: |
1 |
34: <span style="color: rgb(0, 128, 0);">// make particles grow as they age. they'll start at 75% of their size,</span> |
1 |
35: <span style="color: rgb(0, 128, 0);">// and increase to 100% once they're finished.</span> |
1 |
36: <span style="color: rgb(0, 0, 255);">float</span> scale = p.Scale * (.75f + .25f * normalizedLifetime); |
1 |
37: |
1 |
38: StarTrooperGame.spriteBatch.Draw(m_textures[pe.Texture], p.Position, <span style="color: rgb(0, 0, 255);">null</span>, color, |
1 |
39: p.Rotation, pe.Origin, scale, SpriteEffects.None, 0.0f); |
1 |
40: |
1 |
41: } |
1 |
42: StarTrooperGame.spriteBatch.End(); |
1 |
43: } |
1 |
44: <span style="color: rgb(0, 0, 255);">base</span>.Draw(gameTime); |
1 |
45: } |
Here we reuse the SpriteBatch from the main game class setting the corresponding Sprite Blend mode according to the Emitter (so we can use both additive and alpha blended effects (more on that when we use this). The rest of the code is well commented (from the original MS sample) were we use the particles lifetime to adjust the alpha part of the particle (making if fade out as it dies) and the scale (to make the particle grow as it dies).
Finally we draw each particle to the screen using the texture from the manager according to the specified texture in the emitter.
To finish off the updates to the manager we add a few helper functions that just make life a bit easier:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">void</span> Add(ParticleEmitter emitter) |
1 |
2: { |
1 |
3: m_emitters.Add(emitter); |
1 |
4: } |
1 |
5: |
1 |
6: <span style="color: rgb(0, 0, 255);">public</span> Texture2D LoadTexture(String texture) |
1 |
7: { |
1 |
8: Texture2D tex; |
1 |
9: <span style="color: rgb(0, 0, 255);">try</span> { tex = m_textures[texture]; } |
1 |
10: <span style="color: rgb(0, 0, 255);">catch</span> |
1 |
11: { |
1 |
12: tex = Game.Content.Load<Texture2D>(@"<span style="color: rgb(139, 0, 0);">Particles\</span>" + texture); |
1 |
13: m_textures.Add(texture, tex); |
1 |
14: } |
1 |
15: |
1 |
16: <span style="color: rgb(0, 0, 255);">return</span> tex; |
1 |
17: } |
Here we just expose the method off adding a new emitter to the manager and a function to manage the texture library maintained by the Particle Manager.
On with the show, the first effect
To make it easier to distinguish between the Emitter template and our new effects, first setup a new class in the engine folder called “ParticleEmitters.cs” and then add the following code for the effect:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: <span style="color: rgb(0, 0, 255);">using</span> System; |
1 |
2: <span style="color: rgb(0, 0, 255);">using</span> System.Collections.Generic; |
1 |
3: <span style="color: rgb(0, 0, 255);">using</span> System.Linq; |
1 |
4: <span style="color: rgb(0, 0, 255);">using</span> System.Text; |
1 |
5: <span style="color: rgb(0, 0, 255);">using</span> Microsoft.Xna.Framework; |
1 |
6: <span style="color: rgb(0, 0, 255);">using</span> Microsoft.Xna.Framework.Graphics; |
1 |
7: |
1 |
8: <span style="color: rgb(0, 0, 255);">namespace</span> TestProject |
1 |
9: { |
1 |
10: |
1 |
11: /// <summary> |
1 |
12: /// ExplosionParticleSystem <span style="color: rgb(0, 0, 255);">is</span> a specialization <span style="color: rgb(0, 0, 255);">of</span> ParticleEmitter which creates a |
1 |
13: /// fiery explosion. It should be combined <span style="color: rgb(0, 0, 255);">with</span> FireballSmokeParticleSystem <span style="color: rgb(0, 0, 255);">for</span> |
1 |
14: /// best effect. |
1 |
15: /// </summary> |
1 |
16: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">class</span> FireballParticleEmitter : ParticleEmitter |
1 |
17: { |
1 |
18: float cycleMeter = 0; |
1 |
19: <span style="color: rgb(0, 0, 255);">public</span> FireballParticleEmitter() |
1 |
20: { |
1 |
21: } |
1 |
22: |
1 |
23: /// <summary> |
1 |
24: /// <span style="color: rgb(0, 0, 255);">Set</span> up the constants that will give this particle system its behavior <span style="color: rgb(0, 0, 255);">and</span> |
1 |
25: /// properties. |
1 |
26: /// </summary> |
1 |
27: <span style="color: rgb(0, 0, 255);">protected</span> override void InitializeConstants() |
1 |
28: { |
1 |
29: // high initial speed <span style="color: rgb(0, 0, 255);">with</span> lots <span style="color: rgb(0, 0, 255);">of</span> variance. make the values closer |
1 |
30: // together <span style="color: rgb(0, 0, 255);">to</span> have more consistently circular explosions. |
1 |
31: minInitialSpeed = 40; |
1 |
32: maxInitialSpeed = 500; |
1 |
33: |
1 |
34: // doesn<span style="color: rgb(0, 128, 0);">'t matter what these values are set to, acceleration is tweaked in</span> |
1 |
35: // the override <span style="color: rgb(0, 0, 255);">of</span> InitializeParticle. |
1 |
36: minAcceleration = 0; |
1 |
37: maxAcceleration = 0; |
1 |
38: |
1 |
39: // explosions should be relatively <span style="color: rgb(0, 0, 255);">short</span> lived |
1 |
40: minLifetime = 1f; |
1 |
41: maxLifetime = 2f; |
1 |
42: |
1 |
43: minScale = 0.03f; |
1 |
44: maxScale = 0.1f; |
1 |
45: |
1 |
46: minNumParticles = 1; |
1 |
47: maxNumParticles = 2; |
1 |
48: |
1 |
49: minRotationSpeed = -MathHelper.PiOver4; |
1 |
50: maxRotationSpeed = MathHelper.PiOver4; |
1 |
51: |
1 |
52: // additive blending <span style="color: rgb(0, 0, 255);">is</span> very good at creating fiery effects. |
1 |
53: spriteBlendMode = SpriteBlendMode.Additive; |
1 |
54: |
1 |
55: } |
1 |
56: |
1 |
57: /// <summary> |
1 |
58: /// We overide the update <span style="color: rgb(0, 0, 255);">to</span> generate <span style="color: rgb(0, 0, 255);">new</span> particles acording <span style="color: rgb(0, 0, 255);">to</span> our cycle pattern. |
1 |
59: /// </summary> |
1 |
60: /// <param name="<span style="color: rgb(139, 0, 0);">dt</span>">time since the last update</param> |
1 |
61: <span style="color: rgb(0, 0, 255);">public</span> override void Update(float dt) |
1 |
62: { |
1 |
63: cycleMeter += dt; |
1 |
64: <span style="color: rgb(0, 0, 255);">if</span> (cycleMeter > ParticleCycleTime) |
1 |
65: { |
1 |
66: AddParticles(EmitterPosition,2); |
1 |
67: cycleMeter = 0; |
1 |
68: } |
1 |
69: <span style="color: rgb(0, 0, 255);">if</span> (EmitterPosition.Y < -100) |
1 |
70: Active = <span style="color: rgb(0, 0, 255);">false</span>; |
1 |
71: |
1 |
72: base.Update(dt); |
1 |
73: } |
1 |
74: |
1 |
75: /// <summary> |
1 |
76: /// InitializeParticle <span style="color: rgb(0, 0, 255);">is</span> overridden <span style="color: rgb(0, 0, 255);">to</span> bind <span style="color: rgb(0, 0, 255);">to</span> the emitter it<span style="color: rgb(0, 128, 0);">'self.</span> |
1 |
77: /// </summary> |
1 |
78: /// <param name="<span style="color: rgb(139, 0, 0);">p</span>">the particle <span style="color: rgb(0, 0, 255);">to</span> <span style="color: rgb(0, 0, 255);">set</span> up</param> |
1 |
79: /// <param name="<span style="color: rgb(139, 0, 0);">where</span>">where the particle should be placed</param> |
1 |
80: <span style="color: rgb(0, 0, 255);">protected</span> override void InitializeParticle(Particle p, Vector2 where) |
1 |
81: { |
1 |
82: base.InitializeParticle(p, where); |
1 |
83: |
1 |
84: // the base <span style="color: rgb(0, 0, 255);">is</span> mostly good, but we want <span style="color: rgb(0, 0, 255);">to</span> have the effect travel <span style="color: rgb(0, 0, 255);">with</span> the emitter |
1 |
85: p.Position = EmitterPosition; |
1 |
86: p.Velocity = EmitterVelocity; |
1 |
87: p.Acceleration = EmitterAcceleration; |
1 |
88: } |
1 |
89: } |
1 |
90: |
1 |
91: } |
I’ve added the complete code for this effect, so just replace the generated class (renamed the namespace, added using statements for XNA and removed the default class name).
The implementation is quite simple, we have created a unique effect on top of the template and included 3 overloads:
Update to Initialise Constants (Mandatory)
This is the only mandatory requirement for any new particle effect, to setup it’s starting parameters which control how particles are generated when called. These are also the most tricky to configure to get the effect you want.
Supplemented Update
As we are aiming for a self sustained effect (a constant fireball) we want to generate new particles to replace those that die during the cause of our fireball shot, so we override update to add more particles each update. This is also supplemented by an effect specific attribute “CycleMeter” to control how often new particles should happen. (0 = every frame, 10 = every 10 seconds), the timing is set in the “ParticleCycleTime” attribute of the emitter.
Supplemented Initialise Particle
As we want the particles to follow the emitter and not fly of in a random direction (which is the default), we fix the particles position to the emitters position. We could enhance this if we wished to make the particle circle around the emitter if we wished.
The effect that this will give us will be a sustained burst of particles that are constantly generated on top of the emitter.
Not forgetting our actual particle
With all this code in lace we still need a texture for our particle to draw, so download the code from the beginning of this article and copy the Explosion.PNG image from the content folder, create a new folder in your content project called “Particles” and paste it there.
|
Explosion Particle |
Calling the effect
So now that we have our effect, lets bring the fire, this is simple enough since we have a separate function in the trooper section of StarTroopeSprites for the trooper to fire, so add the following to the TrooperFire function:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: FireballParticleEmitter fireballemitter = <span style="color: rgb(0, 0, 255);">new</span> FireballParticleEmitter(); |
1 |
2: fireballemitter.Initialize("<span style="color: rgb(139, 0, 0);">explosion</span>", 10); |
1 |
3: fireballemitter.EmitterPosition = <span style="color: rgb(0, 0, 255);">new</span> Vector2(Position.X, Position.Y - 35); |
1 |
4: fireballemitter.EmitterVelocity = <span style="color: rgb(0, 0, 255);">new</span> Vector2(0, -40); |
1 |
5: fireballemitter.EmitterAcceleration = <span style="color: rgb(0, 0, 255);">new</span> Vector2(0, -0.5f); |
1 |
6: fireballemitter.ParticleCycleTime = 0f; |
1 |
7: StarTrooperGame.ParticleManager.Add(fireballemitter); |
In here we:
- create new emitter for the effect we just setup
- initialised the emitter with the Explosion sprite
- Set it’s position, velocity and acceleration
- Set the Particle cycle time to 0 so that it constantly spews particles (but if you wanted to make the fireball splutter just increase this slightly)
- And finally hand over the emitter to our Particle Manager to control
Run the game now and when you shoot your fireball, it should be followed by a nice new particle enabled fireball.
![]() |
![]() |
Old fireball |
New Fireball |
But wait there’s more.
A good thing to remember with particle effects is that we don’t just have to live with one effect, we can combine effects in many different ways to get a better result. If you look back the the previous post you will notice a nice little smoke plume emanating from the tail of our fireball, fire emits smoke doesn’t it
So lets add another effect to our list, add the following to the ParticleEmitters class:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: /// <summary> |
1 |
2: /// ExplosionSmokeParticleSystem <span style="color: rgb(0, 0, 255);">is</span> a specialization <span style="color: rgb(0, 0, 255);">of</span> ParticleSystem which |
1 |
3: /// creates a circular pattern <span style="color: rgb(0, 0, 255);">of</span> smoke. It should be combined <span style="color: rgb(0, 0, 255);">with</span> |
1 |
4: /// ExplosionParticleSystem <span style="color: rgb(0, 0, 255);">for</span> best effect. |
1 |
5: /// </summary> |
1 |
6: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">class</span> FireballSmokeParticleEmitter : ParticleEmitter |
1 |
7: { |
1 |
8: float cycleMeter = 0; |
1 |
9: <span style="color: rgb(0, 0, 255);">public</span> FireballSmokeParticleEmitter() |
1 |
10: { |
1 |
11: } |
1 |
12: |
1 |
13: /// <summary> |
1 |
14: /// <span style="color: rgb(0, 0, 255);">Set</span> up the constants that will give this particle system its behavior <span style="color: rgb(0, 0, 255);">and</span> |
1 |
15: /// properties. |
1 |
16: /// </summary> |
1 |
17: <span style="color: rgb(0, 0, 255);">protected</span> override void InitializeConstants() |
1 |
18: { |
1 |
19: // less initial speed than the explosion itself |
1 |
20: minInitialSpeed = 20; |
1 |
21: maxInitialSpeed = 200; |
1 |
22: |
1 |
23: // acceleration <span style="color: rgb(0, 0, 255);">is</span> negative, so particles will accelerate away from the |
1 |
24: // initial velocity. this will make them slow down, <span style="color: rgb(0, 0, 255);">as</span> <span style="color: rgb(0, 0, 255);">if</span> from wind |
1 |
25: // resistance. we want the smoke <span style="color: rgb(0, 0, 255);">to</span> linger a bit <span style="color: rgb(0, 0, 255);">and</span> feel wispy, though, |
1 |
26: // so we don<span style="color: rgb(0, 128, 0);">'t stop them completely like we do ExplosionParticleSystem</span> |
1 |
27: // particles. |
1 |
28: minAcceleration = -10; |
1 |
29: maxAcceleration = -50; |
1 |
30: |
1 |
31: // explosion smoke lasts <span style="color: rgb(0, 0, 255);">for</span> longer than the explosion itself, but <span style="color: rgb(0, 0, 255);">not</span> |
1 |
32: // <span style="color: rgb(0, 0, 255);">as</span> <span style="color: rgb(0, 0, 255);">long</span> <span style="color: rgb(0, 0, 255);">as</span> the plumes <span style="color: rgb(0, 0, 255);">do</span>. |
1 |
33: minLifetime = 1.0f; |
1 |
34: maxLifetime = 2.5f; |
1 |
35: |
1 |
36: minScale = 0.03f; |
1 |
37: maxScale = 0.1f; |
1 |
38: |
1 |
39: minNumParticles = 1; |
1 |
40: maxNumParticles = 2; |
1 |
41: |
1 |
42: minRotationSpeed = -MathHelper.PiOver4; |
1 |
43: maxRotationSpeed = MathHelper.PiOver4; |
1 |
44: |
1 |
45: spriteBlendMode = SpriteBlendMode.AlphaBlend; |
1 |
46: |
1 |
47: } |
1 |
48: <span style="color: rgb(0, 0, 255);">protected</span> override void InitializeParticle(Particle p, Vector2 where) |
1 |
49: { |
1 |
50: base.InitializeParticle(p, where); |
1 |
51: |
1 |
52: // The base works fine except <span style="color: rgb(0, 0, 255);">for</span> acceleration. Explosions move outwards, |
1 |
53: // <span style="color: rgb(0, 0, 255);">then</span> slow down <span style="color: rgb(0, 0, 255);">and</span> <span style="color: rgb(0, 0, 255);">stop</span> because <span style="color: rgb(0, 0, 255);">of</span> air resistance. <span style="color: rgb(0, 0, 255);">Let</span><span style="color: rgb(0, 128, 0);">'s change</span> |
1 |
54: // acceleration so that <span style="color: rgb(0, 0, 255);">when</span> the particle <span style="color: rgb(0, 0, 255);">is</span> at max lifetime, the velocity |
1 |
55: // will be zero. |
1 |
56: |
1 |
57: // We<span style="color: rgb(0, 128, 0);">'ll use the equation vt = v0 + (a0 * t). (If you're not familar with</span> |
1 |
58: // this, it<span style="color: rgb(0, 128, 0);">'s one of the basic kinematics equations for constant</span> |
1 |
59: // acceleration, <span style="color: rgb(0, 0, 255);">and</span> basically says: |
1 |
60: // velocity at time t = initial velocity + acceleration * t) |
1 |
61: // We<span style="color: rgb(0, 128, 0);">'ll solve the equation for a0, using t = p.Lifetime and vt = 0.</span> |
1 |
62: // the base <span style="color: rgb(0, 0, 255);">is</span> mostly good, but we want <span style="color: rgb(0, 0, 255);">to</span> have the effect travel <span style="color: rgb(0, 0, 255);">with</span> the emitter |
1 |
63: p.Position = EmitterPosition; |
1 |
64: p.Velocity = EmitterVelocity; |
1 |
65: p.Acceleration = -EmitterVelocity / p.Lifetime; |
1 |
66: } |
1 |
67: |
1 |
68: <span style="color: rgb(0, 0, 255);">public</span> override void Update(float dt) |
1 |
69: { |
1 |
70: cycleMeter += dt; |
1 |
71: <span style="color: rgb(0, 0, 255);">if</span> (cycleMeter > ParticleCycleTime) |
1 |
72: { |
1 |
73: AddParticles(EmitterPosition, 2); |
1 |
74: cycleMeter = 0; |
1 |
75: } |
1 |
76: <span style="color: rgb(0, 0, 255);">if</span> (EmitterPosition.Y < -100) |
1 |
77: Active = <span style="color: rgb(0, 0, 255);">false</span>; |
1 |
78: |
1 |
79: base.Update(dt); |
1 |
80: } |
Now this effect is similar to our Fireball effect with a few minor differences:
It lasts a bit longer (min and max lifetime are greater) as smoke lingers
It is slower (min and max speed are reduced)
The acceleration is also slower so that the smoke gets left behind
We use a different blend effect for better results
If we add this now to our fireball launch code in StarTrooperSprites we get a much nicer effect (I’ve also moved it to a separate function to make it more friendly), so remove the code added above in the “TrooperFire” function for the fireball effect and add the following function:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: void FireballLaunch(Vector2 position, Vector2 velocity, Vector2 accel) |
1 |
2: { |
1 |
3: |
1 |
4: FireballSmokeParticleEmitter smokeemitter = <span style="color: rgb(0, 0, 255);">new</span> FireballSmokeParticleEmitter(); |
1 |
5: smokeemitter.Initialize("<span style="color: rgb(139, 0, 0);">smoke</span>", 10); |
1 |
6: smokeemitter.EmitterPosition = position; |
1 |
7: smokeemitter.EmitterVelocity = velocity; |
1 |
8: smokeemitter.EmitterAcceleration = accel; |
1 |
9: smokeemitter.ParticleCycleTime = 0f; |
1 |
10: StarTrooperGame.ParticleManager.Add(smokeemitter); |
1 |
11: |
1 |
12: FireballParticleEmitter fireballemitter = <span style="color: rgb(0, 0, 255);">new</span> FireballParticleEmitter(); |
1 |
13: fireballemitter.Initialize("<span style="color: rgb(139, 0, 0);">explosion</span>", 10); |
1 |
14: fireballemitter.EmitterPosition = position; |
1 |
15: fireballemitter.EmitterVelocity = velocity; |
1 |
16: fireballemitter.EmitterAcceleration = accel; |
1 |
17: fireballemitter.ParticleCycleTime = 0f; |
1 |
18: StarTrooperGame.ParticleManager.Add(fireballemitter); |
1 |
19: |
1 |
20: } |
And just add the following call to the “TrooperFire” function:
1 |
<pre style="margin: 0em; width: 100%; font-family: consolas,"Courier New",courier,monospace; font-size: 12px; background-color: rgb(251, 251, 251);"> 1: FireballLaunch(<span style="color: rgb(0, 0, 255);">new</span> Vector2(Position.X, Position.Y - 35), <span style="color: rgb(0, 0, 255);">new</span> Vector2(0, -40), <span style="color: rgb(0, 0, 255);">new</span> Vector2(0, -0.5f)); |
This gives us our flaming ball of death, beware Condors!!!
Draw Order
A quick note about draw order. The current particle implementation above does not have any facilities for managing draw order as part of the effects, that being which effect gets drawn on top of which.
For example reverse the order in which the smoke and explosion effects are drawn and have a look at the corresponding result. You should see this:
Not quite as good is it as it now appears we have fire from smoke?, not my cup of tea.
So take care using this and consider in your experiments (see below) which order you want to draw your effects. There are a couple of ways to handle this more programmatically, like adding a sort to the Pool class based on an attribute in the emitter, but this wouldn’t give you as much flexibility as you might think.
Choose wisely as to your approach and beware of the unintended consequences of your developments
One problem which can only be really handled by having multiple particle managers, would be to have the ability to differentiate between foreground effects (like what we have) and background effects (like clouds below the player or smoke rising from a scared city below).
Experimentation
Now the main thing with particle effects is experimentation. the above effect took me approx 1 hour to put together, here’s a little run down of what it took to get to this point
![]() |
![]() |
![]() |
![]() |
![]() |
1st run, way too many particles and they didn’t follow the emitter, except flame up | Less particles but they didn’t last long enough or spawn quick enough. Also too big | Got the fireball right but we needed more | Great effect, something to keep for later. Not the ball I wanted. May be to use in a different colour as a plasma ball? |
The end result. |
This is why most AAA rated games have dedicated teams (not just individuals) for creating and managing effects like this. It can take a fair bit of time to get it right and within budget (both Time and game update/draw costs).
Conclusion and a final note.
This prompted my next intermission on performance, a quick and easy way to monitor it and the cost of the effects I was generating.
Lets bring the numbers!