Breakout Game From Scratch

From Allegro Wiki

Jump to: navigation, search

This tutorial is a detailed view of all the source code of a game done from scratch, with C++ and Allegro, with gameplay very similar to classics such as Arkanoid, Gigas, or Breakout.

Original written by Kronoman - "In loving memory of my father"

Contents

Tasks

<tasks> [1] Make sure the upload of the zip file with the project and the source code is OK. [2] Read the whole article and make sure everything was translated OK from the original tutorial. [x] Add the png screenshoots [3] Break the text into smaller < 30 kb sections. [3] Maybe fix the redaction. </tasks> TODO - remove this task list when everything is done.

Introduction

This tutorial is aimed at showing how to develop a simple game using the Allegro Game Libray, but is not aimed at teaching how to code from scratch.

I assume that you can code reasonably well in C++, and you have knowledge about object oriented programming, and computer use in general.

The whole source code is in a zip file that comes with this tutorial. Here I will only show and discuss fragments of code that are of academic interest to the educative goal.

Download

Here you can download the whole source code for the project.

Or here, a direct link to the file

Goal

In this tutorial, I'm going to make a simple game, that I like to call Kronoide. To make the game, I use the C++ programming language, together with Allegro as the game libray.

Gameplay

This game is going to take the basic gameplay from Arkanoid, an arcade game developed by Taito in 1986. It is a simplified clone of Sega's earlier arcade game Gigas and Gigas Mark 2 which were in turn based upon Atari's Breakout games of the 1970s.

The player controls a small pad, which prevents a ball from falling from the playing field, attempting to bounce it against a number of bricks. The ball striking a brick causes the brick to disappear. When all the bricks are gone, the player goes to the next level, where another pattern of bricks appear.

Design

The game has a brick zone in the upper part of the screen, and the player moves a paddle in the bottom, from left to right, using the mouse.

Kronoide

Project structure

Well, I'm going to start to write the source code, so I need to set up a space on my computer to store all the files that I will produce. I choose to use this directory tree, and I created it on my hard drive in a special place that I have reserved for my development projects (i.e "C:\My programs\" or "~/my_programs/" )

+- kronoide.1.0.0
+--- bin
+--- include
+--- obj
+--- src

I took this directory tree from my project, AlMake, that is a generic Allegro build system and template project tree.

AlMake is a group of scripts and files that helps the developer and end-user to configure, update, and build Allegro and AllegroGL projects, on many platforms, and with different compilers.

You may want to use it too, because it has a simple makefile to help you compile the source code.

Now, I will let you know what I put on each directory.

In kronoide.1.0.0 (the root), I have the makefile that came with AlMake, as well as the README, INSTALL, LICENSE, etc files.

If you plan to distribute your game, you should write at least a README file.

I encourage you to open the makefile with a text editor, and fine tune it until you are satisfied. Or better yet, write your own makefile, just to learn how to do it. Just remember to don't convert tabs to spaces on a makefile, I know why I say it.

In bin, will go the binary file (executable file), and the data files produced after compiling all sprites+sound+etc using the grabber, or, if you want to say it other way, the binary files needed to play the game.

In include, I will put all the header *.h files that I will write for this project.

In obj goes all the intermediate object files that the compiler makes while compiling the source code to a binary form.

In src, I will put all the C++ code, *.cpp files that I will write for this project.

The game loop

Almost any game needs a animation framework, this is the core that allows the game to have good game play across various OSes and hardware platforms.

The animation is managed by a loop which ensures that it progresses at a consistent rate, as independent of the hardware and OS as possible.

The rate is measured in terms of frames per second (FPS), where a frame corresponds to a single rendering of the application (game) to the screen.

My animation framework will implement double buffering drawing, a technique were the operations required for rendering are not applied directly to the screen, but to a secondary image.

When the secondary image is ready, it is displayed on screen. In that way, we don't see flicker or other artifacts.

The game loop will consist of

  1. Update logic of the game.
  2. Update graphics of the game.
  3. Render the graphics to screen.


Also, the game itself is a simple state machine, that means that the game can be only in a unique certain state in a given time (i.e playing, dying, game over, etc)

So well, the game main animation framework will live in kernel.cpp and kernel.h

main.cpp

In main.cpp, there is the program entry point, a start point from were the program starts running.

There, it parses the command line parameters (if any), and then start the hardware (graphics mode, sound, etc), and then start the game main loop.

The code in main is a little simple, and boring, but is really needed in order to get into a proper graphics mode and run gracefully.

kernel.h

Inside kernel.h, lies the Kernel class declaration, that goes like this (please refer to kernel.h, in the project zip file, for the whole text)


class Kernel 
{
	public:
		// Operations
		Kernel(); // constructor
		~Kernel(); // destructor
 
		void init(); // inits the game
		void play(); // plays the main game loop
		void shutdown(); // ends the game
 
		// Attributes
  		BITMAP *double_buffer; // the double buffer bitmap
		Paddle paddle; // the player paddle 
		Ball ball; // the game ball
		GameField game_field; // the game field
 
	private:
		void init_game(); // this inits the game without resetting the kernel or the level, is used for when you start a new level, lose a life, etc
		int update(); // updates game logic
		void render(); // updates graphics of the game
};


I have all the attributes public for ease of use (for us), I know that they break the encapsulation, but I aren't doing enterprise software or something, so I can take some "creative licenses" here.

The public operations of Kernel are used in the main() function (check main.cpp). After main() initializes Allegro, and the needed hardware, I just create a new game Kernel, and run it. The source code in main() (main.cpp) related to the kernel is:


	// run game
	Kernel *kernel;
	kernel = new(Kernel);
	kernel->init();
	kernel->play();
	kernel->shutdown();
	delete(kernel);


kernel.cpp

In kernel.cpp I implement all the functionality of the Kernel.

I will discuss first the public operations, then the private ones.

Kernel::init() is called first, and does the initialization of the game, currently it involves creating the double buffer bitmap.


void Kernel::init()
{	
	double_buffer = create_bitmap(SCREEN_W,SCREEN_H);
	if (!double_buffer)
		raise_error("Kernel::init() - can't create double buffer!");
	clear_bitmap(double_buffer);
}


The Kernel::shutdown() is called when I exit the game, so I leave everything nice and cleaned. I currently release the double buffer bitmap.


void Kernel::shutdown()
{
	if (double_buffer)
		destroy_bitmap(double_buffer);
	double_buffer = NULL;
}


The Kernel::play() is called to actually start playing the game itself.

It contains the main loop, together with some game logic to respond to changing logic states.

Currently, this version is a little messed up, and needs cleaning, was written in a hurry, and is really bare bones. Should be easy to understand what is doing, and works really well.

Note As you may notice below, I included a cheat code to test the levels. You should remove it later.


void Kernel::play()
{
	int ret = KERNEL_UDP_OK; // current game state, all OK
	int level = 1 ; // current level of the game
 
	_start_global_timer(); // we start a global timer, check mtimer.cpp
 
	time_counter = speed_counter = fps_counter = fps_real = 0; // we reset the global timer parameters
 
	clear_keybuf();
 
	init_game(); // initialize a game run
 
	game_field.do_new_random_level(3,2,1); // create a level
 
	show_mouse(NULL); // hide mouse
 
	// This is the main loop of the game, were all the action actually happens
	while (ret != KERNEL_UDP_EXIT && ret != KERNEL_UDP_QUIT) // DEBUG , ESC key should be enabled ??? maybe... maybe not
	{
		if (speed_counter > 0) // while we still have logic frames to do
		{
			ret = update(); // update game logic and see which state we are
 
			if (ret == KERNEL_UDP_LOST_LIFE) // lost a life!
			{
				init_game(); // DEBUG - we should show a message and take down a life
			}
 
			if (ret == KERNEL_UDP_NEXT_LEVEL || key[KEY_SPACE]) // DEBUG -- REMOVE THE CHEAT, the KEY_SPACE thing!!
			{
				// next level
				// DEBUG -- WE MUST SHOW A MESSAGE
 
				level++; // next level :)
 
				// pick new level parameters
				int l = level;
				int w = 3 + rand()%level + level;
				int h = 3 + rand()%level ;
				if (level > 5)
					l = 5; // max life of bricks
				if (w > GAMEFIELD_MAX_W-2)
					w = GAMEFIELD_MAX_W-2; // oh crap, the guy can play :o
				if (h > GAMEFIELD_MAX_H-2)
					h = GAMEFIELD_MAX_H-2; // oh crap, the guy can play :o
 
				game_field.do_new_random_level(w,h,l); // new level
				init_game();
			}
 
			speed_counter--; // decrease logic frames to do
		}
		else
		{
			render(); // draw the game
			fps_counter++; // count frames per second (FPS) (check mtimer.cpp, this aren't the real FPS, fps_real has the real FPS
		}
	}
 
	clear_keybuf();	
	_stop_global_timer(); // stop the global timer
	time_counter = speed_counter = fps_counter = fps_real = 0; // leave everything nice, just to be sure
}


From here, I discuss the private stuff of the Kernel class, this is the inner working of the class.

The Kernel::update() does the logic update of the game, at a constant timed rate (it is called from Kernel::play() loop), it updates everything that is needed, and returns the current state of the game in a integer code, so the loop in Kernel::play() knows what to do in the next loop iteration.

int Kernel::update()
{
	int ret = KERNEL_UDP_OK; // by default, everything is OK
 
	paddle.update(); // update the paddle logic
 
	if (ball.update(paddle, game_field))
		ret = KERNEL_UDP_LOST_LIFE; // player lost life :(
 
	if (game_field.update())
		ret = KERNEL_UDP_NEXT_LEVEL; // gamefield clear, go to next level \\o o//
 
 
	if (key[KEY_ESC]) // exit game -- DEBUG , WE MUST CONFIRM THIS!!
		ret = KERNEL_UDP_EXIT;
 
	return ret;
}

Now, I move to the graphics system, the Kernel::render(), that does all the needed drawing of the game to show it to screen.

void Kernel::render()
{	
	clear_bitmap(double_buffer); // debug - here we should/could blit the background
 
	// first, everything gets drawed to the double buffer bitmap
	game_field.render(double_buffer); // render the game field (the bricks)
	paddle.render(double_buffer); // render the paddle
	ball.render(double_buffer); // render the ball
 
	// after that, we blit (draw) the double buffer to screen
	blit(double_buffer, screen, 0,0,0,0,SCREEN_W, SCREEN_H);
 
}

Finally, I have this useful function, Kernel::init_game(), his purpose is to reset the level, without resetting the game main state. What it does is basically re-position the paddle, the ball, and the mouse, and is called when I lose a ball for example. (check Kernel::play() to see it working)

// Kernel::init_game()
// this inits the game without resetting the kernel or the level
// is used for when you start a game , lose a life, etc
void Kernel::init_game()
{
	paddle.init(); // init the paddle
 
	ball.init(); // init the ball 
 
	ball.sticky_time = BPS_OF_TIMER_MANAGER * 3; // 3 secs before launch of ball =)
 
	position_mouse((int)paddle.x , (int)paddle.y ); // put the mouse cursor in paddle
 
	clear_keybuf();	 // clear keyboard buffer
}


The gamefield

Kronoide

As is shown in the screen shoot, the gamefield is composed of bricks, that are destroyed by hits of the ball.

gamefield.h

Here I can see the structure of the Gamefield class.

class GameField
{
	public:
		// Operations
		GameField();
		~GameField();
 
		void do_new_random_level(int w, int h, int max_life); // creates a new level size w*h, with 1..max life for bricks
 
		bool update();
		void render(BITMAP *bmp);
 
		bool ball_hit_brick(int x_px , int y_px); // this is used by the ball to hit the bricks, returns true if a brick was hit
 
		// Attributes
 
		Brick bricks[GAMEFIELD_MAX_W][GAMEFIELD_MAX_H];
		int w, h; // current width and height in bricks, must be < GAMEFIELD_MAX_*
		int bc; // brick count, when bc==0, next level
};

I took a really easy approach (that wastes some bytes of memory), so the gamefield is composed of a 2-dimensional array of bricks, that can have a maximum size. Since the game is simple, all the levels are random generated. Also, I have a helper function that lets us do the ball hits a brick check and update.

gamefield.cpp

The implementation is really simple, and I will describe the important bits below.


This GameField::do_new_random_level(int w, int h, int max_life) creates a new level.

It takes the width and height of the level, and the maximum life that each brick can have (how many hits it can take before it breaks and disappears).

The algorithm is very simple, it just fills the matrix with random coloured bricks, and each brick has a size proportional so the gamefield takes all the screen width, and one third of the screen height.

void GameField::do_new_random_level(int w, int h, int max_life)
{
	if (w < 1 || w > GAMEFIELD_MAX_W) 
		w = GAMEFIELD_MAX_W; // safe check
	if (h < 1 || h > GAMEFIELD_MAX_H) 
		h = GAMEFIELD_MAX_H; // safe check
 
	this->w = w;
	this->h = h;
	bc = 0;
 
	// fill the matrix with random level
	for (int y = 0 ; y < h; y++)
	{
		for (int x = 0 ; x < w; x++)
		{
			bricks[x][y].life = rand() % max_life + 1;
 
			bricks[x][y].x = x * SCREEN_W / w;
			bricks[x][y].y = y * SCREEN_H / 3 / h;
 
			bricks[x][y].w = SCREEN_W / w;
			bricks[x][y].h = SCREEN_H / 3 / h;
 
			// pick a random color
			bricks[x][y].c.r = rand_ex_i(128, 255);
			bricks[x][y].c.g = rand_ex_i(128, 255);
			bricks[x][y].c.b = rand_ex_i(128, 255);
 
			bc ++; // count bricks (lame)
		}
	}
}

The logic update() currently does nothing more that just return true if I don't have more bricks left (I won the level), or false if I still have bricks left.

// returns true if we finished the level (0 bricks left)
bool GameField::update()
{
	return (bool)(bc < 1);
}

Here, GameField::render(BITMAP *bmp) renders the game field (the bricks)

void GameField::render(BITMAP *bmp)
{
	for (int y = 0 ; y < h; y++)
		for (int x = 0 ; x < w; x++)
			bricks[x][y].render(bmp);
}


GameField::ball_hit_brick() checks the hit of the ball in a given x_px, y_px position (in pixels). The ball coordinates in pixels are translated to brick coordinates, and then a check is performed against the gamefield matrix. If the ball hit a brick, the life of the brick is decreased, and if it reaches zero, I count a brick less (so I can know when to pass the level). Will return false if I didn't hit anything, or true otherwise.

bool GameField::ball_hit_brick(int x_px , int y_px)
{
	// this is used by the ball to hit the bricks, returns true if a brick was hit
	int w_b = SCREEN_W / w;
	int h_b = SCREEN_H / 3 / h;
 
	if (y_px < 0 ||
	        x_px < 0 ||
	        y_px / h_b > h ||
	        x_px / w_b > w)
		return false; // ball too low or out of bounds
 
	if (bricks[x_px / w_b][y_px / h_b].life > 0)
	{
		bricks[x_px / w_b][y_px / h_b].life--;
 
		if (bricks[x_px / w_b][y_px / h_b].life <= 0)
			bc--; // wasted brick!
 
		return true; // hit!
	}
 
	return false;
}

The bricks

As I already said, the gamefield is composed of bricks.

brick.h

Each brick has a life (hits remaining before breaking), a position (x,y), width and height (w,h), and a RGB color (c). The class declaration looks like this:

class Brick
{
	public:
		// Operations
		Brick();
		~Brick();
 
		void update();
		void render(BITMAP * bmp);
 
		// Attributes
		int life; // life, in ball hits
 
		int x; // position, in pixels
		int y;
 
		int w; // size, in pixels
		int h;
 
		RGB c; // color, in R,G,B
};


brick.cpp

The brick implementation is really simple, there is only one interesting function here.

Each brick is a rectangle that is draw in Brick::render(BITMAP *bmp)

void Brick::render(BITMAP *bmp)
{
	if (life < 1)
		return; // we don't draw broken bricks
	if (w > 12 && h > 12) // check if the brick is big enough to shadow it
	{
		// render with shadow
		rectfill(bmp, x, y, x + w, y + h, makecol((int)(c.r*0.8), (int)(c.g*0.8), (int)(c.b*0.8)));
		rectfill(bmp, x+3, y+3, x + w-3, y + h-3, makecol((int)(c.r*0.9), (int)(c.g*0.9), (int)(c.b*0.9)));
		rectfill(bmp, x+6, y+6, x + w-6, y + h-6, makecol(c.r, c.g, c.b));
	}
	else
	{
		// render normal
		rectfill(bmp, x, y, x + w, y + h, makecol(c.r, c.g, c.b));
	}
}

The paddle

The paddle is how the player prevents the ball for going outside the screen. He moves the paddle using the mouse.

paddle.h

The paddle is composed of a position (x,y), a width, height (w,h), and a RGB color (c).

class Paddle
{
	public:
		// Operations
		Paddle();
		~Paddle();
		void init();
		void update();
		void render(BITMAP * bmp);
 
		// Attributes
		float x;
		float y;
		int w;
		int h;
		RGB c;
};

paddle.cpp

The paddle has little stuff to do, it just moves from left to right, and bounces the ball.

Paddle::init() just sets the paddle in the starting position. I encourage you to play with this values.

void Paddle::init()
{
	// lame default values... DEBUG - this should be better done
	w = (int)(SCREEN_W * 0.20);
	h = (int)(SCREEN_H * 0.05);
 
	x = SCREEN_W / 2;
	y = SCREEN_H - h - 10;
 
	// paddle color
	c.r = 0;
	c.g = 128;
	c.b = 200;
 
}


This Paddle::update() does the control of paddle, using the mouse position. There is not a lot to explain. Also, keeps a eye to make sure the paddle stays inside the screen.

void Paddle::update()
{
	// mouse control... little lame
 
	x = mouse_x - (w / 2);
	//y = mouse_y - (h / 2); // I disabled the Y movement for now -- DEBUG 
 
	if (y < SCREEN_H - (SCREEN_H / 3))
		y = SCREEN_H - (SCREEN_H / 3);
 
	if (y > SCREEN_H - h)
		y = SCREEN_H - h;
 
	if (x < 0)
		x = 0;
 
	if (x > SCREEN_W - w)
		x = SCREEN_W - w;
}


The Paddle::render() draws the paddle, which is just a shadowed rectangle. You could, as a exercise, replace this drawing code, for example with a sprite, or a gradient.

void Paddle::render( BITMAP * bmp )
{
 
	rectfill(bmp, (int)x, (int)y, (int)x + w, (int)y + h, makecol((int)(c.r*0.6), (int)(c.g*0.6), (int)(c.b*0.6)));
	rectfill(bmp, (int)x+2, (int)y+2, (int)x + w-2, (int)y + h-2, makecol((int)(c.r*0.7), (int)(c.g*0.7), (int)(c.b*0.7)));
	rectfill(bmp, (int)x+4, (int)y+4, (int)x + w-4, (int)y + h-4, makecol((int)(c.r*0.8), (int)(c.g*0.8), (int)(c.b*0.8)));
	rectfill(bmp, (int)x+6, (int)y+6, (int)x + w-6, (int)y + h-6, makecol((int)(c.r*0.9), (int)(c.g*0.9), (int)(c.b*0.9)));
 
	rectfill(bmp, (int)x + 8, (int)y + 8, (int)x + w - 8, (int)y + h - 8, makecol(c.r, c.g, c.b));
}

The ball

The ball is a very important piece of the gameplay. With the ball, the player removes the bricks in order to move fordward in the levels.

ball.h

The ball is composed of a position (x,y), a direction (dx,dy), a radius (r), and a color (c).

Also, it has a time of the ball to be "sticky" (glued) to the paddle. After this time expires, the ball is released.

class Ball
{	
	public:
		// Operations
		Ball();
		~Ball();
		void init();
		bool update(Paddle &paddle, GameField &game_field);
		void render(BITMAP * bmp);
 
		// Attributes
		// position
		float x;
		float y;
 
		// direction (speed in x,y)
		float dx;
		float dy;
 
		// radius
		int r;
 
		// color
		RGB c;
 
		// time of the ball to be "sticky" to paddle... after this time ,the ball releases... given in tick counts
		int sticky_time; 			
	private:
		void bounce_x();
		void bounce_y();
};


ball.cpp

The implementation of the ball don't has secrets, is very simple. I will explain only the important stuff.


Ball::update(Paddle &paddle, GameField &game_field) updates the ball. Returns true if the ball was lost (I lose a life).

This is possible the most complicated part of the entire game.

I receive the player's paddle, and the gamefield, so I can check against them.

 
bool Ball::update(Paddle &paddle, GameField &game_field)
{
	static float paddle_last_y = 0; // we use this so the ball can't go trough the paddle, we interpolate
 
	if (sticky_time > 0)
	{
 
		sticky_time--;
		x = paddle.x + paddle.w / 2;
		y = paddle.y - r;
 
		if (mouse_b & 1) // mouse button is pressed, release the ball
			sticky_time = 0;
 
		if (sticky_time <= 0) // time is up?
		{
			// do the up release
			dy = -rand_ex_f(BALL_MIN_SPD ,BALL_MAX_SPD );
			dx = rand_sign_switch(rand_ex_f(BALL_MIN_SPD,BALL_MAX_SPD ));
		}
		return false; // we are stick to the paddle. all OK
	}
	else
		sticky_time = 0;
 
	// upgrade ball position with speed x,y
	x += dx;
	y += dy;
 
	// bounce on field bounds
	if (x < r)
	{
		x = r;
		bounce_x();
	}
 
	if (x > SCREEN_W - r)
	{
		x = SCREEN_W - r;
		bounce_x();
	}
 
	if (y < r)
	{
		y = r;
		bounce_y();
	}
 
	if (y > SCREEN_H - r)  // here we DIE ...
	{
		return true; // oops >;^) shit happens
		/*
		y = SCREEN_H - r;
		bounce_y();
		*/
	}
 
	// bounce on paddle, only if going down =P
	if (x + r > paddle.x && 
		x - r < paddle.x + paddle.w && 
		(y + r > paddle.y  || y + r > paddle_last_y  ) && 
		(y - r < paddle.y + paddle.h || y - r < paddle_last_y + paddle.h))
	{
			y = paddle.y - r; // this is lame safe check
 
			bounce_y();
	}
 
	// bounce and hit on bricks
	if (game_field.ball_hit_brick((int)x-r,(int)y) || game_field.ball_hit_brick((int)x+r,(int)y))
		bounce_x();	
 
	if (game_field.ball_hit_brick((int)x,(int)y-r) || game_field.ball_hit_brick((int)x,(int)y+r))
		bounce_y();	
 
 
	// never go faster than paddle size, or we are screwed :P
	if (dy > paddle.h*0.25) 
		dy = paddle.h*0.25;
 
	if (dy < -paddle.h*0.25) 
		dy = -paddle.h*0.25;
 
	if (dx > paddle.w*0.25) 
		dx = paddle.w*0.25;
 
	if (dx < -paddle.w*0.25) 
		dx = -paddle.w*0.25;
 
 
	paddle_last_y = paddle.y; // save our last position
 
	return false; // all OK for now
}

The ball has to bounce around, since I'm doing a simple tutorial, I really simplified the physics of the ball, so the bounce is a little weird, but works OK. This two functions, Ball::bounce_x() and Ball::bounce_y() are helpers for the bouncing of the ball, and give the ball a random spin in the bounce direction. I use random number generation functions defined in krandom.cpp and krandom.h, so I can easily get numbers inside a range.

void Ball::bounce_x()
{
	dx = -rand_ex_f(dx * 0.9, dx * 1.1);
 
	if (rand() % 100 < 10)
		dx = rand_sign_switch(rand_ex_f(BALL_MIN_SPD , BALL_MAX_SPD ));
}
 
 
void Ball::bounce_y()
{
	dy = -rand_ex_f(dy * 0.9, dy*1.1);
}


Ball::render() just renders the ball, is a shadowed ball.

void Ball::render(BITMAP * bmp)
{
	circlefill(bmp, (int)x, (int)y, r, makecol((int)(c.r*0.6), (int)(c.g*0.6), (int)(c.b*0.6)));
	circlefill(bmp, (int)x, (int)y, r-2, makecol((int)(c.r*0.8), (int)(c.g*0.8), (int)(c.b*0.8)));
	circlefill(bmp, (int)x, (int)y, r-4, makecol(c.r, c.g, c.b));
}

Miscellaneous source files

I have some files in the project that aren't really related to the gameplay, but are really needed in order to get the game working.

gerror.cpp, gerror.h

This files serve the purpose of gracefully reporting errors to the player. The only function is raise_error(), which works like printf(), but it will show a error message in a way appropriate to the platform, and then will finish the game and return to the operating system.

/// Goes back to text mode, shows the message and ends the program. Printf style.
void raise_error(AL_CONST char *msg, ...);

I use it to report critical errors, like the lack of memory, or a proper graphics video mode.

mtimer.cpp, mtimer.h

Here I have a global timer system to be used in the game. The timer system allows the game to run at the same speed on different hardware (so the game don't runs too fast on new machines or too slow on old ones). Thanks to this timer, I can measure how fast the game is going, and keep the logic updates at a constant frame rate. The drawed frames per second (FPS) are stored in the global variable fps_real The functions here are:

void _start_global_timer(); ///< starts global timer
void _stop_global_timer();  ///< stops global timer

krandom.cpp, krandom.h

The functions here are:

int rand_ex_i(int min, int max); // integer version 
float rand_ex_f(float min, float max); // floating point, with 3 decimals x.xxx
 
// version for switching the sign (positive, negative)
int rand_sign_switch(int number);
float rand_sign_switch(float number);

With this functions, I can easily choose random numbers in a given range, integers or floats.

Makefile

To build the project, I used a makefile. That way, in the command line (for example, bash), you just type "make" (without the ") and the thing compiles (if the development environment is set up fine).

In computer programming, make is a utility for automatically building large applications. Files specifying instructions for make are called Makefiles. make is most commonly used in C/C++ projects, but in principle it can be used with almost any compiled language.

The makefile that I used is AlMake, a makefile made specially for Allegro projects, that you can use too, and can be downloaded here. AlMake is a group of scripts and files that helps the developer and end-user to configure, update, and build Allegro and AllegroGL projects, on many platforms, and with different compilers. The main feature is that is multiplatform, and auto detects most common configurations, thus is so easy as just running 'make' on project dir.

Example of a compilation of Kronoide in GNU/Linux Ubuntu

kronoman@motorhead:~/pixelate/kronoide_tutorial/kronoide.1.0.0$ make
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/ball.cpp -o obj/linux/ball.o
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/brick.cpp -o obj/linux/brick.o
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/gamefield.cpp -o obj/linux/gamefield.o
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/gerror.cpp -o obj/linux/gerror.o
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/kernel.cpp -o obj/linux/kernel.o
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/krandom.cpp -o obj/linux/krandom.o
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/main.cpp -o obj/linux/main.o
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/mtimer.cpp -o obj/linux/mtimer.o
g++ -Iinclude -Wall -O3 -fomit-frame-pointer -c src/paddle.cpp -o obj/linux/paddle.o
g++ obj/linux/ball.o obj/linux/brick.o obj/linux/gamefield.o obj/linux/gerror.o obj/linux/kernel.o obj/linux/krandom.o obj/linux/main.o obj/linux/mtimer.o obj/linux/paddle.o -o bin/kronoide -s `allegro-config --libs`
The bin/kronoide is ready!
----------------------------------------------------------------------------
Finished Kronoide
Product is at bin/kronoide.
----------------------------------------------------------------------------
Makefile script done with AlMake - Generic Allegro build system
Get updates of AlMake at official website: http://almake.sf.net/
----------------------------------------------------------------------------

Features to add to the game

This game is very simple, and can be vastly improved. It would be a great exercise to add features to it, for example

  • Score system, level count and messages on screen.
  • Life count and game over.
  • Introduction screen, about screen, etc...
  • Particle system (so the bricks explode!)
  • Power ups (multiball? should not be that hard to implement)
  • A nice background or background set instead of plain black. (Maybe fractals?)
  • Sound and/or music.
  • Keyboard and joystick support to move the paddle.
  • Level editor (to avoid random generated levels).
  • Better graphics (use sprites)

Your imagination is the limit... start hacking it! :)

Personal tools
Adsense