Revision History | ||
---|---|---|
Revision bf69c8e35b92 | 2011-08-01 | jfita |
The downloads for Balls are now images instead of text-only. | ||
Revision 34b7522b4f97 | 2011-03-28 | jfita |
atangle is now using a new style for directives which don't collide with XML tags. I had to update all games and programs as well in order to use the new directive syntax. | ||
Revision 6cc909c0b61d | 2011-03-07 | jfita |
Added the comments section. | ||
Revision c9eaa8c0700e | 2010-12-07 | jfita |
Adde Msc OS X download. | ||
Revision ee5e12ec551b | 2010-12-02 | jfita |
Fixed a typo in an attribute in balls. | ||
Revision 05834d60678b | 2010-12-02 | jfita |
Added the download links for balls. | ||
Revision cbc64249ae63 | 2010-12-02 | jfita |
Added the ball's window title. | ||
Revision 4cfce6ba4827 | 2010-12-02 | jfita |
Added the parameters to balls' main. | ||
Revision 51b327de0ada | 2010-12-01 | jfita |
Reworded some sections of balls. | ||
Revision f62268623456 | 2010-12-01 | jfita |
Reworded some of the sections in balls. | ||
Revision 3dec1ee56026 | 2010-11-30 | jfita |
Fixed ball's introduction. | ||
Revision ed9775e118c3 | 2010-11-29 | jfita |
Added the screenshot of balls. | ||
Revision d3c0c6c9668e | 2010-11-29 | jfita |
Finished the first draft of balls. | ||
Revision 83caede7f3ba | 2010-11-26 | jfita |
Added the collision between explosions and balls. | ||
Revision 7b0414a5b806 | 2010-11-25 | jfita |
Fixed bug with updating the Balls' direction. | ||
Revision b770974d9595 | 2010-11-25 | jfita |
Fixed a problem with clipping rectangles in Balls' SDL_FillCircle. | ||
Revision c7bbfb93e109 | 2010-11-25 | jfita |
Added Ball and Explosion classes. | ||
Revision df5df5644434 | 2010-11-24 | jfita |
Added the game loop section and drawing subsection. | ||
Revision 11dd3e5908d0 | 2010-11-24 | jfita |
Added the SDL section to balls. | ||
Revision 0fc39080c372 | 2010-11-24 | jfita |
Added the initial version of balls. It does nothing but create a bunch of levels. |
Table of Contents
Balls! is a puzzle game in which the player must remove a level-specific number of bouncing balls from the screen. Balls are only removed when they touch an explosion’s wave which, in turn, makes the ball explode and start a new wave. Clicking with the mouse on the screen, the player is only allowed to start a single explosion at the proper place and time which shall start a chain of explosions that must remove, at least, the required number of balls.
Balls! uses SDL and SDL_ttf and is written in C++.
Each level needs two values: the total number of balls on the screen and the number of balls to remove. This is stored in a structure.
<<level structure>>= struct Level { unsigned int balls; unsigned int target; };
Both the total number of balls and the target must be positive; it makes no sense to have less than zero balls.
Balls! has only 10 levels, for simplicity’s sake, stored in an array which I
initialize with the balls
and target
values.
<<levels definition>>= Level levels[] = { // balls, target. { 4, 1}, { 4, 2}, { 8, 4}, {15, 7}, {25, 12}, {30, 17}, {30, 20}, {30, 25}, {30, 28}, {30, 30} };
Then, instead of hard coding the number of levels in the array, I divide the array’s size with the size of a single level to get the total number of levels. This has the advantage that to add a new level I only need to add it to the array without worrying about updating the number of levels available.
<<levels definition>>= const unsigned int numLevels = sizeof(levels) / sizeof(Level);
Of course, the first level is 0
.
<<levels definition>>= unsigned int currentLevel = 0;
To initialize a level I create a new vector
with as many balls as specified
by the current level index in the array. Each ball has the same radius, but
random position and color.
<<constants>>= const unsigned int BALLS_RADIUS = 4;
<<initialize level>>= std::vector<Ball> balls; for (size_t ball = 0 ; ball < levels[currentLevel].balls ; ++ball) { balls.push_back( Ball( rand() % SCREEN_WIDTH, rand() % SCREEN_HEIGHT, BALLS_RADIUS, colors[rand() % colors.size()] ) ); }
To be able to use vector
I have to include its header file.
<<headers>>= #include <vector>
The colors that can be assigned to the balls are converted from their red,
green, and blue components using the screen’s surface format and are kept in a
vector
. With this, I can select a random color by choosing a random index
for that vector
.
<<map level colors>>= std::vector<Uint32> colors; colors.push_back(SDL_MapRGB(screen->format, 255, 0, 0)); // red colors.push_back(SDL_MapRGB(screen->format, 0, 255, 0)); // lime colors.push_back(SDL_MapRGB(screen->format, 0, 0, 255)); // blue colors.push_back(SDL_MapRGB(screen->format, 0, 255, 255)); // aqua colors.push_back(SDL_MapRGB(screen->format, 255, 255, 0)); // yellow colors.push_back(SDL_MapRGB(screen->format, 255, 0, 255)); // fuchsia
With this approach I avoid having balls with colors that are too similar to the background color or are too similar to each other. Fortunately, in Balls! the color doesn’t have any impact on the game play.
Before I can use the random function, though, I need to set a seed value for the random generator. In this case, using the time in which the game started is good enough.
<<initialize random number generator>>= srand(std::time(NULL));
I need to include the cstdlib
header for srand
and ctime
for time
.
<<headers>>= #include <cstdlib> #include <ctime>
In contrast to the balls, there are no explosions when the level starts. It is
the player’s duty to add a new explosion when she sees fit and start that
chain of explosions from the removed balls. Thus, I create a new vector
of
explosions, but I don’t add any until later.
<<initialize level>>= std::vector<Explosion> explosions;
Besides balls and explosions, there are still two flags that the level must keep track of: one to know whether the level starter and another to know if the player already placed the first explosion on the screen.
The first flag is used to show a screen and wait for the player to click on the
screen before the level starts. This is specially useful between levels,
because otherwise the level would start right away after the previous level is
cleared or when the player fails to meet the required target of balls to
remove, in which case the message shown is slightly altered to tell the player
to try again. This variable, thus, must be initialized to false
.
<<initialize level>>= bool levelStarted = false;
And is only set to true
when the player clicks on the screen with the mouse.
<<process event types>>= case SDL_MOUSEBUTTONDOWN: if (!levelStarted) { levelStarted = true; }
The other flag, to know whether the user placed the first explosion, is used to
limit the number of explosions that the player can blow off. It is first set
to the false
.
<<initialize level>>= bool firstExplosion = false;
Then, when the player clicks with the mouse button and the level has already
started, this variable is set to true
and a new explosion is added to the
vector
. If the player tries to click again, the game won’t add another
explosion to the vector
until this flag is set to false
again, which only
happens when the next level starts or the player is forced to repeat the same
level.
The explosion added to the vector
when the player click the mouse button is
set to the cursor’s position, starting from a radius of 1 pixel and has a
random color, like the balls.
<<process event types>>= else if (!firstExplosion) { explosions.push_back(Explosion(Vector2D(event.button.x, event.button.y), 1, colors[rand() % colors.size()])); firstExplosion = true; } break;
A ball has four attributes:
The first two attributes, radius and color, are scalar values with the radius
in pixels and the color as returned by SDL_MapRGB
. On the other hand,
position and direction are both 2D vectors. The direction attribute is used to
update the ball’s position when appropriate.
Thus, I need a 2D vector structure to hold the two coordinates, x and y,
and a constructor to set the initial values of these coordinates. It would be
possible to use std:pair
instead, but the disadvantage of std:pair
is that
its two members are named first
and second
instead of the more familiar x
and y
for 2D vectors. To avoid confusion, I prefer to use a separate
structure, which in this case doesn’t need to be a template because I know that
I only need integer 2D vectors.
<<2d vector struct>>= struct Vector2D { int x; int y; Vector2D(int x, int y): x(x), y(y) { } };
With this new structure is now possible to define a new Ball
class with the
required attributes.
<<Ball class>>= class Ball { public: <<Ball constructor>> <<Ball draw>> <<Ball update>> Uint32 color() const { return color_; } Vector2D position() const { return position_; } unsigned int radius() const { return radius_; } private: Uint32 color_; Vector2D direction_; Vector2D position_; unsigned int radius_; };
All the ball’s initial attributes are passed as parameters to the constructor except for the direction, which is always initialized to move the ball upwards and to the left, although it would be possible to move in a random direction, for instance; it doesn’t alter the game play at all.
<<Ball constructor>>= Ball(int x, int y, unsigned int radius, Uint32 color): color_(color), direction_(-1, -1), position_(x, y), radius_(radius) { }
Once set, the radius and color never change within the lifespan of a Ball
object, but each time the ball is updated, its position and possibly its
direction changes. The position always changes according to the direction, but
the direction only changes when the ball bounces on the level’s limits. The
level’s limits are are passed as parameter to the update
function because the
ball doesn’t have to know anything else about the level and thus avoid coupling
these two objects too much.
<<Ball update>>= void update(int minX, int minY, int maxX, int maxY) { position_.x += direction_.x; if (position_.x <= minX) { direction_.x = 1; } else if (position_.x >= maxX) { direction_.x = -1; } position_.y += direction_.y; if (position_.y <= minY) { direction_.y = 1; } else if (position_.y >= maxY) { direction_.y = -1; } }
To update all the all the position of the balls in the level, thus, I to
iterate the vector
of balls and call their update
function passing the
screen’s resolution as upper limits and 0 as the lower limits, because the
level uses all the screen.
<<update balls>>= for(std::vector<Ball>::iterator ball = balls.begin() ; ball != balls.end () ; ++ball) { ball->update(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); }
To draw a ball on the screen I have to draw a filled circle of the specified radius, with the given color, and at the current ball’s position. The surface on which to draw the ball is also passed as a parameters, because we could be drawing on a screen, on a back buffer surface, etc.
<<Ball draw>>= void draw(SDL_Surface *destination) { SDL_FillCircle(destination, position_.x, position_.y, radius_, color_); }
Much like updating, to draw all the balls on the screen, I iterate all the
balls in the level’s vector
and call the draw
function. In the case of
draw
, though, given that I only to pass a single parameter to the function, I
can use the standard for_each
to iterate the all the elements in the vector
and the bind2nd
structure to bind the screen surface to the draw
second
parameter, the first being the object itself. I also need to use mem_fun_ref
to call a member function of an object reference.
<<draw balls>>= std::for_each(balls.begin(), balls.end(), bind2nd(std::mem_fun_ref(&Ball::draw), screen));
The for_each
, bind2nd
, and mem_fun_ref
helpers are declared in the
standard algorithm
header that I need to include.
<<headers>>= #include <algorithm>
An explosion also needs four attributes:
Three of the attributes are the same as the attributes for balls — color, position, and radius — but instead of moving around the screen, the explosions grows their radius up to a maximum and then shrink until the radius is zero. Whether the radius grows or shrinks is kept in the radius growth attribute, which is the number of pixels to grow or shrink the radius each update. Thus this value is a signed scalar.
<<Explosion class>>= class Explosion { public: <<Explosion constructor>> <<Explosion draw>> <<Explosion update>> Vector2D position() const { return position_; } unsigned int radius() const { return radius_; } private: Uint32 color_; Vector2D position_; unsigned int radius_; int radiusGrowth_; };
The color and position are fixed through all the explosion’s lifetime and passed as parameters to the constructor. The initial explosion radius is also passed as parameter, because it is different if the explosion is started by the player (the initial radius is zero) or if it is due a ball exploding (the initial radius is the same as the ball’s radius.) In both cases, though, the explosion is growing and thus the growth rate is 1 pixel positive.
<<Explosion constructor>>= Explosion(const Vector2D &position, unsigned int radius, Uint32 color): color_(color), position_(position), radius_(radius), radiusGrowth_(1) {
As stated, when an explosion is updated it grows or shrinks depending on the
sign of radiusGrowth_
: if positive it grows, otherwise it shrinks. The
explosion grows until it reaches a maximum radius, which passed as a parameter
of update
. Once it reaches this maximum radius, it then shrinks the radius
until reaching 0, in which case the explosion is no longer active.
For that motive, I can’t allow the Explosion
class to have an initial radius
of zero, otherwise it wouldn’t grow at all. Passing a radius non-zero radius
to the constructor is a caller’s responsibility and I only enforce this
condition using an assert
. I could also use an exception, but I don’t want
to keep the condition for release build, where assert
does nothing.
<<Explosion constructor>>= assert(radius > 0 && "The explosion radius can't be 0."); }
The assert
macro is defined in the standard cassert
header file.
<<headers>>= #include <cassert>
To update
an explosion then I increment the radius as many units as specified
by the radiusGrowth_
if that radius is not zero already. The maximum radius
that the explosion can grow to is passed as parameter.
<<Explosion update>>= void update(unsigned int maximumRadius) { if (radius_ > 0) { radius_ += radiusGrowth_; if (radius_ >= maximumRadius) { radiusGrowth_ = -1; } } }
For Balls! I’ve decided to use the same maximum radius for all the explosions. Thus, I need to define a maximum explosion radius as a constant.
<<constants>>= const unsigned int EXPLOSION_MAXIMUM_RADIUS = 30;
Them, I use this constant to update all the explosions inside the level’s
explosions vector
. If any of the explosions reaches a radius of zero, then
this explosion gets removed from the vector.
<<update explosions>>= for(std::vector<Explosion>::iterator explosion = explosions.begin() ; explosion != explosions.end() ; ) { explosion->update(EXPLOSION_MAXIMUM_RADIUS); if (explosion->radius() > 0) { <<check collision between explosion and balls>> ++explosion; } else { explosion = explosions.erase(explosion); } }
If after updating an explosion its radius still isn’t zero, then I need to
check if it collides with any of the balls bouncing on the screen. If any ball
enters in contact with then explosion, I replace the ball with an explosion by
adding a new explosion to the explosions
vector
and removing the ball
balls
. Unfortunately, I can’t just push_back
the new explosion into the
vector
because if the vector
needs to allocate more memory to accommodate
the new element, then the iterator gets invalidated. To avoid these
allocations, when starting the level I am going to reserve as many explosions
as balls there are in the level, plus the explosion triggered by the user,
because as the player can only start a single explosion there can’t be any
other explosion. Keep in mind that this doesn’t create the Explosion
objects, just reserve enough memory to hold at least as many Explosion
objects as told.
<<initialize level>>= explosions.reserve(levels[currentLevel].balls);
Without the possibility of allocations, now I can push the new explosion if a
ball and explosion collides, remembering to remove the ball from the vector
.
The explosion’s position, color, and initial radius are the same as the ball’s.
<<check collision between explosion and balls>>= for (std::vector<Ball>::iterator ball = balls.begin () ; ball != balls.end () ; ) { if (collides(*explosion, *ball)) { explosions.push_back(Explosion(ball->position(), ball->radius(), ball->color())); ball = balls.erase(ball); } else { ++ball; } }
The collision function uses the fact that both the explosion and the ball are circles. Thus, if the distance between the centers of the two circles is less than the sum of their radius, the two are colliding. The distance between the two centers can be computed using the Pythagorean theorem, but instead of retrieving the actual distance, I am going to get the squared distance, to avoid a square root.
<<collision function between explosion and ball>>= bool collides(const Explosion &explosion, const Ball &ball) { unsigned int distanceSquared = (explosion.radius() + ball.radius()) * (explosion.radius() + ball.radius()); return ((ball.position().x - explosion.position().x) * (ball.position().x - explosion.position().x) + (ball.position().y - explosion.position().y) * (ball.position().y - explosion.position().y)) < distanceSquared; }
Finally, much like balls, to draw explosion I need to draw a filled circle at the explosion’s position, using its current radius, and with the specified color.
<<Explosion draw>>= void draw(SDL_Surface *destination) { SDL_FillCircle(destination, position_.x, position_.y, radius_, color_); }
Again, to draw all the explosion in the level, I am going to use for_each
but the explosions are drawn in reverse order, so newer explosions are drawn
under older. This is because explosions are drawn after the balls and thus
appear the be on top of them. When an explosion is fired, then, it also must
come from under the already existent explosions. Drawing from the beginning
to the end of the vector
would, then, draw the explosions backwards of what
is expected.
<<draw explosions>>= std::for_each(explosions.rbegin(), explosions.rend(), bind2nd(std::mem_fun_ref(&Explosion::draw), screen));
The game loop for Balls! is fairly typical: receive events, update entities, draw entities, update screen, limit the frame rate. This all must occur while the level is running which, which in this game means until the player placed the first explosion and all the subsequent explosion have faded off; i.e., the vector of explosions is empty again.
<<game loop>>= while (!quit && (!firstExplosion || !explosions.empty())) { <<process events>> <<clear screen>> if (levelStarted) { <<update balls>> <<update explosions>> <<draw balls>> <<draw explosions>> <<draw exploded balls and target>> } else { <<draw start level screen>> } <<refresh screen>> <<control frame time>> } <<check for next level>>
Notice how, as I said in the section called “Levels”, if the level hasn’t started yet I do not update the balls nor the explosions and instead I show a screen that prompts the player to click a mouse button to start the level.
Also, when the level is finished, I need to check whether the player can go to the next level by checking if she has reached the target number of balls to remove.
As Balls! is a fairly low resource game, if I let the balls and explosions update as fast as the computer allows I would end up having a game to fast to be enjoyable. To avoid that, I pause for a few milliseconds each loop. Here I use the most easy and usually the less desirable way to control the game’s frame per second: pause a fixed amount of milliseconds. In this case, I found that 50 milliseconds seems to be nice.
<<control frame time>>= SDL_Delay(50);
This means that the game runs at a maximum of 20 frames per second (1000 / 50), but if the computer is not fast enough to cope with all the other chores it must perform within the loop, the game will run slower. This is the reason why this way is not too often used. In this case, being a very simple game, and without the need of very smooth animations I believe that it will be enough.
For Balls! I only need to receive two kind of events:
The quit event is received when the player closes the game’s window. In this case the only thing that I need to do is mark the game as over and end the loop.
<<process event types>>= case SDL_QUIT: quit = true; break;
Another way to quit the game is by pressing the Escape key. This case is the
same as the SDL_QUIT
, but additionally I need to check if the pressed key is
really the escape key.
<<process event types>>= case SDL_KEYDOWN: if (SDLK_ESCAPE == event.key.keysym.sym) { quit = true; } break;
Once the level updated the status of all its entities — both balls and explosions — is time to draw them onto the screen. But, before drawing the current game’s status, I need to remove the previously drawn state. The easiest way to clean up the previous state is to fill the whole screen with the background color before drawing any other entity. That is, of course, also the slowest method as it needs to fill regions of the screen that doesn’t need to be redraw but most systems should cope with this, specially given the small game’s resolution.
<<clear screen>>= // The NULL rect is to fill the whole surface. SDL_FillRect(screen, NULL, background);
The background color is an unsigned integer that I’ve extracted from the screen’s surface by mapping arbitrarily selected red, green, and blue components.
<<map background color>>= // 42 is always the answer. SDL_Color backgroundColor = {42, 84, 126}; Uint32 background = SDL_MapRGB(screen->format, backgroundColor.r, backgroundColor.g, backgroundColor.b);
While playing, the level must show the number of balls already removed and the number the level demands to be removed — its target — before passing to the next one.
The target is readily available from the levels
array initialized at the
beginning. To know the number of removed balls, instead of having a separate
variable, I am going to subtract the number of remaining balls in the balls
vector from the total number of balls that are supposed to be on the level.
<<draw exploded balls and target>>= unsigned int explodedBalls = levels[currentLevel].balls - balls.size();
Then, using a string stream from the standard library, I convert this information to a string to draw onto the bottom right corner of the screen.
<<draw exploded balls and target>>= std::ostringstream levelBalls; levelBalls << explodedBalls << "/" << levels[currentLevel].target; draw_right_string(font, levelBalls.str(), SCREEN_WIDTH - 10, SCREEN_HEIGHT - 20, fontColor, backgroundColor, screen);
The string stream is defined in the standard sstream
header file.
<<headers>>= #include <sstream>
When the current level is finished, I need to check if the number of removed balls reached the target imposed by the level. Again, I can know the number of exploded balls by the difference between the remaining balls and the total number of balls. If this difference is equal or greater than the target, then I can increment the current level index.
<<check for next level>>= nextLevel = levels[currentLevel].balls - balls.size() >= levels[currentLevel].target; if (nextLevel) { ++currentLevel; }
The nextLevel
variable is set to true
at the program’s start. This
variable is also used to know whether to show the "next level" screen or the
"try again" screen when starting the level.
<<initialize variables>>= bool nextLevel = true;
When the number of levels reached its limit, then the game must end.
<<check for next level>>= finished = currentLevel >= numLevels; if (finished) { quit = true; }
The finished
variable is initialized, as expected, to false
.
<<initialize variables>>= bool finished = false;
The text to show while waiting for the user to click and start the level
depends on whether we are really starting a new level or retrying the same. I
can know this by looking at the nextLevel
variable, which is set to true
only when starting a new level.
<<draw start level screen>>= std::string startLevelText; if (nextLevel) { startLevelText = "Click to Start Next Level"; } else { startLevelText = "Oops! Try Again"; } draw_centered_string(font, startLevelText, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, fontColor, backgroundColor, screen);
When all the levels are finished, the game shows a text informing the player.
<<draw finished text>>= <<clear screen>> draw_centered_string(font, "All Levels Finished. Well Done!", SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, fontColor, backgroundColor, screen); <<refresh screen>>
Then, it waits for the player to close the window, press escape or click the
mouse button. In this case I am using SDL_WaitEvent
because I only need the
event and don’t need the CPU for anything else.
<<wait for exit event>>= quit = false; while (!quit) { SDL_Event event; SDL_WaitEvent(&event); quit = SDL_QUIT == event.type || SDL_MOUSEBUTTONDOWN == event.type || (SDL_KEYDOWN == event.type && SDLK_ESCAPE == event.key.keysym.sym); }
To use any function from SDL I first need to include its header. The actual
directory in which the header is located at is passed to the compiler by the
build system, thus including the SDL.h
file (in caps, UNIX systems are case
sensitive) is enough.
<<headers>>= #include <SDL.h>
For this game, I only need SDL’s timer and video subsystems. I tend to only
initialize the SDL’s subsystems that I really need instead of using
SDL_INIT_EVERYTHING
to initialize everything, because if any of the
subsystems can’t be initialized — such as the CD-ROM under Dingux — the
initialization would return an error. Specifying the subsystems that I
require means that I don’t need to which subsystem failed; I can’t continue
at all.
<<initialize SDL>>= if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) < 0) { throw sdl_error(); }
sdl_error
is just a convenient class derived from std::runtime_error
that
uses the output of SDL_GetError
to set the exception message to be the error
as reported by SDL.
<<sdl_error class definition>>= class sdl_error: public std::runtime_error { public: sdl_error(): std::runtime_error(SDL_GetError()) { } };
std::runtime_error
is a standard exception class declared and defined in the
stdexcept
header file.
<<headers>>= #include <stdexcept>
Once SDL is initialized, I need to make sure that SDL will be properly released
when exiting the application. Being a pure C library, be best way to ensure
that SDL is released when the application ends, either correctly or due an
error, is to use the atexit
function. With atexit
I can tell the
application to call SDL_Quit
whenever the application calls exit
either
explicitly or by returning from the main
function.
<<initialize SDL>>= atexit(SDL_Quit);
Another way would be to wrap the calls to SDL’s functions in a class whose
constructor would call SDL_Init
and its destructor SDL_Quit
, but to me it
just adds unnecessary code for too little gain, although I have to admit that
that using a wrapper class feels more like C\++.
With SDL initialized, I need a surface in which to draw the current game’s
status onto. Regardless of whether the surface is actually the whole system’s
screen or just a window, I will call this surface screen
.
The screen’s size for Balls! is set to the same resolution as an iPhone set in landscape mode, which is 480x320 pixles (HVGA.) This resolution is arbitrary and could be changed without any effect on the game play.
<<constants>>= const size_t SCREEN_HEIGHT = 320; const size_t SCREEN_WIDTH = 480;
As for the bits per pixel, I really don’t care much. Balls! doesn’t require
a large number of colors, thus a 8-bit surface would do, but higher bits per
pixel won’t degrade the game’s performance noticeably. Then, I’ll tell SDL to
use the current desktop’s bits per pixel by specifying 0
.
<<constants>>= const size_t SCREEN_BPP = 0;
The surface I request to SDL is a software surface, not hardware. According to SDL’s documentation, to achieve a high framerate when doing per-pixel manipulation the screen, such as when drawing the circles, must be a software surface, otherwise each time the surface is locked the screen’s contents must be copied from video memory to system memory and back when unlocked.
<<initialize screen>>= SDL_Surface *screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE); if (NULL == screen) { throw sdl_error(); }
With this surface created, it is now possible to draw the game’s contents on it. But, for the user to be able to see the actual screen I must update the screen’s surface. The reason behind this update is that most systems, for performance reasons, don’t update the application’s contents all the time, only when necessary. What is deemed necessary is application dependent, but most application only needs to redraw the screen when a control, such as an edit box, changes its contents or when the application’s window was previously hidden under another’s window. This is, of course, not true for game which needs to update its screen each frame.
SDL provides the SDL_UpdateRect
function to make sure that a surface’s
rectangle is updated and visible to the player, but I usually use SDL_Flip
instead. When applying a surface without double buffering as a parameter,
SDL_Flip
actually behaves just like SDL_UpdateRect(surface, 0, 0, 0, 0)
.
But when the surface is double buffered it swaps the surface’s back buffer and
front buffer instead. Thus, SDL_Flip
works for all surface and is easier to
remember.
<<refresh screen>>= SDL_Flip(screen);
SDL allows me to change the screen’s window’s title for my own instead of the default SDL_App. I just need to pass the string to display on the window’s top part and the text to show when the window is iconified. In this case I use the same string for both.
<<initialize SDL>>= SDL_WM_SetCaption("Balls!", "Balls!");
In a typical SDL game, the application receives a number of events comming either from the player, such as a mouse click, or from internals subsystems, such as timers. SDL keeps all received events in a queue ready to be read by the game when it sees fit.
To get the events and remove them from SDL’s queue, I am going to use
SDL_PollEvent
. SDL also provides the SDL_WaitEvent
function to perform the
same job, but the difference between the two functions is that SDL_WaitEvent
waits indefinitely until there is an event, while SDL_PollEvent
returns
whether there is an event in the queue or not.
<<process events>>= SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { <<process event types>> } }
SDL by itself has no built-in functions to draw text on the screen. There are some different techniques to show text in SDL, but for simplicity’s sake I am going to use SDL_ttf. With SDL_ttf, I can open most TrueType fonts and use those to fonts to generate surfaces with the text on it.
All SDL_ttf’s functions are defined in the SDL_ttf.h
header file.
<<headers>>= #include <SDL_ttf.h>
But before I can load fonts, I first must initialize the library in the same fashion as I initialize the base SDL library, except that I have no subsystems to initialize.
<<initialize SDL_ttf>>= if (TTF_Init() < 0) { throw sdl_error(); }
Just like SDL, at exit I need to call its clean up function.
<<initialize SDL_ttf>>= atexit(TTF_Quit);
Now is possible to load the font file by passing the font’s file name and the size to use to draw text. For this game I am going to use Bitstream Vera Fonts due to their generous license.
<<load font>>= TTF_Font *font = TTF_OpenFont("VeraMono.ttf", 16); if (NULL == font) { throw sdl_error(); }
With the font loaded, drawing text is just a simple matter of calling the adequate SDL_ttf’s function to generate a new surface with the text to show. For Balls! I’ll add two new functions to draw text. The first is to draw the font centered on a given position, the other to draw the text right aligned to the given position.
For the first function, I first need to generate the surface with the text by
calling TTF_RenderText_Shaded
. Then, I am going to use half the image’s
with and half the image’s height to move the image’s center to the specified
position.
<<draw centered string function>>= void draw_centered_string(TTF_Font *font, const std::string &text, int x, int y, const SDL_Color &foregroundColor, const SDL_Color &backgroundColor, SDL_Surface *destination) { assert(NULL != font && "The font is NULL."); assert(NULL != destination && "The destination surface is NULL."); SDL_Surface *textSurface = TTF_RenderText_Shaded(font, text.c_str(), foregroundColor, backgroundColor); if (NULL == textSurface) { throw sdl_error(); } // Center the surface to the given position. SDL_Rect pos; pos.x = x - textSurface->w / 2; pos.y = y - textSurface->h / 2; pos.w = 0; pos.h = 0; SDL_BlitSurface(textSurface, NULL, destination, &pos); SDL_FreeSurface(textSurface); }
The function to draw the right aligned text is almost the same, but instead of subtracting halt the rendered text’s width, it subtracts the whole width. The height is still centered, though.
<<draw right string function>>= void draw_right_string(TTF_Font *font, const std::string &text, int x, int y, const SDL_Color &foregroundColor, const SDL_Color &backgroundColor, SDL_Surface *destination) { assert(NULL != font && "The font is NULL."); assert(NULL != destination && "The destination surface is NULL."); SDL_Surface *textSurface = TTF_RenderText_Shaded(font, text.c_str(), foregroundColor, backgroundColor); if (NULL == textSurface) { throw sdl_error(); } // Center the surface to the given position. SDL_Rect pos; pos.x = x - textSurface->w; pos.y = y - textSurface->h / 2; pos.w = 0; pos.h = 0; SDL_BlitSurface(textSurface, NULL, destination, &pos); SDL_FreeSurface(textSurface); }
In both cases I am going to draw the text in white.
<<define font color>>= SDL_Color fontColor = {255, 255, 255};
All the modules described above can be placed on a single source module ready to compile.
<<*>>= // // Balls! -- A simple game with balls. // Copyright 2010 Jordi Fita <jfita@geishastudios.com> // <<license>> // <<headers>> namespace { <<constants>> <<level structure>> <<2d vector struct>> <<sdl_error class definition>> <<draw centered string function>> <<draw right string function>> <<draw filled circle function>> <<Ball class>> <<Explosion class>> <<collision function between explosion and ball>> } int main(int argc, char *argv[]) { try { <<initialize random number generator>> <<levels definition>> <<initialize variables>> <<initialize SDL>> <<initialize screen>> <<initialize SDL_ttf>> <<load font>> <<define font color>> <<map background color>> <<map level colors>> bool quit = false; while (!quit) { <<initialize level>> <<game loop>> } if (finished) { <<draw finished text>> <<wait for exit event>> } return EXIT_SUCCESS; } catch (std::exception &e) { std::cerr << "Error: " << e.what() << "\n"; } catch (...) { std::cerr << "Unknown error\n"; } return EXIT_FAILURE; }
Notice how I wrapped the “happy path” in a try
catch
block that catches
any thrown exception. All the exceptions thrown by Balls! are considered
fatal, because I can’t do anything if I can’t initialize SDL, or the font, etc.
Thus, I can only show the error to the user, using cerr
, and then exit with
an error status code.
To be able to use the cerr
, I need to include the standard iostream
header.
<<headers>>= #include <iostream>
Also, even though I don’t use arguments in this application, I declared the
main
function to have the standard int
and argv *[]
parameters, instead
of using an empty parameter list, which is also a possibility in C++. I’ve
done this because SDL, in Windows and Mac OS X, renames the main
function,
for cross-platform uniformity, to SDL_main
which requires those two
parameters.
I am using Midpoint circle algorithm but instead of only plotting point on the screen, I fill four rectangles per loop: two on the top hemisphere, and two on the bottom.
<<draw filled circle function>>= void SDL_FillCircle(SDL_Surface *dst, int cx, int cy, unsigned int radius, Uint32 color) { if (0 == radius) { return; } int error = -radius; int x = radius; int y = 0; while (x >= y) { SDL_Rect rect; rect.h = 1; rect.w = 2 * x; rect.x = cx - x; rect.y = cy - y; SDL_FillRect(dst, &rect, color); rect.h = 1; rect.w = 2 * x; rect.x = cx - x; rect.y = cy + y; SDL_FillRect(dst, &rect, color); rect.h = 1; rect.w = 2 * y; rect.x = cx - y; rect.y = cy - x; SDL_FillRect(dst, &rect, color); rect.h = 1; rect.w = 2 * y; rect.x = cx - y; rect.y = cy + x; SDL_FillRect(dst, &rect, color); error += y; ++y; error += y; if (error >= 0) { --x; error -= 2 * x; } } }
Balls! is a cross platform game that needs to build and link against the SDL library on different platforms. A Makefile that whould work on all configurations and platforms would be too cumbersome to write, specially when involving cross compilers.
There are numerous solutions available that make this process easier by detecting which platform to build to and configure the underlying building system accordingly. For Balls! I’ve chosen to use CMake, a build system that relies on the system’s appropriate tools (Makefiles, Solution files, etc.) to build and link the application, all this by using compiler independent configurations files.
CMake’s configuration file is called CMakeLists.txt and the first thing I need to write is the project’s name. The project’s name is used, for example, as the name for Visual Studios Solution file.
<<CMakeLists.txt>>= project(balls)
Although not strictly required, starting from version 2.6 CMake issues a warning if the configuration file doesn’t specify the minimum required version of CMake. In this case, since I have only access to CMake’s versions 2.6 and 2.8, I’ll make version 2.6 the minimum required version. It is, of course, possible that this file works in earlier versions of CMake, but I have no way to test that.
<<CMakeLists.txt>>= cmake_minimum_required(VERSION 2.6)
Being an SDL game, Balls! needs to tell the compiler where to look for its
headers files as well as specify to the linker which libraries to link to.
CMake includes a package that automatically looks for SDL’s header and
libraries files according to the build platform. Being a requirement, I tell
CMake to stop if the SDL package can’t be found by adding the REQUIRED
parameter to find_package
. The SDLMAIN_LIBRARY is required for Windows and
Mac OS X only, but is harmless to add it for Linux.
<<CMakeLists.txt>>= # SDL is required to build Balls! find_package(SDL REQUIRED) include_directories(${SDL_INCLUDE_DIR}) link_libraries(${SDL_LIBRARY}) link_libraries(${SDLMAIN_LIBRARY})
The same must be done with SDL_ttf.
<<CMakeLists.txt>>= # SDL_ttf is required as well. find_package(SDL_ttf REQUIRED) include_directories(${SDLTTF_INCLUDE_DIR}) link_libraries(${SDLTTF_LIBRARY})
With all the build dependences found, it is now possible to build the game.
Before that, though, I must extract the source code from the AsciiDoc document
but only if the file is not already extracted. This is useful when
distributing the game and don’t impose a dependence to atangle
.
CMake’s add_custom_command
is the ideal function to add a new command to the
build system. In this case, I tell CMake that I want to generate a balls.cpp
file from this document using atangle
if balls.cpp
doesn’t exist or is
older than the AsciiDoc document. Notice how I use the ${CMAKE_SOURCE_DIR}
variable to tell to CMake that the source file must be created in the same
directory where CMakeLists.txt
is and not in the directory where is building
the game, which could be different.
<<CMakeLists.txt>>= add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/balls.cpp COMMAND atangle code.txt > balls.cpp MAIN_DEPENDENCY ${CMAKE_SOURCE_DIR}/code.txt WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Extracting aWEB source code")
Once extracted, this source code file can be used to build the executable. It is an habit of mine to add all source and header files in lists so I can later add properties onto. The list’s name in this case is simply SOURCES.
<<CMakeLists.txt>>= set(SOURCES ${CMAKE_SOURCE_DIR}/balls.cpp)
When CMake is analyzing the configuration file and adding build targets, it verifies that the files specified to build a target are available. In the case of Balls! the source code file could be missing if for example starting from a fresh checkout from the version control system and thus CMake would complain and exit before creating the necessary rules to extract the source code from the AsciiDoc.
Setting the file’s GENERATED
property to ON
, CMake no longer verifies the
existence of those files because as they are the output of some other tool — atangle
, in this case.
<<CMakeLists.txt>>= set_source_files_properties(${SOURCES} PROPERTIES GENERATED ON)
The last remaining bit of information to give to CMake is to tell it that I want to build a binary executable that needs to be a graphical windows application or Mac OS X’s bundle, depending on the build platform, which the source files already listed.
<<CMakeLists.txt>>= add_executable(balls WIN32 MACOSX_BUNDLE ${SOURCES})
This program is distributed under the terms of the GNU General Public License (GPL) version 2.0 as follows:
<<license>>= // This program is free software; you can redistribute is and/or modify // it under the terms of the GNU General Public License version 2.0 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 50 Temple Place, Suite 330, Boston, MA 02111-1307 USA
© 2009, 2010, 2011 Geisha Studios