Intermission #7 – Resource pools and Particles
I was going to do this as two separate articles, but after a refresh trawl at what is already our there and available I’ve decided to put them together. We don’t need anything overly complex at this stage of the game, although I will point out some of the areas where you could go further (and point you in the right direction).
I’ve always been an advocate for “not re-inventing the wheel” and in these days of XNA development, you don’t need to. There are many resources and code stores to find implementations of what you need and you can either learn the lesson of someone else’s approach or just copy it for your own use (make sure the original author gets a mention if you do.
Source updated for Final combined update project for GS 4.0 project here on Codeplex (Windows and WP7)
Trawling through the net
In looking around at what other particle and resource pool are around, I found very different levels of content available, some were old but good (that just need tweaking to get back up to date), some were new but were either too basic or didn’t offer enough flexibility. Some I really liked, here’s a list of what is good that I’ve found so far:
Resource Pools by the SwampThing – best description of what is required for a full resource pool, implements this in the CC sample below (still in GS 2)
An XNA resource pool on CodeCube – Quick and dirty yet ultimately flexible pool system
An Honourable mention to my colleague NemoKrads particle tutorial series – Good graphics oriented particle tutorial (both 3D and 2D with shading)
Creators club 2D particle sample – Good resource but still in Game Studio 2 framework, needs a little love to get it going
Riemers 2D game series particles section – Riemers has always been one of my favourites, the site hasn’t been updated in a while and the tuts are in GS 3, but the forums are still alive and happening (I don’t believe I actually wrote that then).
Resource Pools
Resource pools are an attempt to make large lists of items as memory efficient as possible, the problem with using lists or fixed arrays are countless for areas where you need a large amount of objects that need to be created and destroyed on a regular basis. Lists incur garbage collection issues (which on the XBOX is really bad) and arrays are of fixed size so unless you know exactly how many you are going to need and how often you need to clear it our or loop through it, it becomes problematic.
Resources pools try to get through this by using several methods, they use the fixed limit of an array and combine it with a stack of some description (usually a Queue, which is a dynamic list of values which takes items from the top and adds them back to the bottom when done, like the queue in the bank, once you leave the front of the queue you have to go to the back if you want in again 🙂 ). Some just use a queue but this can be problematic.
The idea is simple enough, have a group of people standing around waiting to do work (no pun intended in the current state of the economy) and have a manager sitting on the side calling names. When a workers name is called they are set off and when they are done they go to the back of the queue.
Now the XNA sample just uses a Queue with no management, which is too light. The CodeCube class is nice and neat and extends this basic format efficiently, but doesn’t have enough umph to it (IMHO, but I may still use it for my own projects at some point where it’s useful).
So we are left with the SwampThing Resource pool, overall it provides a good framework for a memory efficient resource pool and there is good documentation for how to consume it. So add the Pool.CS class to the engine of our project as is for now. Look in SwampThings post above for some of the background detail.
Particles
Particle systems can be some of the most complex systems of many a game, where the desire for effect is key. It doesn’t have to be though, it just depends on your implementation and how far you want to go with it.
Particle systems are comprised of several Key components:
Particles – A single sprite texture or point sprite, the smallest component of particle effects. Like a single spark in a firework.
Particle Managers – Managers control how long an individual particle lives for, destroying it when it’s dead and updating the one’s still alive.
Particle Emitters – Emitters are a point of flow, like a hosepipe flooding out water, how you squeeze the pipe effects the kind of flow or spray emitted.
Particle Behaviours – Control what a particle does once it has been emitted, swirls, dives or splits in two. Many possibilities.
Particle Animation – A scripted animation of several effects or particles. Bit like a firework show.
We’ll only cover the first two or three here and maybe add more later, animations with particles in particular can be quite a bit of a challenge to get right.
An individual particle, as described above, is like a spark in a firework, however that spark can either be very small like a coloured pixel on a screen (sometimes referred to as a point sprite) or it can be quite large, which is usually referred to as a billboard sprite. Of course it can also be in-between, point is that the actual texture used for the particle can be anything you want.
If you look around there are several good test apps or samples of firework displays which are all driven by particle effects.
![]() |
![]() |
![]() |
Explosion effect (with Smoke) |
Smoke Plume effect |
Explosion Particle |
1. Particle class
Now in keeping with reuse, the MS particle class example is as good an effort as any, so we’ll reuse that, grab it from here off the codeplex site and add it to the engine folder (it has been updated slightly to fit in the project properly, just so you are aware, should you use the one direct from the CC particle sample). To keep things simple though we will update the namespace of the class to the same as our project this time. So update the namespace to:
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);">namespace</span> TestProject |
The class itself is easy to under stand and well commented, in short we have a set of attributes about the particle:
Position – location on the screen of the particle
Velocity – it’s velocity
Acceleration – Its acceleration state so the particle increases or decreases each update
Lifetime – how long the particle should live
Scale – so we can expand or reduce the particle size
Rotation Speed – how fast the particle rotates
Rotation – the particles current rotation, initialised to a random angle
Time Since Start – how long the particle has been alive for
We do need to add a few bits to the StarTrooperGame.cs class to support the MS version, nothing major, just adding the “RandomBetween” function and changing the state of the m_Random attribute/. First change the m_Random attribute at the start of the StarTlrooperGame class to be static, like so:
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);">static</span> Random m_Random = <span style="color: rgb(0, 0, 255);">new</span> Random(); |
Then add the following function towards the end of the class after the LoadResources 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: #region Helper Functions |
1 |
2: |
1 |
3: <span style="color: rgb(0, 128, 0);">// a handy little function that gives a random float between two</span> |
1 |
4: <span style="color: rgb(0, 128, 0);">// values. This will be used in several places in the sample, in particilar in</span> |
1 |
5: <span style="color: rgb(0, 128, 0);">// ParticleSystem.InitializeParticle.</span> |
1 |
6: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">static</span> <span style="color: rgb(0, 0, 255);">float</span> RandomBetween(<span style="color: rgb(0, 0, 255);">float</span> min, <span style="color: rgb(0, 0, 255);">float</span> max) |
1 |
7: { |
1 |
8: <span style="color: rgb(0, 0, 255);">return</span> min + (<span style="color: rgb(0, 0, 255);">float</span>)m_Random.NextDouble() * (max - min); |
1 |
9: } |
1 |
10: |
1 |
11: #endregion |
Finally add the following property for the Random attribute, this helps us later when we need random values for particles or emitters:
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, 128, 0);">// a random number generator that the whole project can share.</span> |
1 |
2: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">static</span> Random Random { <span style="color: rgb(0, 0, 255);">get</span> { <span style="color: rgb(0, 0, 255);">return</span> m_Random; } } |
2. Particle Manager
Now this class we’ll create from scratch as the sample that is available is a bit hard to read and Swampthings version although complete doe beat around the bush a bit. Since we also want to have more control over how the particles flow we need to remove some of the randomness (adding a but of particle behaviour)
So right click on the Engine folder in the solution explorer and select new item, ensure you have “XNA Game Studio 3.1” highlighted in the tree on the left hand side and select the “Game Component” option and enter a name of “ParticleManager”.
What you will see now is the standard framework for an XNA game component, however we need a drawable game component as we want it to draw to the screen, pretty obvious really (It’s still a mystery why Drawable game component is not an option on the new item screen). So to change this into a drawable game component, we need to update the following:
Change the inherited type from Game Component to Drawable Game component
Add the Load and Unload Resource overloads
Add the overloaded Draw function
This is what it should look like after it’s updated:
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);">class</span> ParticleManager : Microsoft.Xna.Framework.DrawableGameComponent |
1 |
2: { |
1 |
3: <span style="color: rgb(0, 0, 255);">public</span> ParticleManager(Game game) |
1 |
4: : <span style="color: rgb(0, 0, 255);">base</span>(game) |
1 |
5: { |
1 |
6: <span style="color: rgb(0, 128, 0);">// TODO: Construct any child components here</span> |
1 |
7: } |
1 |
8: |
1 |
9: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
10: <span style="color: rgb(128, 128, 128);">/// Allows the game component to perform any initialization it needs to before starting</span> |
1 |
11: <span style="color: rgb(128, 128, 128);">/// to run. This is where it can query for any required services and load content.</span> |
1 |
12: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
13: <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 |
14: { |
1 |
15: <span style="color: rgb(0, 128, 0);">// TODO: Add your initialization code here</span> |
1 |
16: |
1 |
17: <span style="color: rgb(0, 0, 255);">base</span>.Initialize(); |
1 |
18: } |
1 |
19: |
1 |
20: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
21: <span style="color: rgb(128, 128, 128);">/// LoadContent will be called once per game and is the place to load</span> |
1 |
22: <span style="color: rgb(128, 128, 128);">/// all of your content.</span> |
1 |
23: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
24: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">override</span> <span style="color: rgb(0, 0, 255);">void</span> LoadContent() |
1 |
25: { |
1 |
26: <span style="color: rgb(0, 128, 0);">// TODO: use this.Content to load your game content here</span> |
1 |
27: } |
1 |
28: |
1 |
29: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
30: <span style="color: rgb(128, 128, 128);">/// UnloadContent will be called once per game and is the place to unload</span> |
1 |
31: <span style="color: rgb(128, 128, 128);">/// all content.</span> |
1 |
32: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
33: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">override</span> <span style="color: rgb(0, 0, 255);">void</span> UnloadContent() |
1 |
34: { |
1 |
35: <span style="color: rgb(0, 128, 0);">// TODO: Unload any non ContentManager content here</span> |
1 |
36: } |
1 |
37: |
1 |
38: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
39: <span style="color: rgb(128, 128, 128);">/// Allows the game component to update itself.</span> |
1 |
40: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
41: <span style="color: rgb(128, 128, 128);">/// <param name="gameTime">Provides a snapshot of timing values.</param></span> |
1 |
42: <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 |
43: { |
1 |
44: <span style="color: rgb(0, 128, 0);">// TODO: Add your update code here</span> |
1 |
45: |
1 |
46: <span style="color: rgb(0, 0, 255);">base</span>.Update(gameTime); |
1 |
47: } |
1 |
48: |
1 |
49: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
50: <span style="color: rgb(128, 128, 128);">/// This is called when the game should draw itself.</span> |
1 |
51: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
52: <span style="color: rgb(128, 128, 128);">/// <param name="gameTime">Provides a snapshot of timing values.</param></span> |
1 |
53: <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 |
54: { |
1 |
55: <span style="color: rgb(0, 128, 0);">// TODO: Add your drawing code here</span> |
1 |
56: |
1 |
57: <span style="color: rgb(0, 0, 255);">base</span>.Draw(gameTime); |
1 |
58: } |
1 |
59: } |
1 |
60: |
With the base component in place, it’s best to add the component to the game, mainly so you don’t forget to later and wonder why it’s not working
So in the StarTrooperGame class add the following after the gamer service component:
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);">this</span>.Components.Add(<span style="color: rgb(0, 0, 255);">new</span> ParticleManager(<span style="color: rgb(0, 0, 255);">this</span>)); |
There, now with the manager in place we can start defining how it should work.
3. Particle Emitter
The particle emitter is the work horse of any particle engine and like Sprites we have a large template from which to create a multitude of different effects based upon a set of customisable attributes.
So the template Particle Emitter takes care of individual particle management and generation plus some default behaviour, the individual particle effects define how each effect works and can supplement the defaults or completely replace them as necessary.
As this section is quite long, we will go through it section by section. First off create a new class in the engine folder called “ParticleEmitter.cs” (don’t forget to update the namespace and remove the .Engine part)
First the class and constructor:
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);">class</span> ParticleEmitter |
1 |
2: { |
1 |
3: <span style="color: rgb(0, 128, 0);">// Position, Velocity, and Acceleration represent exactly what their names</span> |
1 |
4: <span style="color: rgb(0, 128, 0);">// indicate. They are public fields rather than properties so that users</span> |
1 |
5: <span style="color: rgb(0, 128, 0);">// can directly access their .X and .Y properties.</span> |
1 |
6: <span style="color: rgb(0, 0, 255);">public</span> Vector2 EmitterPosition = Vector2.Zero; |
1 |
7: <span style="color: rgb(0, 0, 255);">public</span> Vector2 EmitterVelocity = Vector2.Zero; |
1 |
8: <span style="color: rgb(0, 0, 255);">public</span> Vector2 EmitterAcceleration = Vector2.Zero; |
1 |
9: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">float</span> ParticleCycleTime = 0; |
1 |
10: |
1 |
11: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
12: <span style="color: rgb(128, 128, 128);">/// Constructs a new ParticleEmitter.</span> |
1 |
13: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
14: <span style="color: rgb(128, 128, 128);">/// <remarks>As this is intended to be used inside a resource pool,</span> |
1 |
15: <span style="color: rgb(128, 128, 128);">/// it needs to have a parameterless constructor, for initialising </span> |
1 |
16: <span style="color: rgb(128, 128, 128);">/// we have a separate initilise class</remarks></span> |
1 |
17: <span style="color: rgb(0, 0, 255);">public</span> ParticleEmitter() |
1 |
18: { |
1 |
19: } |
The code is fairly well commented, the main thing to notice here (that’s different from most the samples) is that the Emitter can move, so we have some attributes (like the Particles) to control movement. We could just leave the Emitter static so it is just the point at which particles are launched from and have them move themselves, but this limits us for certain effects which might revolve around the emitter or where outside forces can effect particle generation. We also couldn’t effectively do collision detection against individual particles (more on that later in the series), although it has been done by others with more advanced particle engines.
We also have an extra attribute to control how often particles are sent out from the emitter but more on that later.
Next is the initialise function, which sets an emitter up and creates the pool for the particles:
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);">/// override the base class's Initialize to do some additional work; we want to</span> |
1 |
3: <span style="color: rgb(128, 128, 128);">/// call InitializeConstants to let subclasses set the constants that we'll use.</span> |
1 |
4: <span style="color: rgb(128, 128, 128);">/// </span> |
1 |
5: <span style="color: rgb(128, 128, 128);">/// also, the particle array and freeParticles queue are set up here.</span> |
1 |
6: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
7: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">void</span> Initialize(<span style="color: rgb(0, 0, 255);">string</span> texture, <span style="color: rgb(0, 0, 255);">int</span> howManyEffects) |
1 |
8: { |
1 |
9: |
1 |
10: Texture2D tex = StarTrooperGame.ParticleManager.LoadTexture(texture); |
1 |
11: m_texture = texture; |
1 |
12: m_howManyEffects = howManyEffects; |
1 |
13: <span style="color: rgb(0, 128, 0);">// Calculate the center. this'll be used in the draw call, we</span> |
1 |
14: <span style="color: rgb(0, 128, 0);">// always want to rotate and scale around this point.</span> |
1 |
15: m_origin.X = tex.Width / 2; |
1 |
16: m_origin.Y = tex.Height / 2; |
1 |
17: |
1 |
18: InitializeConstants(); |
1 |
19: |
1 |
20: <span style="color: rgb(0, 128, 0);">// Create a pool contiaining the maximum number of particles we will ever need for this effect.</span> |
1 |
21: particles = <span style="color: rgb(0, 0, 255);">new</span> SwampLib.Pool<Particle>(m_howManyEffects * maxNumParticles); |
1 |
22: |
1 |
23: m_active = <span style="color: rgb(0, 0, 255);">true</span>; |
1 |
24: } |
1 |
25: |
1 |
26: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
27: <span style="color: rgb(128, 128, 128);">/// this abstract function must be overriden by subclasses of ParticleEmitter.</span> |
1 |
28: <span style="color: rgb(128, 128, 128);">/// It's here that they should set all the constants marked in the region</span> |
1 |
29: <span style="color: rgb(128, 128, 128);">/// "constants to be set by subclasses", which give each ParticleEmitter its</span> |
1 |
30: <span style="color: rgb(128, 128, 128);">/// specific flavor.</span> |
1 |
31: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
32: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">virtual</span> <span style="color: rgb(0, 0, 255);">void</span> InitializeConstants() { } |
So we load the texture for the emitter (which is actually stored in the particle manager to reduce duplicate textures being loaded, but more on that later) and set the origin point for the emitter, which is impotent if the emitter needs to rotate.
We then call the Initialise Constants function which is the internal initialisation function for the different effects. In this Emitter template the initialisation constants function is an abstract function (meaning it has no code as it is provided by classes that inherit this template class)
Lastly we set up the pool for the particles and set the emitter as active.
Next up are the Add Particles function which control the flow of particles from the emitter, which can either produce a random number of particles depending on the constants or a fixed number.
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);">/// AddParticles's job is to add an effect somewhere on the screen. If there </span> |
1 |
3: <span style="color: rgb(128, 128, 128);">/// aren't enough particles in the freeParticles queue, it will use as many as </span> |
1 |
4: <span style="color: rgb(128, 128, 128);">/// it can. This means that if there not enough particles available, calling</span> |
1 |
5: <span style="color: rgb(128, 128, 128);">/// AddParticles will have no effect.</span> |
1 |
6: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
7: <span style="color: rgb(128, 128, 128);">/// <param name="where">where the particle effect should be created</param></span> |
1 |
8: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">void</span> AddParticles(Vector2 where) |
1 |
9: { |
1 |
10: <span style="color: rgb(0, 128, 0);">// the number of particles we want for this effect is a random number</span> |
1 |
11: <span style="color: rgb(0, 128, 0);">// somewhere between the two constants specified by the subclasses.</span> |
1 |
12: <span style="color: rgb(0, 0, 255);">int</span> numParticles = |
1 |
13: StarTrooperGame.Random.Next(minNumParticles, maxNumParticles); |
1 |
14: AddParticles(where, numParticles); |
1 |
15: |
1 |
16: } |
1 |
17: |
1 |
18: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
19: <span style="color: rgb(128, 128, 128);">/// AddParticles's job is to add an effect somewhere on the screen. If there </span> |
1 |
20: <span style="color: rgb(128, 128, 128);">/// aren't enough particles in the freeParticles queue, it will use as many as </span> |
1 |
21: <span style="color: rgb(128, 128, 128);">/// it can. This means that if there not enough particles available, calling</span> |
1 |
22: <span style="color: rgb(128, 128, 128);">/// AddParticles will have no effect.</span> |
1 |
23: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
24: <span style="color: rgb(128, 128, 128);">/// <param name="where">where the particle effect should be created</param></span> |
1 |
25: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">void</span> AddParticles(Vector2 where, <span style="color: rgb(0, 0, 255);">int</span> numParticles) |
1 |
26: { |
1 |
27: <span style="color: rgb(0, 128, 0);">// Create the desired number of particles, up to the number of available</span> |
1 |
28: <span style="color: rgb(0, 128, 0);">// particles in the pool.</span> |
1 |
29: numParticles = Math.Min(numParticles, particles.AvailableCount); |
1 |
30: <span style="color: rgb(0, 0, 255);">while</span> (numParticles-- > 0) |
1 |
31: { |
1 |
32: SwampLib.Pool<Particle>.Node p = particles.Get(); |
1 |
33: InitializeParticle(p.Item, where); |
1 |
34: } |
1 |
35: } |
This results in a set of active particles in the particle pool
As you can see above, adding particles causes them to be initialised, by default we produce a completely random effect, this can be overridden or supplemented by the actual effects as the class is defined as virtual:
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);">/// InitializeParticle randomizes some properties for a particle, then</span> |
1 |
3: <span style="color: rgb(128, 128, 128);">/// calls initialize on it. It can be overriden by subclasses if they </span> |
1 |
4: <span style="color: rgb(128, 128, 128);">/// want to modify the way particles are created. For example, </span> |
1 |
5: <span style="color: rgb(128, 128, 128);">/// SmokePlumeParticleSystem overrides this function make all particles</span> |
1 |
6: <span style="color: rgb(128, 128, 128);">/// accelerate to the right, simulating wind.</span> |
1 |
7: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
8: <span style="color: rgb(128, 128, 128);">/// <param name="p">the particle to initialize</param></span> |
1 |
9: <span style="color: rgb(128, 128, 128);">/// <param name="where">the position on the screen that the particle should be</span> |
1 |
10: <span style="color: rgb(128, 128, 128);">/// </param></span> |
1 |
11: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">virtual</span> <span style="color: rgb(0, 0, 255);">void</span> InitializeParticle(Particle p, Vector2 where) |
1 |
12: { |
1 |
13: <span style="color: rgb(0, 128, 0);">// first, call PickRandomDirection to figure out which way the particle</span> |
1 |
14: <span style="color: rgb(0, 128, 0);">// will be moving. velocity and acceleration's values will come from this.</span> |
1 |
15: Vector2 direction; |
1 |
16: <span style="color: rgb(0, 0, 255);">if</span> (EmitterVelocity == Vector2.Zero) |
1 |
17: direction = PickRandomDirection(); |
1 |
18: <span style="color: rgb(0, 0, 255);">else</span> |
1 |
19: direction = EmitterVelocity += EmitterAcceleration; |
1 |
20: |
1 |
21: <span style="color: rgb(0, 128, 0);">// pick some random values for our particle</span> |
1 |
22: <span style="color: rgb(0, 0, 255);">float</span> velocity = |
1 |
23: StarTrooperGame.RandomBetween(minInitialSpeed, maxInitialSpeed); |
1 |
24: <span style="color: rgb(0, 0, 255);">float</span> acceleration = |
1 |
25: StarTrooperGame.RandomBetween(minAcceleration, maxAcceleration); |
1 |
26: <span style="color: rgb(0, 0, 255);">float</span> lifetime = |
1 |
27: StarTrooperGame.RandomBetween(minLifetime, maxLifetime); |
1 |
28: <span style="color: rgb(0, 0, 255);">float</span> scale = |
1 |
29: StarTrooperGame.RandomBetween(minScale, maxScale); |
1 |
30: <span style="color: rgb(0, 0, 255);">float</span> rotationSpeed = |
1 |
31: StarTrooperGame.RandomBetween(minRotationSpeed, maxRotationSpeed); |
1 |
32: |
1 |
33: <span style="color: rgb(0, 128, 0);">// then initialize it with those random values. initialize will save those,</span> |
1 |
34: <span style="color: rgb(0, 128, 0);">// and make sure it is marked as active.</span> |
1 |
35: p.Initialize( |
1 |
36: where, velocity * direction, acceleration * direction, |
1 |
37: lifetime, scale, rotationSpeed); |
1 |
38: } |
This returns initialised particles ready for action.
Then we have the Update and supporting function which pretty much speak for themselves (note we don’t draw particles from the emitter as this is handled by 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: <span style="color: rgb(0, 128, 0);">// update is called by the Emitter on every frame. This is where the</span> |
1 |
2: <span style="color: rgb(0, 128, 0);">// particle's position and that kind of thing get updated.</span> |
1 |
3: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">virtual</span> <span style="color: rgb(0, 0, 255);">void</span> Update(<span style="color: rgb(0, 0, 255);">float</span> dt) |
1 |
4: { |
1 |
5: EmitterVelocity += EmitterAcceleration * dt; |
1 |
6: EmitterPosition += EmitterVelocity * dt; |
1 |
7: |
1 |
8: |
1 |
9: <span style="color: rgb(0, 0, 255);">foreach</span> (SwampLib.Pool<Particle>.Node node <span style="color: rgb(0, 0, 255);">in</span> particles.ActiveNodes) |
1 |
10: { |
1 |
11: Particle p = node.Item; |
1 |
12: |
1 |
13: <span style="color: rgb(0, 0, 255);">if</span> (p.Active) |
1 |
14: { |
1 |
15: <span style="color: rgb(0, 128, 0);">// ... and if they're active, update them.</span> |
1 |
16: p.Update(dt); |
1 |
17: <span style="color: rgb(0, 128, 0);">// if that update finishes them, return them to the pool.</span> |
1 |
18: <span style="color: rgb(0, 0, 255);">if</span> (!p.Active) |
1 |
19: { |
1 |
20: particles.Return(node); |
1 |
21: } |
1 |
22: } |
1 |
23: |
1 |
24: } |
1 |
25: |
1 |
26: |
1 |
27: } |
1 |
28: |
1 |
29: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
30: <span style="color: rgb(128, 128, 128);">/// PickRandomDirection is used by InitializeParticles to decide which direction</span> |
1 |
31: <span style="color: rgb(128, 128, 128);">/// particles will move. The default implementation is a random vector in a</span> |
1 |
32: <span style="color: rgb(128, 128, 128);">/// circular pattern.</span> |
1 |
33: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
34: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">virtual</span> Vector2 PickRandomDirection() |
1 |
35: { |
1 |
36: <span style="color: rgb(0, 0, 255);">float</span> angle = (<span style="color: rgb(0, 0, 255);">float</span>)StarTrooperGame.Random.NextDouble() * -MathHelper.TwoPi; |
1 |
37: <span style="color: rgb(0, 0, 255);">return</span> <span style="color: rgb(0, 0, 255);">new</span> Vector2((<span style="color: rgb(0, 0, 255);">float</span>)Math.Cos(angle), (<span style="color: rgb(0, 0, 255);">float</span>)Math.Sin(angle)); |
1 |
38: } |
The update loop simply updates all the particles for the emitter, the PickRandomDirection does exactly what it says on the tin and produces a random angle, used to give a random direction for a particle.
Lastly we have the properties and constants for the Emitter 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: #region properties |
1 |
2: |
1 |
3: <span style="color: rgb(0, 128, 0);">// the texture this particle system will use.</span> |
1 |
4: <span style="color: rgb(0, 0, 255);">private</span> String m_texture; |
1 |
5: <span style="color: rgb(0, 0, 255);">public</span> String Texture |
1 |
6: { |
1 |
7: <span style="color: rgb(0, 0, 255);">get</span> { <span style="color: rgb(0, 0, 255);">return</span> m_texture; } |
1 |
8: } |
1 |
9: |
1 |
10: <span style="color: rgb(0, 128, 0);">// the origin when we're drawing textures. this will be the middle of the</span> |
1 |
11: <span style="color: rgb(0, 128, 0);">// texture.</span> |
1 |
12: <span style="color: rgb(0, 0, 255);">private</span> Vector2 m_origin; |
1 |
13: <span style="color: rgb(0, 0, 255);">public</span> Vector2 Origin |
1 |
14: { |
1 |
15: <span style="color: rgb(0, 0, 255);">get</span> { <span style="color: rgb(0, 0, 255);">return</span> m_origin; } |
1 |
16: <span style="color: rgb(0, 0, 255);">set</span> { m_origin = <span style="color: rgb(0, 0, 255);">value</span>; } |
1 |
17: } |
1 |
18: <span style="color: rgb(0, 128, 0);">// this number represents the maximum number of effects this particle system</span> |
1 |
19: <span style="color: rgb(0, 128, 0);">// will be expected to draw at one time. this is set in the constructor and is</span> |
1 |
20: <span style="color: rgb(0, 128, 0);">// used to calculate how many particles we will need.</span> |
1 |
21: <span style="color: rgb(0, 0, 255);">private</span> <span style="color: rgb(0, 0, 255);">int</span> m_howManyEffects; |
1 |
22: |
1 |
23: <span style="color: rgb(0, 128, 0);">// is this emitter still alive? if not then particle should no longer be drawn or updated.</span> |
1 |
24: <span style="color: rgb(0, 0, 255);">private</span> <span style="color: rgb(0, 0, 255);">bool</span> m_active = <span style="color: rgb(0, 0, 255);">false</span>; |
1 |
25: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">bool</span> Active |
1 |
26: { |
1 |
27: <span style="color: rgb(0, 0, 255);">get</span> { <span style="color: rgb(0, 0, 255);">return</span> m_active; } |
1 |
28: <span style="color: rgb(0, 0, 255);">set</span> { m_active = <span style="color: rgb(0, 0, 255);">value</span>; } |
1 |
29: } |
1 |
30: |
1 |
31: <span style="color: rgb(0, 128, 0);">// The pool of particles used by this system. </span> |
1 |
32: <span style="color: rgb(0, 128, 0);">// The pool automatically manages one-time allocation of particles, and reuses</span> |
1 |
33: <span style="color: rgb(0, 128, 0);">// particles when they are returned to the pool.</span> |
1 |
34: <span style="color: rgb(0, 0, 255);">public</span> SwampLib.Pool<Particle> particles; |
1 |
35: |
1 |
36: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
37: <span style="color: rgb(128, 128, 128);">/// returns the number of particles that are available for a new effect.</span> |
1 |
38: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
39: <span style="color: rgb(0, 0, 255);">public</span> <span style="color: rgb(0, 0, 255);">int</span> FreeParticleCount |
1 |
40: { |
1 |
41: <span style="color: rgb(0, 0, 255);">get</span> |
1 |
42: { |
1 |
43: <span style="color: rgb(0, 128, 0);">// Get the number of particles in the pool that are available for use.</span> |
1 |
44: <span style="color: rgb(0, 0, 255);">return</span> particles.AvailableCount; |
1 |
45: } |
1 |
46: } |
1 |
47: |
1 |
48: #endregion |
1 |
49: |
1 |
50: <span style="color: rgb(0, 128, 0);">// This region of values control the "look" of the particle system, and should </span> |
1 |
51: <span style="color: rgb(0, 128, 0);">// be set by deriving particle systems in the InitializeConstants method. The</span> |
1 |
52: <span style="color: rgb(0, 128, 0);">// values are then used by the virtual function InitializeParticle. Subclasses</span> |
1 |
53: <span style="color: rgb(0, 128, 0);">// can override InitializeParticle for further</span> |
1 |
54: <span style="color: rgb(0, 128, 0);">// customization.</span> |
1 |
55: #region constants to be <span style="color: rgb(0, 0, 255);">set</span> by subclasses |
1 |
56: |
1 |
57: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
58: <span style="color: rgb(128, 128, 128);">/// minNumParticles and maxNumParticles control the number of particles that are</span> |
1 |
59: <span style="color: rgb(128, 128, 128);">/// added when AddParticles is called. The number of particles will be a random</span> |
1 |
60: <span style="color: rgb(128, 128, 128);">/// number between minNumParticles and maxNumParticles.</span> |
1 |
61: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
62: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">int</span> minNumParticles = 0; |
1 |
63: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">int</span> maxNumParticles = 0; |
1 |
64: |
1 |
65: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
66: <span style="color: rgb(128, 128, 128);">/// minInitialSpeed and maxInitialSpeed are used to control the initial velocity</span> |
1 |
67: <span style="color: rgb(128, 128, 128);">/// of the particles. The particle's initial speed will be a random number </span> |
1 |
68: <span style="color: rgb(128, 128, 128);">/// between these two. The direction is determined by the function </span> |
1 |
69: <span style="color: rgb(128, 128, 128);">/// PickRandomDirection, which can be overriden.</span> |
1 |
70: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
71: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">float</span> minInitialSpeed = 0; |
1 |
72: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">float</span> maxInitialSpeed = 0; |
1 |
73: |
1 |
74: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
75: <span style="color: rgb(128, 128, 128);">/// minAcceleration and maxAcceleration are used to control the acceleration of</span> |
1 |
76: <span style="color: rgb(128, 128, 128);">/// the particles. The particle's acceleration will be a random number between</span> |
1 |
77: <span style="color: rgb(128, 128, 128);">/// these two. By default, the direction of acceleration is the same as the</span> |
1 |
78: <span style="color: rgb(128, 128, 128);">/// direction of the initial velocity.</span> |
1 |
79: <span style="color: rgb(128, 128, 128);">/// </summary></span> |
1 |
80: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">float</span> minAcceleration = 0; |
1 |
81: <span style="color: rgb(0, 0, 255);">protected</span> <span style="color: rgb(0, 0, 255);">float</span> maxAcceleration = 0; |
1 |
82: |
1 |
83: <span style="color: rgb(128, 128, 128);">/// <summary></span> |
1 |
84: <span style="color: rgb(128, 128, 128);">/// minRotationSpeed and maxRotationSpeed control the particles' angular</span> |
1 |
85: <span style="color: rgb(128, 128, 128);">/// velocity: the speed at which particles will rotate. Each particle's rotation</span> |
1 |
86: <span style="color: rgb(128, 128, 128);">/// speed will be a random number between minRotationSpeed and maxRotationSpeed.</span> |