In this series of tutorials, I'll show you how to make a neon twin stick shooter, like Geometry Wars, in XNA. The goal of these tutorials is not to leave you with an exact replica of Geometry Wars, but rather to go over the necessary elements that will allow you to create your own high-quality variant.

Overview

In the series so far, we've set up the gameplay and added bloom. Next up, we'll add particle effects.

Warning: Loud!

Particle effects are created by making a large number of small particles. They are very versatile and can be used to add flair to nearly any game. In Shape Blaster we will make explosions using particle effects. We will also use particle effects to create exhaust fire for the player's ship, and to add visual flair to the black holes. Plus, we'll look at how to make particles interact with the gravity from the black holes.

The ParticleManager Class

We'll start by creating a ParticleManager class that will store, update, and draw all the particles. We'll make this class general enough that it can easily be reused in other projects. To keep the ParticleManager general, it won't be responsible for how the particles look or move; we'll handle that elsewhere.

Particles tend to be created and destroyed rapidly and in large numbers. We will use an object pool to avoid creating large amounts of garbage. This means we will allocate a large number of particles up front, and then keep reusing these same particles. We will also make ParticleManager have a fixed capacity. This will simplify it and help ensure we don't exceed our performance or memory limitations by creating too many particles. When the maximum number of particles is exceeded, we will start replacing the oldest particles with new ones.

We'll make the ParticleManager a generic class. This will allow us to store custom state information for the particles without hard coding it into the ParticleManager itself. We'll also create a nested Particle class.

The Particle class has all the information required to display a particle and manage its lifetime. The generic parameter, T State, is there to hold any additional data we may need for our particles. What data is needed will vary depending on the particle effects desired; it could be used to store velocity, acceleration, rotation speed, or anything else you may need.

To help manage the particles, we'll need a class that functions as a circular array, meaning that indices that would normally be out of bounds will instead wrap around to the beginning of the array. This will make it easy to replace the oldest particles first if we run out of space for new particles in our array. We add the following as a nested class in ParticleManager.

We can set the Start property to adjust where index zero in our CircularParticleArray corresponds to in the underlying array, and Count will be used to track how many active particles are in the list. We will ensure that the particle at index zero is always the oldest particle. If we replace the oldest particle with a new one, we will simply increment Start, which essentially rotates the circular array.

Now that we have our helper classes, we can start filling out the ParticleManager class. We'll need some member variables, and a constructor.

The first variable declared, updateParticle, will be a custom method that updates the particles appropriately for the desired effect. A game can have multiple ParticleManagers that update differently if necessary. We also create a CircularParticleList and fill it with empty particles. The constructor is the only place the ParticleManager allocates memory.

Next we add the CreateParticle() method, which creates a new particle using the next unused particle in the pool, or the oldest particle if there are no unused particles.

Particles may be destroyed at any time. We need to remove these particles while ensuring the other particles remain in the same order. We can do this by iterating through the list of particles while keeping track how many have been destroyed. As we go, we move each active particle in front of all the destroyed particles by swapping it with the first destroyed particle. Once all the destroyed particles are at the end of the list, we deactivate them by setting the list's Count variable to the number of active particles. Destroyed particles will remain in the underlying array, but won't be updated or drawn.

ParticleManager.Update() handles updating each particle and removing destroyed particles from the list.

The ParticleState Struct

The next thing to do is make a custom class or struct to customize how the particles will look in Shape Blaster. There will be several different types of particles in Shape Blaster that behave slightly differently, so we'll start by creating an enum for the particle type. We'll also need variables for the particle's velocity and initial length.

Enemy Explosions

We'll come back and improve this method in a moment. First, let's create some particle effects so we can actually test out our changes. In GameRoot, declare a new ParticleManager and call its Update() and Draw() methods.

This creates 120 particles that will shoot outwards with different speeds in all directions. The random speed is weighted such that particles are more likely to travel near the maximum speed. This will cause more particles to be at the edge of the explosion as it expands. The particles last 190 frames, or just over three seconds.

You can now run the game and watch enemies explode. However, there are still some improvements to be made for the particle effects.

The first issue is the particles disappear abruptly once their duration runs out. It would be nicer if they could smoothly fade out. But let's go a bit further than this and make the particles glow brighter when they are moving fast. Also, it looks nice if we lengthen fast moving particles and shorten slow moving ones.

Modify the ParticleState.UpdateParticle() method as follows (changes are highlighted).

The explosions look much better now, but they are all the same color. We can give them more variety by choosing random colors. One method of producing random colors is to choose the red, blue and green components randomly, but this will produce a lot of dull colors and we'd like our particles to have a neon light appearance. We can have more control over our colors by specifying them in the HSV color space. HSV stands for hue, saturation, and value. We'd like to pick colors with a random hue but a fixed saturation and value. We need a helper function that can produce a color from HSV values.

Now we can modify Enemy.WasShot() to use random colors. To make the explosion color less monotonous, we'll pick two nearby key colors for each explosion and linearly interpolate between them by a random amount for each particle.

You can play around with the color generation to suit your preferences. An alternative technique that works well is to hand pick a number of color patterns for explosions and choose randomly among your pre-chosen color schemes.

Bullet Explosions

We can also make the bullets explode when they reach the edge of the screen. We'll essentially do the same thing we did for enemy explosions.

You may notice that giving the particles a random direction is wasteful, because at least half the particles will immediately head off-screen (more if the bullet explodes in a corner). We could do some extra work to ensure particles are only given velocities opposite to the wall they are facing. Instead, though, we'll take a cue from Geometry Wars and make all particles bounce off the walls. Any particles heading off-screen will be bounced back.

Add the following lines to ParticleState.UpdateParticle() anywhere between the first and last lines.

Here, n is the unit vector pointing towards the black hole. The attractive force is a modified version of the inverse square function. The first modification is that the denominator is \(distance^2 + 10,000\). This causes the attractive force to approach a maximum value instead of tending towards infinity as the distance becomes very small. When the distance is much greater than 100 pixels, \(distance^2\) becomes much greater than 10,000. Therefore, adding 10,000 to \(distance^2\) has a very small effect, and the function approximates a normal inverse square function. However, when the distance is much smaller than 100 pixels, the distance has a small effect on the value of the denominator, and the equation becomes approximately equal to:

vel += n;

The second modification is adding a sideways component to the velocity when the particles get close enough to the black hole. This serves two purposes. First, it makes the particles spiral clockwise in towards the black hole. Second, when the particles get close enough, they will reach equilibrium and form a glowing circle around the black hole.

Producing Particles

Black holes will produce two types of particles. First, they will periodically spray out particles that will orbit around them. Second, when a black hole is shot, it will spray out special particles that are not affected by its gravity.

This works mostly the same way as the other particle explosions. One difference is that we pick the hue of the color based on the total elapsed game time. If you shoot the black hole multiple times in rapid succession, you will see the hue of the explosions gradually rotate. This looks less messy than using random colors while still allowing variation.

For the orbiting particle spray, we need to add a variable to the BlackHole class to track the direction in which we are currently spraying particles.

This will cause the black holes to spray spurts of purple particles that will form a cool glowing ring that orbits around the black hole, like so:

Ship Exhaust Fire

As dictated by the laws of geometric-neon physics, the player's ship propels itself by jetting a stream of fiery particles out its exhaust pipe. With our particle engine in place, this effect is easy to make and adds visual flair to the ship's movement.

As the ship moves, we create three streams of particles: a center stream that fires straight out the back of the ship, and two side streams whose angles swivel back and forth relative to the ship. The two side streams swivel in opposite directions to make a criss-crossing pattern. The side streams have a redder color, while the center stream has a hotter, yellow-white color. The animation below shows the effect.

To make the fire glow more brightly than it would from bloom alone, we will have the ship emit additional particles that look like this:

These particles will be tinted and blended with the regular particles. The code for the entire effect is shown below.

There's nothing sneaky going on in this code. We use a sine function to produce the swivelling effect in the side streams by varying their sideways velocity over time. For each stream, we create two overlapping particles per frame: one semitransparent-white LineParticle and a coloured glow particle behind it. Call MakeExhaustFire() at the end of PlayerShip.Update(), immediately before setting the ship's velocity to zero.

Conclusion

With all these particle effects, Shape Blaster is starting to look pretty cool. In the final part of this series, we will add one more awesome effect: the warping background grid.