Hudzilla Coding Academy: Project Ten

Code

Hudzilla Coding Academy

The last few projects have been relatively easy, but with your skills racing forward as they are it's definitely time you tackled something much bigger. How does 360 lines of code sound to you? Scary? We're going to produce a game that's simple and fun to play, and along the way you'll learn some valuable new coding skills. The game we're going to make is called Bang! and the idea is simple: as different-coloured fireworks launch into the air, the player needs to use their mouse to select the fireworks with the same colour, then press Space to explode them. The more they select of the same colour, the more points they get.

Just to make things a bit more lively, I'm going to introduce you to something called a particle system, which is a very common special effects technique for games that simulates explosions, fire, smoke and much more. Sound good? Sound worth the effort of writing almost 400 lines of code? Let's go!

First steps

Create a new command-line project in MonoDevelop and call it Bang. As with Project Four: Fish Feast and Project Six: Bubble Trouble you'll need to copy the SdlDotNet.dll and Tao.Sdl.dll files into your project directory then add them as references in your project. While you're adding references, add one for System.Drawing.

You'll also need to change the output directory for your program so that it points to the root directory of your project - again, see project four for how to do that.

In the source code for this project I've provided a "media" directory containing pictures for use in the game; copy that into your project directory so that it's in the same place as the Main.cs file that MonoDevelop made for you.

That's all the media in place; now we just need to create the three class definitions that be used to power the game. Right-click on the Bang project (not "Solution Bang" but the "Bang" that's beneath it) and choose Add > New File, then choose Empty Class from the window that appears. Name it "Bang". Repeat the procedure to create classes called Explosion and Firework, which gives us the three classes that will power our game.

In Bang.cs, Explosion.cs and Firework.cs, set the "using" statements to these:

using SdlDotNet.Core;
using SdlDotNet.Graphics;
using SdlDotNet.Graphics.Primitives;
using SdlDotNet.Input;
using System;
using System.Collections.Generic;
using System.Drawing;

While you're in each of those files, you'll see that MonoDevelop "helpfully" created a constructor method for each class - just delete them, because we won't be using them. As a reminder, constructor methods get called when an object of a class is created, and they look something like this:

public Bang()
{
}

Like I said, just delete them. Now open up Main.cs and replace the Console.WriteLine() call with this:

Bang game = new Bang();
game.Run();

That will create a new instance of our game and start it running - or at least it will once we make the Run() method!

Open up Bang.cs and put these variable declarations inside the Bang class:

Surface sfcGameWindow;
Surface sfcBackground = new Surface("media/background.jpg");
Random Rand = new Random();

const int GameWidth = 640;
const int GameHeight = 480;
const int GameHalfWidth = GameWidth / 2;

That gives us just enough data to fire up a basic game screen and show the background. But first we need to drop in a few basic method to handle running a simple, skeleton version of our game.

First up, the Run() method. this needs to create the game window, hook up methods for the events we'll be using, then hand control over to SDL. Which events will we be using? Well, quite a few, actually: we want to read mouse movement, mouse button clicks, keyboard presses, game ticks and the quit signal, so we need to hook them all up to empty methods that we'll fill out later. Here's how the first-draft Run() method should look - put this in Bang.cs:

public void Run() {
	sfcGameWindow = Video.SetVideoMode(GameWidth, GameHeight);
	Events.MouseMotion += new EventHandler<MouseMotionEventArgs>(Events_MouseMotion);
	Events.MouseButtonDown += new EventHandler<MouseButtonEventArgs>(Events_MouseButtonDown);
	Events.KeyboardDown += new EventHandler<KeyboardEventArgs>(Events_KeyboardDown);
	Events.Tick += new EventHandler<TickEventArgs>(Events_Tick);
	Events.Quit += new EventHandler<QuitEventArgs>(Events_Quit);

	Events.Run();
}

You'll need to provide skeleton methods for Events_MouseMotion(), Events_MouseButtonDown(), Events_KeyboardDown(), Events_Tick() and Events_Quit(). They can be pretty much empty for now, with the exception of Events_Tick() and Events_Quit() which will use the same calls to Update()/Draw() and Events.QuitApplication() as seen in project six. If you missed that project, go back and start there first because I'm not going to explain it all again here!

So, here are the skeleton methods for you to put into Bang.cs:

void Events_MouseMotion(object sender, MouseMotionEventArgs e) {

}

void Events_MouseButtonDown(object sender, MouseButtonEventArgs e) {

}

void Events_KeyboardDown(object sender, KeyboardEventArgs e) {

}

void Events_Tick(object sender, TickEventArgs e) {
	Update(e.SecondsElapsed);
	Draw();
}

private void Draw() {
	sfcGameWindow.Blit(sfcBackground);
	sfcGameWindow.Update();
}

private void Update(float elapsed) {

}

void Events_Quit(object sender, QuitEventArgs e) {
	Events.QuitApplication();
}

As you can see, the Draw() method just draws the background image then tells SDL to update the screen.

Finally, copy in the PointOverRect method that we used in previous games to detect whether a mouse click is over a rectangle:

bool PointOverRect(float x1, float y1, float x2, float y2, int width, int height) {
	if (x1 >= x2 && x1 <= x2 + width) {
		if (y1 >= y2 && y1 <= y2 + height) {
			return true;
		}
	}

	return false;
}

What makes a firework work?

Phew! That's all the setup code out of the way: if you run the "game" now you'll see it brings up a window that shows the background. Nothing happens - after all that work! As you can imagine, it's fairly common practice to take a snapshot of your project right now, then squirrel that away to use to kickstart any future game projects.

The first thing we're going to do is define what we need to store about a firework to make it functional in the game:

  • What angle is it moving at?
  • Which colour is it?
  • What is its X and Y position?
  • What is its X and Y speed?
  • Have we clicked on it?

The combination of the angle and colour information is enough to let us know which graphic should be used to draw the firework. Here's how the Firework class looks in C#:

class Firework {
	public int Angle;
	public Color Colour;
	public float X;
	public float Y;
	public float XSpeed;
	public float YSpeed;
	public bool IsSelected;
}

As you should recall from project six, using floats for position rather than integers allows us to store the position of things more accurately by making frame-independent movement possible. More on this later!

And now comes the first tricky bit: we need to load all the firework graphics into the game. This might sound easy, but it's actually quite hard because we need to make sure it's all carefully organised by direction and type.

As far back as project two we looked at the List generic data type, which is an array that holds data of a specific type. That works well for storing data in a given order: you add items to the list, then read them back out simply by referring to their position in the list.

In this tutorial I want to introduce you to a new data type called a Dictionary, which lets you add items to an array at a specific location rather than just at the next available slot. What can the location be? Well, just about anything - if you want to store something at location "fish", that's fine. If you want to store something at location 90, that's fine - even if locations 0 through 89 don't exist.

Unlike lists, dictionaries store their items in any positions you want - you can even define what kind of data the position is.

Unlike lists, dictionaries store their items in any positions you want - you can even define what kind of data the position is.

As with a List, you can store anything you want in a Dictionary, and this is where things can become a bit tricky. You see, we're going to use a Dictionary to store Dictionaries, and each dictionary inside will store SDL Surfaces for the fireworks we'll be drawing to the screen. If that doesn't make sense, it will once you see the code that actually loads the fireworks. First, add this code just before the Run() method:

List<Firework> Fireworks = new List<Firework>();
Dictionary<int, Dictionary<Color, Surface>> FireworkTypes = new Dictionary<int, Dictionary<Color, Surface>>();

Technically it's "bad style" to have a dictionary storing other dictionaries, but it's a really quick way of solving our problem! Regardless, it's that last line that's likely to trip you up. Let me show you a simpler example: a dictionary designed to hold people's names and ages:

Dictionary<string, int> People = new Dictionary<string, int>();

Like I said earlier, a dictionary store any value at any location. In the People dictionary above, we're telling Mono that the value type we want to store will be an int, and the location - known as the "key" - will be specified as a string. Using that example, we could add items to the dictionary like this:

People.Add("Paul Hudson", 29);
People.Add("Ildiko Hudson", 30);
People.Add("Nick Veitch", 59);

We can then read values back out from the dictionary like this:

Console.WriteLine("Paul's age is " + People["Paul Hudson"]);

There is one proviso, though, which is that you shouldn't add two values to a dictionary using the same key. That is, code like this will cause an error:

People.Add("Paul Hudson", 29);
People.Add("Ildiko Hudson", 30);
People.Add("Nick Veitch", 59);
People.Add("Paul Hudson", 17);

Let's go back to the dictionary we'll be using in this project:

Dictionary<int, Dictionary<Color, Surface>> FireworkTypes = new Dictionary<int, Dictionary<Color, Surface>>();

What that means is that our key will be an integer, and the value is a dictionary with Colours as the keys and SDL Surfaces as the values. If you look in the media directory for this game you'll see there are three types of firework: blue, green and red. And for each colour, we also have the same firework at three different angles. So what we're going to do is add each colour and firework image for a given angle to a dictionary, then add that list to the dictionary at the specified angle. That means we can retrieve the correct picture for a firework by knowing its angle and colour.

So, let's start by loading all the firework images that point upwards. Add this to the start of your Run() method:

Dictionary<Color, Surface> fwtypes = new Dictionary<Color, Surface>();
fwtypes.Add(Color.Blue, new Surface("media/firework_blue.png"));
fwtypes.Add(Color.Green, new Surface("media/firework_green.png"));
fwtypes.Add(Color.Red, new Surface("media/firework_red.png"));
FireworkTypes.Add(90, fwtypes);

First we create a new dictionary with Color for the key and Surface for the value. We then add the three firework images for this angle, each time using the correct colour value for it. Once that's done, we add that dictionary to the FireworkTypes parent dictionary with the key 90 - an angle of 0 is pointing to the left, so 90 is pointing directly upwards.

Using this method, if you wanted to read the SDL Surface for the blue firework at 90 degrees, you would use FireworkTypes[90][Color.Blue], which is nice and easy to read.

We need to load all the other firework images as well, so add this code beneath the code from above:

fwtypes = new Dictionary<Color, Surface>();
fwtypes.Add(Color.Blue, new Surface("media/firework_blue_135.png"));
fwtypes.Add(Color.Green, new Surface("media/firework_green_135.png"));
fwtypes.Add(Color.Red, new Surface("media/firework_red_135.png"));
FireworkTypes.Add(135, fwtypes);

fwtypes = new Dictionary<Color, Surface>();
fwtypes.Add(Color.Blue, new Surface("media/firework_blue_45.png"));
fwtypes.Add(Color.Green, new Surface("media/firework_green_45.png"));
fwtypes.Add(Color.Red, new Surface("media/firework_red_45.png"));
FireworkTypes.Add(45, fwtypes);

And now for a little bit of SDL magic: if you look at the firework pictures in the media directory, you'll notice they all have a magenta background. We can tell SDL that we want magenta to be drawn as transparent on the screen, which will cause all those magenta parts of the pictures to be invisible. To do that, we just need to loop through every angle and every picture, setting its TransparentColor and Transparent values. Put this just after the previous code:

foreach(Dictionary<Color, Surface> direction in FireworkTypes.Values) {
	foreach (Surface firework in direction.Values) {
		firework.TransparentColor = Color.Magenta;
		firework.Transparent = true;
	}
}

We've used the foreach loop a lot in previous projects, but this is the first time we've used it to loop over a dictionary, and also the first time we've used it to read a complex data type. So: as you might have guessed, when looping over a dictionary, you can choose to read either the keys (FireworkTypes.Keys) or the values (FireworkTypes.Values).

Each firework is available at three angles, but all of them have a magenta background. We can knock that out with SDL.

Each firework is available at three angles, but all of them have a magenta background. We can knock that out with SDL.

You also need to specify the exact kind of data you're getting back - this is nothing new, as we've been using things like "foreach (string str in somestrings)" for a while, but the difference here is that when you read a generic data type back in, such as a dictionary, you need to tell Mono exactly what kind of dictionary you're working with.

Yes, this can be a bit of a pain, but if you remember all the way back to project one you can use the "var" keyword in place of data types, so if you wanted to you could be a bit lazy and write this:

foreach(var direction in FireworkTypes.Values) {
	foreach (Surface firework in direction.Values) {
		firework.TransparentColor = Color.Magenta;
		firework.Transparent = true;
	}
}

Once we've set the TransparentColor and Transparent properties, those magenta parts will no longer appear - SDL will ensure they are automatically invisible.

Ready, aim, fire!

Now that all the firework images are loaded into RAM, let's make the game launch some fireworks so we can make sure everything is working. This task can be broken down into four small ones that we'll tackle individually:

  1. We need to track the last time a firework was launched.
  2. If enough time has passed, we need to launch new fireworks.
  3. We need to move each firework that's on the screen.
  4. We also need to draw each firework that's on the screen.

We'll tackle them in order of difficulty, starting with tracking the last time a firework was launched. Add this to the collection of variables in Bang.cs, just above the Run() method:

int LastLaunchTime;

Then put this line somewhere in the Run() method:

LastLaunchTime = Timer.TicksElapsed;

That tells starts the game off with a zero counter, meaning that it will wait a few seconds before launching the first firework. Task number one solved!

Next, drawing all the fireworks on the screen. As discussed earlier, Firework objects use floats for their X and Y position, whereas SDL needs integers for drawing to the screen. So, inside the Draw() method we need to loop over each firework, force its position floats into integers, then draw them at the screen.

I also told you that to pull out the blue firework at 90 degrees we need to use this code: FireworkTypes[90][Color.Blue]. So, to pull out any given surface for a firework, we just need to use its Angle and Colour values. Put this code into your Draw() method, after drawing the background and before updating the game window:

foreach (Firework firework in Fireworks) {
	sfcGameWindow.Blit(FireworkTypes[firework.Angle][firework.Colour],
		new Point((int)firework.X, (int)firework.Y));
}

Next up, we need to move each of the active fireworks. We're also going to take the opportunity here to remove any fireworks that have moved off the screen. If you think back to the definition of the Firework class, you'll remember that each firework has an X and Y position, but also that it has X and Y speeds, so to move a firework across the screen we just need to subtract their X and Y speeds from their X and Y positions.

So, put this code into your Update() method:

for (int i = Fireworks.Count - 1; i >= 0; --i) {
	Firework firework = Fireworks[i];
	firework.X -= firework.XSpeed;
	firework.Y -= firework.YSpeed;

	if (firework.Y < -300) {
		Fireworks.RemoveAt(i);
	}
}

Note that we need to loop backwards over the array because we're potentially removing one or more fireworks and it would cause problems if we remove a firework during a loop - see project four if that all seems a bit hazy.

Why do you think I'm using -300 rather than just looking for the height of the firework? The answer is simply a game-play one: if a firework is just off the screen and the player wants to explode it, we need to give them a bit of leeway because it's likely the explosion will just about make it onto the screen.

The last task is the most complicated one: we need to launch a new firework every few seconds. In principle, this is as simple as adding code like this to the Update() method:

if (LastLaunchTime + 4000 < Timer.TicksElapsed) {
	LaunchFireworks();
}

...but that's just shifting the work to a new method! So, add that code into Update(), and let's take a look at what a LaunchFireworks() method should do.

First, it needs to reset the LastLaunchTime variable to the current time, so that the game waits another four seconds before launching more fireworks. Then it needs to decide randomly whether it should launch fireworks from the left, from the right or from the bottom. And then it needs to create five fireworks, each with a random colour.

Each direction (from the left, right, or bottom) has to create five fireworks at different positions on that side of the screen, so there are fifteen ways to create a firework. Hopefully that should be setting off alarm bells in your head: this is something that definitely calls for a method all of its own.

Let's start there: let's create a method that launches precisely one firework at a given angle, and at a specific X/Y position:

void CreateFirework(int angle, int xpos, int ypos) {
	Firework firework = new Firework();
	firework.X = xpos;
	firework.Y = ypos;

	firework.XSpeed = (float)(Math.Cos(angle * Math.PI / 180)) * 5;
	firework.YSpeed = (float)(Math.Sin(angle * Math.PI / 180)) * 5;
	
	switch (Rand.Next(0, 3)) {
		case 0:
			firework.Colour = Color.Blue;
			break;
			
		case 1:
			firework.Colour = Color.Green;
			break;
			
		case 2:
			firework.Colour = Color.Red;
			break;
	}
		
	firework.Angle = angle;
	Fireworks.Add(firework);
}

There's nothing really new in there - the two long-ish lines that call Math.Cos() and Math.Sin() have both been used and explained in project six, with the exception that I've put "* 5" on the end to make them all move a bit faster!

As each firework is created, we add it to the list of active fireworks (the Fireworks list) so that it can be moved and drawn correctly.

That CreateFirework() method launches a firework at an angle and position, so all we need to do is write the LaunchFireworks() method that calls CreateFirework() once for each firework it wants to create. Put this somewhere into Bang.cs:

void LaunchFireworks() {
	// reset the timer
	LastLaunchTime = Timer.TicksElapsed;

	// pick a random direction for the fireworks
	switch (Rand.Next(0, 3)) {
		// fire your fireworks here
	}
}

You can have as many firework launch patterns as you want, but for the sake of this simple tutorial we're only going to have three: up, from the left and from the right. Put this code where I've marked "// fire your fireworks here":

case 0:
	// fire five, straight up
	CreateFirework(90, GameHalfWidth - FireworkTypes[90][Color.Blue].Width, GameHeight);
	CreateFirework(90, GameHalfWidth - FireworkTypes[90][Color.Blue].Width - 50, GameHeight);
	CreateFirework(90, GameHalfWidth - FireworkTypes[90][Color.Blue].Width - 100, GameHeight);
	CreateFirework(90, GameHalfWidth - FireworkTypes[90][Color.Blue].Width + 50, GameHeight);
	CreateFirework(90, GameHalfWidth - FireworkTypes[90][Color.Blue].Width + 100, GameHeight);
	break;

case 1:
	// fire five, from the left to the right
	CreateFirework(135, -FireworkTypes[135][Color.Blue].Width, GameHeight - 300);
	CreateFirework(135, -FireworkTypes[135][Color.Blue].Width, GameHeight - 250);
	CreateFirework(135, -FireworkTypes[135][Color.Blue].Width, GameHeight - 200);
	CreateFirework(135, -FireworkTypes[135][Color.Blue].Width, GameHeight - 150);
	CreateFirework(135, -FireworkTypes[135][Color.Blue].Width, GameHeight - 100);
	break;

case 2:
	// fire five, from the right to the left
	CreateFirework(45, GameWidth, GameHeight - 300);
	CreateFirework(45, GameWidth, GameHeight - 250);
	CreateFirework(45, GameWidth, GameHeight - 200);
	CreateFirework(45, GameWidth, GameHeight - 150);
	CreateFirework(45, GameWidth, GameHeight - 100);
	break;

That's quite a lot of code, but it's really dull stuff - it's just repetitive calls to CreateFirework(), varying the angle, X and Y positions of the parameters. For example, when firing fireworks up from the bottom of the screen, we fire one in the middle, two off to the left and two off to the right. Firing from the left or right, we keep the X position the same, and vary the height.

There is one minor point I ought to clear up, and that's the use of Color.Blue for reading the width values. The reason this is used is because we need to offset the fireworks' positions by their width, so they appear off the screen. As all our fireworks are the same size, we can read any colour and it will be correct, so I just used the first one, Color.Blue.

With those four tasks complete, the game is starting to come together a little bit: if you build and run the code, you'll see the same background, but now fireworks will fly across the screen. Of course, you can't actually do anything with them yet, but then again we're only half way through this project!

Our game now has fireworks that fly up the screen in various directions - it's a bit dull, but at least things are starting to come together now.

Our game now has fireworks that fly up the screen in various directions - it's a bit dull, but at least things are starting to come together now.

Choosing a target

For the player to be able to score points, we need to let them a) choose which fireworks should be exploded, then b) explode them. Choosing which fireworks should be exploded is pretty straightforward: when the mouse button is clicked, or if the mouse is moved when the mouse button is held down, we need to see whether the cursor is over any fireworks. If it is, then we need to select it.

However, here's the catch that makes the game more difficult: the player can select only one colour at a time, which means if they have a green firework selected then click a red firework, we need to deselect the green firework. On the other hand, if they have a green firework selected and then click another green firework, they both become selected.

Put this method, CheckSelectFirework() into Bang.cs:

void CheckSelectFirework(Point point) {
	// loop over every active firework
	foreach (Firework firework in Fireworks) {
		if (PointOverRect(point.X, point.Y, firework.X, firework.Y,
			FireworkTypes[firework.Angle][firework.Colour].Width,
			FireworkTypes[firework.Angle][firework.Colour].Height)) {

			// a firework was selected!

			foreach (Firework firework2 in Fireworks) {
				// now loop over every other firework

				if (firework2.IsSelected && firework2.Colour != firework.Colour) {
					// deselect any other fireworks that aren't this colour
					firework2.IsSelected = false;
				}
			}

			// finally, select the new firework
			firework.IsSelected = true;
		}
	}			
}

That's all very simple in code, but it makes the game a lot harder to play!

Now to run that method whenever the mouse is moved with the mouse button down or when the mouse button is pressed, we just need to modify Events_MouseButtonDown() and Events_MouseMotion(). Both of these methods receive the mouse position as part of their parameters, so we just need to pass that onto CheckSelectFirework() so that it can check whether any fireworks were selected. Modify your code to this:

void Events_MouseButtonDown(object sender, MouseButtonEventArgs e) {
	CheckSelectFirework(e.Position);
}

void Events_MouseMotion(object sender, MouseMotionEventArgs e) {
	if (Mouse.IsButtonPressed(MouseButton.PrimaryButton)) {
		CheckSelectFirework(e.Position);
	}
}

Finally, we need to make it easier for players to see which fireworks are currently selected, because selecting a different colour will unselect previously selected fireworks!

So, go into your Draw() method and modify the Fireworks foreach loop to this:

foreach (Firework firework in Fireworks) {
	if (firework.IsSelected) {
		short box_left = (short)(firework.X - 3);
		short box_top = (short)(firework.Y - 3);
		short box_right = (short)(FireworkTypes[firework.Angle][Color.Blue].Width + firework.X + 3);
		short box_bottom = (short)(FireworkTypes[firework.Angle][Color.Blue].Height + firework.Y + 3);

		sfcGameWindow.Draw(new Box(box_left, box_top, box_right, box_bottom), Color.White);
	}
	
	sfcGameWindow.Blit(FireworkTypes[firework.Angle][firework.Colour],
		new Point((int)firework.X, (int)firework.Y));
}

With that in place, when the fireworks are drawn a box is drawn behind them if they are selected. One of the (many!) advantages of SDL is that it makes it very easy to draw shapes on the screen such as boxes, circles and lines. In this code, we define the four corners of the box: it's left and top edges are the firework's X and Y positions, and the right and bottom edges are the position plus the firework width. In both cases I've added or subtracted 3 to make the box a little bigger than the firework to make it look better.

If you run the game now, you'll be able to select fireworks by clicking on them. Having the option to hold down the mouse button and just wave the cursor over fireworks to select them makes the game a little easier, so it's good to have both.

Clicking on fireworks selects them, showing a box to make it clear they are highlighted.

Clicking on fireworks selects them, showing a box to make it clear they are highlighted.

Bang!

And now it's time for the main event. Or at least it's time to start working towards the main event: we need to make it possible for players to explode the fireworks they've selected.

First, add a new variable just under the declaration of "int LastLaunchTime" so that we can track the player's score:

int Score;

Second, create a new method that will update the window's title with the score whenever we call it:

void SetWindowCaption() {
	Video.WindowCaption = "Bang!     Score: " + Score;
}

At the start of the game, we should update the window title with an empty score, so add this at the start of the Run() method:

SetWindowCaption();

And now all we need to do is make pressing the space key detonate any selected fireworks. This takes just under 40 lines of code, so you might be forgiven for thinking this is quite hard to do. But the truth is that it's really easy:

  1. Create a variable to count the number of fireworks that were selected.
  2. Loop backwards through all the fireworks, adding one to our counter for each one that was selected, then removing it from the list of active fireworks - hence the need to loop through the list backwards.
  3. Add to the Score variable depending on how many fireworks were detonated.
  4. Call SetWindowCaption() to update the window caption with the new score.

Updating the score actually takes up almost half the code for the method, because we want to add more score for exploding multiple fireworks - ie, exploding two fireworks together should give the player more points than exploding both fireworks individually.

All this is done inside the Events_KeyboardDown() method, and should only happen when the player presses space. So, modify your method so that it looks like this:

void Events_KeyboardDown(object sender, KeyboardEventArgs e) {
	if (e.Key == Key.Space) {
		int numexploded = 0;

		for (int i = Fireworks.Count - 1; i >= 0; --i) {
			Firework firework = Fireworks[i];

			if (firework.IsSelected) {
				// destroy this firework!
				Fireworks.RemoveAt(i);

				++numexploded;
			}
		}

		// how much score should be added?
		switch (numexploded) {
			case 0:
				// nothing; rubbish!
				break;
			case 1:
				Score += 200;
				break;
			case 2:
				Score += 500;
				break;
			case 3:
				Score += 1500;
				break;
			case 4:
				Score += 2500;
				break;
			case 5:
				Score += 4000;
				break;
		}

		SetWindowCaption();
	}			
}

Go ahead and run the game now: fireworks fly up, you can select them with your mouse, then press space to destroy them. Is the game finished? Well, yes. But what it really misses is some sort of interest: making fireworks disappear is very dull, hardly worthy of a game called "Bang!" And, of course, I did promise you a particle system, right?

Special effects are go!

We're going to add colourful explosions to the game to make it look a little nicer. To do that, we need to make a couple of changes to Bang.cs:

  1. We need to set up a List array to store active explosions.
  2. Every time a firework is exploded, we need to call a method called ExplodeFirework().
  3. That ExplodeFirework() method needs to create a new explosion object, and add it to the list of active explosions.
  4. Every time Draw() is called, we need to remove old explosions, or draw ones that are still active.

All those changes add up to just 12 lines of code, at which point we can crack on with making the Explosion class. First, add this line up in the variable declaration section, just beneath the FireworkTypes dictionary:

public List<Explosion> Explosions = new List<Explosion>();

Next, we need to call a non-yet-created method, ExplodeFirework(), for each firework that is being detonated because the player pressed space. This needs to pass in the firework that is being destroyed, so add this line into your Events_KeyboardDown() method, just beneath the comment "// destroy this firework!":

ExplodeFirework(firework);

That ExplodeFirework() method isn't terribly complicated, but it's worth keeping it as a separate method in case we ever need to explode fireworks from anywhere outside the Events_KeyboardDown() method - you might add a smart bomb power up for example.

What ExplodeFirework() needs to do is create a new instance of the Explosion class, passing in the position that the explosion should be, how fast the particles should move, how long each particle should live, and also what colour should be used to draw it all. Once that's done, the new explosion should be added to the Explosions list so that we can keep track of it.

Turning all that into C#, we get this:

void ExplodeFirework(Firework firework) {
	// find the horizontal centre of the firework
	float xpos = firework.X + FireworkTypes[firework.Angle][firework.Colour].Width / 2;

	// create the explosion at that centre in the firework's colour
	Explosions.Add(new Explosion(xpos, firework.Y, 200, 2000, firework.Colour));
}

The final change we need to make in Bang.cs is to draw any active explosions, and remove any ones that are finished. Put this code just before sfcGameWindow.Update() in your Draw() method:

for (int i = Explosions.Count - 1; i >= 0; --i) {
	if (Explosions[i].Particles.Count == 0) {
		// this explosion is done, remove it
		Explosions.RemoveAt(i);
	} else {
		// it's alive, draw it
		Explosions[i].Render(sfcGameWindow);
	}
}

As with any other loop where we might remove elements, we need to loop through this one backwards. Otherwise all that code is pretty straightforward.

What's that you say? Where did Particles and Render() come from? How can we create an Explosion and pass in all those variables without a constructor? Simple: we can't. And that's why we still have one more thing to do: we need to create the Explosion class.

Fireworks night

This really is the last thing we have to do in this project, but I've saved the best - and hardest - to last.

As per usual, before we jump into the code, let's spec out how explosions should work. Each explosion needs:

  • An X/Y position on the screen.
  • A colour to use when drawing particles.
  • The speed at which to create particles flying outwards.
  • How long each particle should exist for.
  • A list of all the particles that it owns.

Going a step further, we need to make a class for the particles as well, because each particle needs to store some information:

  • Its X/Y position.
  • Its X/Y speed.
  • When the particle was created.

So, the first step to creating our particle system is to create the basic classes for the explosion and its particles - we'll call the classes Explosion and ExplosionDot. Go into the Explosion.cs file and put these two classses in there:

public class ExplosionDot {
	public float X;
	public float Y;

	public float XSpeed;
	public float YSpeed;
	public int TimeCreated;
}

public class Explosion {
	float X;
	float Y;

	Random Rand = new Random();
	Color Colour;
	int Speed;
	int Lifespan;

	List<ExplosionDot> ExplosionDots = new List<ExplosionDot>();
}

The only real code we need inside the Explosion class is a constructor and a Render() method, the first of which is very straightforward: we need to copy all the parameters into the explosion for later reference, then create 100 particles. Here it is:

public Explosion(float x, float y, int speed, int lifespan, Color colour) {
	// copy all the variables into the Explosion object
	X = x;
	Y = y;
	Colour = colour;
	Speed = speed;
	Lifespan = lifespan;

	// now create 100 particles
	for (int i = 0; i < 100; ++i) {
		ExplosionDot dot = new ExplosionDot();
		dot.X = X;
		dot.Y = Y;
		// one of the parameters passed in is the speed to make particles move
		// we use that as a range from -speed to +speed, making each particle
		// move at a different speed.

		dot.XSpeed = Rand.Next(-Speed, Speed);
		dot.YSpeed = Rand.Next(-Speed, Speed);
		dot.TimeCreated = Timer.TicksElapsed;
		Particles.Add(dot);
	}
}

And that just leaves the Render() method for explosions. To make the code a bit easier to read in Bang.cs, I've made this Render() method both update the particle positions and draw the particles - it's not ideal because it means we can't draw the explosions without making them move, which rules out things like a pause option, but it's OK for now and something you can tackle for homework.

The best way to explain this code is to scatter comments through it, so here goes:

public void Render(Surface sfc) {
	// loop backwards through the list of particles because we might remove some
	for (int i = Particles.Count - 1; i >= 0; --i) {
		ExplosionDot dot = Particles[i];

		// update the particle's position
		dot.X += dot.XSpeed / Events.Fps;
		dot.Y += dot.YSpeed / Events.Fps;

		// if this dot has outlived its lifespan, remove it
		if (dot.TimeCreated + Lifespan < Timer.TicksElapsed) {
			Particles.RemoveAt(i);
			continue;
		}

		// figure out how much time has passed since this particle was created
		int timepassed = Timer.TicksElapsed - dot.TimeCreated;

		// ... then use it to calculate the alpha value for this particle
		int alpha = (int)Math.Round(255 - (255 * ((float)timepassed / Lifespan)));

		// if the particle is basically invisible, don't draw it
		if (alpha < 1) continue;

		// otherwise, create a colour for it based on the original colour
		// and the alpha for this particle
		Color thiscol = Color.FromArgb(alpha, Colour.R, Colour.G, Colour.B);

		// now draw the particle
		short left = (short)Math.Round(dot.X);
		short top = (short)Math.Round(dot.Y);
		short right = (short)(Math.Round(dot.X) + 2);
		short bottom = (short)(Math.Round(dot.Y) + 2);
		sfc.Draw(new Box(left, top, right, bottom), thiscol);
	}
}

There are two points of interest in that code: the code to create an alpha value for the particle, then the call to Color.FromArgb() to turn that alpha value into a colour that can be drawn. If particles have a lifespan, as they do in this example, then the best way to draw them is usually to fade them out slowly so that they are 100% opaque when created, and 0% opaque when they are about to be destroyed due to old age.

In SDL, alpha values are expressed as values between 0 (wholly transparent) and 255 (wholly opaque), so what we need to do is what value a given particle should have by comparing its creation time against the current time and its lifespan. For the purpose of a really simple example, let's say a particle was created at time 0, has a lifespan of 200, and the current time is 100, so the particle is exactly half way through its life.

What the code does is to figure out the amount of time that has passed by subtracting the current time from the created time. In our example, the current time is 100, so we subtract from that the created time, which is 0, giving 100 - 100 milliseconds have passed since the particle was created.

Next it takes that value and divides it by the lifespan of particles, which is 200, giving 0.5. It then takes that value and multiplies it by 255, giving 127.5. Finally, it subtracts that from 255, giving, again, 127.5. Finally, that number gets rounded to an integer, giving 128, so the final alpha value for this pixel is 128.

If you're wondering the we need to subtract the value from 255, it will become clear if you take different input values. For example, if the particle was created at time 0, the current time was 100, but the lifespan of particles was 110, you get this: 100 - 0 = 100; 100 / 110 = 0.909090909; 0.909090909 * 255 = 231.818181818.

As you can see, the particle has gotten closer to its end of life, its alpha value has gone up rather than down - it's going from transparent to opaque! We want the exact opposite of that, so we just take the finished value and subtract it from 255 so that over time the particles get more transparent until eventually becoming invisible.

Once the alpha value is calculated, we can create a new Mono Color object from it by using the Color.FromArgb() method. This takes colour values in the order Alpha, Red, Green, Blue, so we just specify the new alpha value as the first parameter then use the colour value that was specified when the explosion was created for the other parameters.

And with that the game is finished: if you run it now you'll see pretty explosions when fireworks are detonated, and it only looks nicer when you destroy multiple at once!

This is the effect our particle system gives us: a hundred or so little particles flying outwards from the centre, fading away as they get older.

This is the effect our particle system gives us: a hundred or so little particles flying outwards from the centre, fading away as they get older.

Let's wrap up

This has been the biggest project to date, and I think the finished product is something you can be really proud of - it's a fun game, it's easy to play, and you've learned a lot of neat stuff along the way! Up until this project, all the little classes we've defined as part of our projects have basically been glorified data stores - they haven't had any intelligence of their own. But in this project, the Explosion class has two methods of its own: the functionality required to make it work is encapsulated inside the class, so you can copy it around to other projects fairly easily.

You've also learned how to use the Dictionary data type for when a simple List isn't enough, how to draw boxes using SDL, and, most impressive of all, how a simple particle system works. And actually it's in particle systems that you can have the most fun extending this project - it's easy to add gravity (just modify each particle's YSpeed value each update) or wind, to make particle systems that keep firing new particles rather than expelling them all at once, and so on. Play around and see what you can achieve!

It's been a lot of work, but the payoff is another big batch of learning, plus another finished project. Well done!

Homework

If you're following this with a tutor, you will be required to complete the following homework before continuing. If you're working by yourself, I strongly recommend you find someone who can help check your work and provide feedback.

The homework for this project is made up of four coding problems; the first three are required, and the last one is for coders who want a bit more of a challenge.

  • Split Render() method of the Explosion class into two methods: one called Update() that actually does update the explosion (this should be called from inside the Update() method of the main game), and the rest should stay in Render() and do the drawing of the particles.
  • Right now, we're storing the time created and calculating the alpha value and colour for each particle, which is a nice feature for more advanced particle systems but not needed here because all particles in an explosion are created at the same time and thus share the same alpha value. Make the time created shared for all particles, then calculate the alpha value only once per update and share it across all the particles.
  • When particles are created, their XSpeed and YSpeed is set by creating a random number between -Speed and +Speed, but if you look carefully that actually creates square explosions because particles can potentially move at -Speed in the X axis and -Speed in the Y axis, which is more than they would be able to move if travelling diagonally. Fix this creating each particle in a random direction, then calculating the X and Y speed from that direction and multiplying the result by a random value between 0 and Speed.
  • Optional, but recommended: create a new type of firework that's white. Usually, if a player selects a red firework then a blue firework, the red firework is deselected. Your job is to make this white firework into a linking firework: if a player selects a white firework, it doesn't deselect the previous firework no matter what colour it is, and the player can then freely choose the next firework, regardless of its colour. For example, red, red, white, blue, blue is a valid combination.

If you're having trouble trying to figure out the third problem, you should look at the Math.Sin() and Math.Cos() code for an example of how to do it.

If you have problems, try to solve them yourself - you might not succeed, but you'll learn a lot by trying! If you're still having problems, drop your tutor an email and ask for help.

The small print

This work was produced for TuxRadar.com as part of the Hudzilla Coding Academy series. All source code for this project is licensed under the GNU General Public License v3 or later. All other rights are reserved.

You should follow us on Identi.ca or Twitter


Your comments

Oh! Dear!

While this article provides a useful insight into the capabilities of the Sdl package the coding is awful! The author really needs to go on an object oriented design course.

The key dictionary of dictionaries data structure is both contorted and completely unnecessary. Proper definitions for the firework and explosion classes, which encapsulate details of their display and update operations, would simplify the code out of all recognition and improve efficiency by removing all the dictionary look-ups. An instance of a firework would be bound to the image of the sprite (Surface) which represents it on screen. It would not need to look it up.

Ugh!

@MSP

I think you're absolutely right in saying the code could be improved in many ways, but I also think maybe you're misunderstanding part of the point of this course. Put simply, I do want to explain to people how encapsulation works and why it's important, but I'm taking an iterative approach to programming: show people how to get started by actually making something easy, then, later, improve on the design by explaining new features/theory. You'll notice that I'm declaring everything as "public" right now and basically using classes as dumb data structures - that's by design, not accident.

If you want to teach people how to make "proper definitions" of classes to "improve efficiency", fine, please go ahead and write tutorials yourself - the more free tutorials we have, the better. But I hope you can understand that my goal here is different. As I said in the introduction to this course, everything is on a need to know basis, and, while your code might run 1% faster by removing the dictionary look ups, I don't think that comes under "need to know".

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

CAPTCHA
We can't accept links (unless you obfuscate them). You also need to negotiate the following CAPTCHA...

Username:   Password:
Create Account | About TuxRadar