16 Software
Breevy: A text expander that lets you insert long words or phrases and launch apps, websites, and more just by typing abbreviations.
IcyScreen: Automatic screenshots. Have them saved, e-mailed, and uploaded.

Getting Your Feet Wet in SDL, Part 2: The Pong Clone!

In Part 1, I gave a little teaser introduction to SDL. I explained what it is exactly (a free, open-source, portable game programming API), how easy it is to use, along with some other basic stuff, such as the basic initialization functions, how graphical "memory" is represented in it, where to get it, etc.

Anyway, today I'll be walking you through the code of a Pong clone I wrote a little while back. I think it's the perfect game to start with when learning how to write a game -- it was the first game I wrote -- because it's simple and short (< 400 lines), but it still utilizes important concepts you'll need to know when writing other games, such as making graphics move around on the screen, detecting key presses and other events, capping the frame rate, drawing text, basic collision detection, writing a game loop, etc.

Prerequisites

If you haven't done so yet, you should probably read Part 1 of this two-part series (it's pretty short) because it'll definitely help out if you're new to SDL, and I do make the assumption that you're familiar with what I talked about in the article. Grab a copy of the SDL docs as well.

In order to compile the game, you'll need to have the SDL and SDL_TTF library and header files, and you'll need to point your compiler to them and append the appropriate linker flags (-lSDL -lSDL_ttf should work). In order to run the game, you'll also need the SDL and SDL_TTF runtime files. Place them in a directory where your program can find them; on Windows (.DLL files) your easiest bet is to place them in the directory that you'll be placing the game executable in. On *nix, you likely don't need to do anything if you've installed everything by way of your package management system.

Edit (4/26): If you're having some issues getting SDL set up, check out this excellent comment.

You'll probably want to order a pizza and/or a nice sandwich, too.

Everything you need in a package...

Note that I didn't post the pong.h header file code here, number one because I think header files are boring, and number two because it would take up too much unnecessary space. You can download the header file, as well as the source code, font, and images necessary to compile and run the Pong game, here.

The code

So enough of the chatter, let's get down to business. I think the best way to walk through this code is to go function by function, as each one is called, so here goes:

#include "pong.h"

paddleData lPaddle, rPaddle;
ballData pongBall;
SDL_Surface *main_screen = NULL;
TTF_Font *score_font = NULL;

int main(int argc, char **argv) {

srand(time(NULL));

if (SDL_Init(SDL_INIT_VIDEO) != 0) {
fprintf(stderr, "Unable to initialize SDL: %s\n", SDL_GetError());
exit(1);
}

main_screen = SDL_SetVideoMode(WIN_WIDTH, WIN_HEIGHT,
BITS_PER_PIXEL, SDL_SWSURFACE);
if (main_screen == NULL) {
fprintf(stderr, "Unable to get screen: %s\n", SDL_GetError());
quit_the_game();
}

SDL_WM_SetCaption("Pong Clone", NULL);

if (load_all_images() != 0) {
fprintf(stderr, "Unable to load all images.\n");
quit_the_game();
}

if (setup_score_font() != 0) {
fprintf(stderr, "Unable to load font!\n");
quit_the_game();
}

set_initial_coordinates_for_images();

run_game_loop();

quit_the_game();
return 0;
}

As I described earlier, we call SDL_Init() to initialize SDL and the video subsystem, and SDL_SetVideoMode() to create a video screen that we can draw our graphics on. SDL_SWSURFACE lets SDL know we want the video surface to be created in system, rather than in video, memory. The video screen is returned as an SDL_Surface, which is what all graphical memory is represented as in SDL.

Loading up our images and font

The next function called (besides SDL_WM_SetCaption(), which I'm sure you don't need an explanation for) is load_all_images():

int load_all_images(void) {

SDL_Surface *paddle = image_load_from_file("images/paddle.bmp", TRUE);
if (paddle == NULL) {
return -1;
}

lPaddle.surface = paddle;
rPaddle.surface = paddle;

SDL_Surface *ball = image_load_from_file("images/ball.bmp", TRUE);
if (ball == NULL) {
return -1;
}
pongBall.surface = ball;

return 0;
}

All we're doing here is loading an image into an SDL_Surface with image_load_from_file(), then setting the global image surface variables to these loaded surfaces, so we can draw them on the screen later. Note how the left and right paddle share the same surface data, since we don't mind if both the left and right paddles look exactly the same. We just have to remember to call SDL_FreeSurface() on only one of the surfaces when we're done with them. Here's the image_load_from_file() code:

SDL_Surface *image_load_from_file(const char *path, int apply_colorkey) {

SDL_Surface *loaded = SDL_LoadBMP(path);
if (loaded == NULL) {
return NULL;
}

if (apply_colorkey == TRUE) {

SDL_SetColorKey(loaded, SDL_SRCCOLORKEY | SDL_RLEACCEL,
(Uint32)SDL_MapRGB(loaded->format, 0, 0, 0));
}

SDL_Surface *optimized = SDL_DisplayFormat(loaded);
SDL_FreeSurface(loaded);

return optimized;
}

SDL_LoadBMP() loads up the image at the path specified. If the apply_colorkey variable is TRUE, we call SDL_SetColorKey(). The color key is the RGB value that SDL should show as transparent on the screen. We call SDL_MapRGB() (a convenience function, to convert individual RGB values into a 32-bit integer) with all zeros to let SDL we want all pixels that are absolute black in our images to be transparent. SDL_RLEACCEL enables RLE acceleration when drawing the surface on screen.

Then we call SDL_DisplayFormat() with the loaded image surface. This isn't necessary, but it converts the image to the format and colors of the display, so SDL doesn't have to do it each time the image is blitted (drawn) on screen.

Back to the main() function, we make a call to setup_score_font():

int setup_score_font(void) {

if (TTF_Init() != 0) {
return -1;
}

score_font = TTF_OpenFont("fonts/SAVEDBYZ.TTF", 32);
if (score_font == NULL) {
return -1;
}

return 0;
}

All this does is initialize the SDL_TTF system with a call to TTF_Init(), and then load up our font (I'm using Saved By Zero, which looks kind of retro-ish yet modern at the same time) at a point size of 32. You can use whatever font you'd like.

Setting the initial image coordinates

Returning once more to the main() function, we call set_initial_coordinates_for_images():

void set_initial_coordinates_for_images(void) {

lPaddle.x = 0;
lPaddle.y = rand_between(0, WIN_HEIGHT - lPaddle.surface->h);

rPaddle.x = WIN_WIDTH - rPaddle.surface->w;
rPaddle.y = rand_between(0, WIN_HEIGHT - rPaddle.surface->h);

pong_ball_reset();
}

Here we set the X/Y coordinates for the left paddle, the right paddle, and the ball. Now's a great time to explain something you should always keep in mind when setting coordinates for graphics in your game: the graphic's width and height.

In SDL (and most other libraries, I'm sure), drawing starts at the coordinates you provide. So if you were to tell SDL you want to draw an image at an X/Y coordinate of 0,0 on the screen, drawing would start at 0,0 (top left-hand corner), and would finish at 0+image width, 0+image height.

In our case, we want to have each paddle on the very end of either side of the screen: the left paddle at the very left, the right paddle at the very right. To place the left paddle at the very left-hand side of the screen, we can just pass an X (left/right) coordinate of 0. However, for the right-paddle to be placed on the very right-hand side of the screen, we must factor in the paddle's width. If we didn't, and just passed an X coordinate of the width of the screen (WIN_WIDTH), the paddle would only start being drawn at that coordinate, which means you wouldn't see the paddle -- it would be past the right edge of the screen. The simple solution is to factor in the width of the paddle... so the X coordinate should be WIN_WIDTH - rPaddle.surface->w, instead.

The same goes for the Y (up/down) coordinates. When you want to draw the image starting at the very top of the screen, just use a Y coordinate of 0 -- because it'll start being drawn at that point. When you want to draw the image at the very bottom of the screen, use a Y coordinate of WIN_HEIGHT - (image height), so that the paddle finishes being drawn at the very bottom of the screen.

In short: when thinking about what coordinate to set for your image, think about where you want the image to start being drawn, so that it's fully visible. I hope I haven't confused you.

The game loop

We're finally through with our boring initialization code. The next function main() calls is our game loop, run_game_loop(). The purpose of a game loop is to... uhh, loop. But it's more than just that. On each loop, the game loop can handle user input (keypresses, mouse movement, etc), graphic movement, program events, drawing, clearing the screen, updating the score, etc. I guess you could say that the game loop is somewhat analgous to an operating system kernel. It's the driver of the game:

void run_game_loop(void) {

Uint32 start_ticks = 0, end_ticks = 0;
SDL_Event event;
Uint8 *keystate = NULL;

for (;;) {

start_ticks = SDL_GetTicks();

while (SDL_PollEvent(&event)) {

switch (event.type) {

case SDL_QUIT:
quit_the_game();
}
}

keystate = SDL_GetKeyState(NULL);

handle_keystate(keystate);
redraw_all_images();

pong_ball_handle_collision();

end_ticks = SDL_GetTicks();
cap_fps(start_ticks, end_ticks);
}
}

Phew. Lots of stuff to deal with here. I use for (;;) instead of while (TRUE), simply because it looks cooler. Both loop infinitely, which is all that matters.

At the top of the loop we call SDL_GetTicks(), which returns the number of milliseconds elapsed since our game first started (we'll deal with this value further on down in the loop, when capping the framerate). Then we have a while loop, which calls SDL_PollEvent(). SDL_PollEvent() simply polls SDL's event queue for a pending event (such as a mouse movement, keypress, etc). If it returns 1, there was an event, and it was "popped" off the queue and the SDL_Event structure we passed to it is filled with info pertaining to the event. All we want to deal with here is the SDL_QUIT event type, which we'll automatically receive if the user presses the 'X' button. If that happens, we call our quit function, quit_the_game(), which I'll show you later on.

Handling the key state

Then we grab what's called the "key state" with a call to SDL_GetKeyState(). This returns a pointer to a byte-array, which contains pressed/unpressed (1/0) values for each key. You can get the state of each key simply by accessing the corresponding array subscript, which can either be one of SDL's defined keysym constants representing the key (such as SDLK_a for the 'a' key, SDLK_ESCAPE for the Escape key, etc... see the header file SDL_keysym.h for a complete list) or the ASCII value (such as 'a' for the 'a' key). Anyway, we pass this keystate array to handle_keystate(), which... handles the keystate array:

void handle_keystate(Uint8 *keystate) {

if (keystate[LPADDLE_UP_KEY]) {
paddle_move(&lPaddle, DIRECTION_UP);
}

if (keystate[LPADDLE_DOWN_KEY]) {
paddle_move(&lPaddle, DIRECTION_DOWN);
}

if (keystate[RPADDLE_UP_KEY]) {
paddle_move(&rPaddle, DIRECTION_UP);
}

if (keystate[RPADDLE_DOWN_KEY]) {
paddle_move(&rPaddle, DIRECTION_DOWN);
}

if (keystate[START_GAME_KEY]) {

if (pongBall.moving == FALSE) {

pong_ball_set_in_motion();
}
}

if (pongBall.moving == TRUE) {
pong_ball_move();
}

if (keystate[QUIT_GAME_KEY]) {
quit_the_game();
}
}

Moving the paddle(s)

We check the state of the paddle movement keys in this function. If they're pressed, we call paddle_move() and pass it the paddle that should be moved depending on the key that was pressed, along with the direction we want to move it in:

void paddle_move(paddleData *paddle, unsigned int direction) {

int difference =
image_get_acceptable_y_movement(paddle->surface, paddle->y,
PADDLE_SPEED, direction);

paddle->y += difference;
}

This function calls yet another function, image_get_acceptable_y_movement(), which returns the maximum number of pixels (no greater than PADDLE_SPEED, in our case) that the image's Y coordinate should be increased/decreased, depending on the direction:

int image_get_acceptable_y_movement(SDL_Surface *surface, int y,
int speed, unsigned int direction) {

int difference = 0;

if (direction & DIRECTION_UP) {

if (y - speed < 0) {
difference = -y;
} else {
difference = -speed;
}

} else if (direction & DIRECTION_DOWN) {

if (y + speed + surface->h > main_screen->h) {
difference = (main_screen->h - y) - surface->h;
} else {
difference = +speed;
}
}

return difference;
}

If we didn't call this function, and instead increased/decreased the Y coordinate by a constant value (like PADDLE_SPEED) depending upon whether we wanted to move down or up, respectively, the new coordinate could potentially cause the image to be drawn off of the screen. For example: If the paddle was at a Y coordinate of 1 and we wanted to move up PADDLE_SPEED (7) pixels, that would cause the paddle's new Y coordinate to be -6 -- not good. So difference is calculated as PADDLE_SPEED if the new Y value won't be off-screen, or the number of pixels left between the paddle and the edge, if it will go off-screen. We also factor in the screen's height, and the paddle's height, if we're moving downward, since it'll be the bottom of the paddle that touches the bottom of the screen, first (see the explanation above).

Moving the ball; checking for collision

Back to handle_keystate(), we check to see if the START_GAME_KEY (Space) key is pressed. If it is, and the ball isn't already moving, we call pong_ball_set_in_motion(), which simply sets the ball's moving variable to TRUE -- to let our pong ball moving code know it is indeed allowed to move the ball -- and also sets it in a random direction:

void pong_ball_set_in_motion(void) {

pongBall.moving = TRUE;
pongBall.direction = 0;

pongBall.direction |= (rand() % 2 == 0) ? DIRECTION_UP : DIRECTION_DOWN;
pongBall.direction |= (rand() % 2 == 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
}

Not the most brilliant logic, but a more realistic implementation can be left as an exercise to the reader. Once more to handle_keystate(): if the ball is supposed to be moving (as signified by the moving variable), then we call pong_ball_move(). This one's kinda big, and is the most complex in the game, so I'll split it up a bit:

void pong_ball_move(void) {

int difference =
image_get_acceptable_y_movement(pongBall.surface, pongBall.y,
BALL_SPEED, pongBall.direction);

pongBall.y += difference;

if (abs(difference) != BALL_SPEED) {
if (pongBall.direction & DIRECTION_UP) {
pongBall.direction &= ~DIRECTION_UP;
pongBall.direction |= DIRECTION_DOWN;
} else if (pongBall.direction & DIRECTION_DOWN) {
pongBall.direction &= ~DIRECTION_DOWN;
pongBall.direction |= DIRECTION_UP;
}
}

Here we first get an acceptable Y coordinate movement value by making a call to image_get_acceptable_y_movement(). Note that it can be negative or positive depending on the direction. Then we "add" this to the ball's Y coordinate, and then compare the absolute (positive) value of this amount with the speed of the ball (BALL_SPEED). If the values are not equal, then we know that the ball has hit the top or bottom of the wall (otherwise the image_get_acceptable_y_movement() function would've returned BALL_SPEED), and we therefore set the ball in an opposite Y direction, to simulate a "bounce". Continuing in the same function:

     if (pongBall.direction & DIRECTION_LEFT) {
pongBall.x -= BALL_SPEED;
} else if (pongBall.direction & DIRECTION_RIGHT) {
pongBall.x += BALL_SPEED;
}

pongBall.hit_paddle = FALSE;
pongBall.out_of_bounds = FALSE;

First we increment/decrement the ball's X coordinate, depending on the direction it's going in. Of course, this could've made the ball collide with one of the paddles, or pass one of the paddles above or below it without hitting it (which we consider a collision with the wall for simplicity), so we check for both conditions:

     if (pongBall.direction & DIRECTION_LEFT) {

if (pongBall.x < (lPaddle.x + lPaddle.surface->w)) {

if (pongBall.y + pongBall.surface->h < lPaddle.y ||
pongBall.y > lPaddle.y + lPaddle.surface->h) {

pongBall.out_of_bounds = TRUE;
} else {

pongBall.hit_paddle = TRUE;
pongBall.x = lPaddle.x + lPaddle.surface->w;
}
}
} else if (pongBall.direction & DIRECTION_RIGHT) {

if (pongBall.x + pongBall.surface->w > rPaddle.x) {

if (pongBall.y + pongBall.surface->h < rPaddle.y ||
pongBall.y > rPaddle.y + rPaddle.surface->h) {

pongBall.out_of_bounds = TRUE;
} else {
pongBall.hit_paddle = TRUE;
pongBall.x = rPaddle.x - pongBall.surface->w;
}
}
}
}

The calculation is somewhat self-explanatory. If the ball's moving left, we check first to see if the left-hand side of the ball (which is simply the X coordinate) has passed the X coordinate of the paddle, plus the width of the paddle (see above for an explanation) because if we're moving left, the ball will hit the right side of the left paddle.

If the ball's moving right, we first check to see if the right-hand side of the ball (the X coordinate, plus the width of the ball) has passed the left side (simply the X coordinate) of the right paddle.

If either case has occurred, we have a collision of some sort... was it with the paddle, or was it with the point just past the paddle, above or below it? Depending on the case, we either set the out_of_bounds variable to TRUE, or set the hit_paddle variable to TRUE and place the ball so it's touching the paddle exactly -- because earlier, when we moved the ball, we may have moved it too far.

Drawing the images on screen

We're now actually done with most of the logic. Back in our run_game_loop() function, now we actually draw the images we just "moved", by modifying their X/Y coordinates, with a call to redraw_all_images():

void redraw_all_images(void) {

clear_screen();

blit_surface(lPaddle.surface, main_screen, lPaddle.x, lPaddle.y);
blit_surface(rPaddle.surface, main_screen, rPaddle.x, rPaddle.y);
blit_surface(pongBall.surface, main_screen, pongBall.x, pongBall.y);
draw_scores(lPaddle.score, rPaddle.score);

SDL_UpdateRect(main_screen, 0, 0, 0, 0);
}

Finally, a simple function! First we clear the screen with a call to clear_screen():

void clear_screen(void) {

SDL_FillRect(main_screen, NULL, SDL_MapRGB(main_screen->format, 0, 0, 0));
}

SDL_FillRect() fills the specified surface (in our case, the screen) with the RGB color specified in the last argument. The second argument is NULL to specify we want to fill the entire surface with the color we specified. You can also pass a pointer to an SDL_Rect to specify only certain sections instead -- which might be a good idea for a game that is more CPU-intensive. Our blit_surface() function, which is the next function called by redraw_all_images(), and which I also explained in Part 1, also utilizes an SDL_Rect to blit (draw) the images to a specific location on the screen:

int blit_surface(SDL_Surface *src, SDL_Surface *dest, Sint16 x, Sint16 y) {

SDL_Rect offset;
offset.x = x;
offset.y = y;

return SDL_BlitSurface(src, NULL, dest, &offset);
}

Once again, this function simply draws the src surface onto the dest surface at coordinates x and y.

Drawing the scores

redraw_all_images() then calls draw_scores() to draw the score for each paddle:

void draw_scores(int left_score, int right_score) {

char lscore_text[10], rscore_text[10];
snprintf(lscore_text, sizeof(lscore_text), "%d", left_score);
snprintf(rscore_text, sizeof(rscore_text), "%d", right_score);

SDL_Surface *lscore_surface = render_text(lscore_text);
SDL_Surface *rscore_surface = render_text(rscore_text);

blit_surface(lscore_surface, main_screen,
(WIN_WIDTH / 2) / 2 - lscore_surface->w / 2, 0);

blit_surface(rscore_surface, main_screen,
((WIN_WIDTH/2)/2) + (WIN_WIDTH/2) -
(rscore_surface->w / 2), 0);

SDL_FreeSurface(lscore_surface);
SDL_FreeSurface(rscore_surface);
}

First we convert each score to a string. Then we call our convenience function render_text() to render the score text into a surface suitable for drawing on the screen, with the RGB color we want (I like 176, 219, 255):

SDL_Surface *render_text(const char *text) {

SDL_Color color;
color.r = 176; color.g = 219; color.b = 255;
return TTF_RenderText_Solid(score_font, text, color);
}

Rendering the score into a surface each time the score is updated probably isn't the most efficient method, but it's the simplest, and when running a benchmark, I didn't see any noticeable difference in CPU utilization with and without the code. Experienced game programmers, feel free to chastise me.

The rest of the draw_scores() function is pretty straightforward. We call blit_surface() with the proper coordinates for each paddle's score. Note the calculation we make for the X/Y coordinate of each score; we want the left paddle's score at the top of the center of the left-half of the screen, and we want the right paddle's score at the top of the center of the right-half of the screen. Of course, when we're done drawing the text, we free the surfaces with a call to SDL_FreeSurface().

Handling a collision

Back once more to the game loop, we call pong_ball_handle_collision(), to check and see if a collision occurred (our collision logic is in the pong_ball_move() function above). If it did, we handle it according to what the ball collided with:

void pong_ball_handle_collision(void) {

if (pongBall.hit_paddle == TRUE) {

pong_ball_move_in_opposite_direction();
pongBall.hit_paddle = FALSE;

} else if (pongBall.out_of_bounds == TRUE) {

if (pongBall.direction & DIRECTION_LEFT) {
rPaddle.score += 1;
} else if (pongBall.direction & DIRECTION_RIGHT) {
lPaddle.score += 1;
}

pong_ball_reset();
pongBall.out_of_bounds = FALSE;
}
}

If the ball collided with the paddle, we clear the hit_paddle variable and set the ball in the opposite direction with a call to pong_ball_move_in_opposite_direction():

void pong_ball_move_in_opposite_direction(void) {

unsigned int direction = (rand() % 2 == 0) ? DIRECTION_UP : DIRECTION_DOWN;

if (pongBall.direction & DIRECTION_LEFT) {
direction |= DIRECTION_RIGHT;
} else if (pongBall.direction & DIRECTION_RIGHT) {
direction |= DIRECTION_LEFT;
}

pongBall.direction = direction;
}

I'm not going to bother explaining what this does. This post is huge already! Back to the pong_ball_handle_collision() function, we check for an "out-of-bounds" collision. If it occurred, we increment the score of the player who paddled the ball to the player who failed to paddle it back. Then we call pong_ball_reset() to place the ball in exactly the middle of the screen at a random Y coordinate, and keep it from moving (until the user hits the Space key again):

void pong_ball_reset(void) {

pongBall.x = (WIN_WIDTH/2) - (pongBall.surface->w / 2);
pongBall.y = rand_between(0, WIN_HEIGHT - pongBall.surface->h);
pongBall.moving = FALSE;
}

Capping the Frame Rate

Recall that at the top of the main game loop, we made a call to SDL_GetTicks(), before we did any logic/drawing functions. Now we make another call to this function, set the result in another variable, and call cap_fps() with the tick count we got in the beginning and the tick count we got just now:

void cap_fps(Uint32 start_ticks, Uint32 end_ticks) {

int sleep_delay = (1000 / FRAMES_PER_SECOND) - (end_ticks-start_ticks);

if (sleep_delay > 0) {
SDL_Delay(sleep_delay);
}
}

Why bother capping the frame rate, you might ask? For one thing, you don't want your game eating 100% of the user's CPU when it doesn't have to. Secondly, if you don't cap the frame rate, the game loop will run as fast as each individual user's CPU will allow, which means on some CPUs, the ball and paddle will be moving crazy fast (because the game loop, and therefore the movement functions, will be executing much more often), and on other CPUs, the ball and paddle will be moving crazy slow. Capping the frame rate keeps the movement mostly uniform on all systems.

So how do you cap the frame rate? There are a few different methods. What I'm doing here is calculating the amount of time required to sleep (with SDL_Delay()) during each loop of the game loop in order to achieve a frame rate of FRAMES_PER_SECOND (which I have defined as 30). Then, I factor in the amount of time it took to execute all of the functions in the game loop -- end_ticks - start_ticks. If the result of this calculation is greater than zero, we sleep for that amount of time to achieve exactly FRAMES_PER_SECOND game loops (and therefore screen redraws) per second; otherwise, we don't sleep, because for some reason, the user's CPU can't handle this 2D game (time to upgrade the 486, likely), and we're actually running behind.

I've read that there are superior methods. If you know of one, feel free to share. This gets the job done in our case, though.

Finishing up

Our game code is complete, save for one important function, quit_the_game(), which is called whenever the QUIT_GAME_KEY (Escape) is pressed, an error occurs, or the 'X' button is pressed in the window:

void quit_the_game(void) {

SDL_FreeSurface(lPaddle.surface);
SDL_FreeSurface(pongBall.surface);

if (TTF_WasInit()) {
TTF_CloseFont(score_font);
TTF_Quit();
}

SDL_Quit();

exit(0);
}

We free all allocated data, close down the TTF system (if it was initialized), and then close down the SDL system with a call to SDL_Quit(). Note that we only free one of the paddle surfaces, since the left and right paddle shared the same surface data, and we don't call SDL_FreeSurface() on our video screen surface -- SDL_Quit() does that for us, and the SDL docs say we shouldn't free it ourselves, anyway.

We're done!

Cool! If you copied everything correctly (or just downloaded the code package), you've setup your compiler to link against the SDL/SDL_TTF header and library files, and you've got the SDL/SDL_TTF runtime libraries installed in a directory where your system can find them (a directory in your PATH variable, or the directory in which the compiled game executable is located), the code should compile cleanly and run perfectly, regardless of what operating system you're using. When you run it, it should look something like this.

This Pong clone is pretty simple. See if you can't add support for computer controlled paddles and make the ball movement/direction changing code more realistic. The best way to learn is to do. hahahah

Have fun writing games.

Posted by Patrick on April 22, 2009 at 2:05pm | 2 Comments
Tagged: , , , and

Getting Your Feet Wet in SDL, Part 1

A few weeks ago I started playing around with SDL again, after months of "neglect". I've been considering making a game for some time now, but have been busy working on version 2.00 of IcyScreen. Well, we released it a few days ago, so now seems like as good a time as any to start. I needed a refresher; working on this little introduction got 'er done for me, and hopefully it'll help you out as well.

What's SDL?

SDL is a free, open-source, portable game programming API that makes writing games for multiple platforms pretty darn straightforward. According to Wikipedia, SDL is used by Second Life, DOOM 3, Quake 4, UT2K4, DOSBox, and other applications, so it's definitely popular. Anyway, SDL deals with all of the low-level, hardware/OS-specific stuff so the only thing you have to worry about is your game code.

SDL is implemented in a fairly modular fashion. The core library contains most necessary gaming-related functions -- those dealing with audio, video, joysticks, timers, threads, etc -- and the rest is left to add-on library files. For example, the SDL_image library adds support for a heckuva lot more image formats, SDL_TTF adds font rendering support, SDL_net adds portable networking functions, etc. To utilize these add-on libraries all you have to do is download them, point your compiler to them, and call one of the functions in your code.

In this part I'll be explaining the basic SDL functions and datatypes you need to know in order to make a basic game. I think most of you would agree that the only true way to learn is to code, so in the next part I'll get more technical and post a walk-through of a Pong clone I wrote that will deal with everything I talked about today, plus a lot more (including some basic game programming concepts). My hope is that this will give you a decent understanding of how SDL works and will provide you with the knowledge necessary to start creating games right away.

Prerequisites

To compile an SDL program, you'll need to download the core SDL library for your platform, and point your compiler to the header and library files you downloaded. For the heck of it, you may as well fetch the SDL_TTF library, because we'll be dealing with it in the next part of this introduction.

Edit (4/26): If you're having some issues getting SDL set up, check out this excellent comment.

You'd probably do well to get a copy of the official API documentation as well. This article isn't meant to replace the documentation; it's meant to make the documentation much less necessary, and easier to swallow.

Also note that I'm using C syntax, but SDL has wrappers for many other languages, including C++.

Getting Started

Now that we're set up, lets talk code. The most important SDL function is SDL_Init():

     if (SDL_Init(SDL_INIT_VIDEO) != 0) {
/* Get error string with SDL_GetError(). */
}

You should always call this before any other SDL function in your program; it initializes the subsystems specified in the argument passed to it. For the sake of simplicity, just pass SDL_INIT_VIDEO to this function and forget about it. SDL_Quit() is the polar opposite of this function; it shuts down all initialized SDL subsystems and frees any resources allocated by them.

SDL_Init() only initializes SDL. Now we need a video screen to put all of our graphics in -- what's a game without graphics? We can create a screen by calling SDL_SetVideoMode(), which returns a pointer to an SDL_Surface:

     SDL_Surface *screen = SDL_SetVideoMode(256, 256, 24, SDL_SWSURFACE);
if (screen == NULL) {
/* Get error string with SDL_GetError(). */
}

What the heck is an SDL_Surface?

In SDL, areas of "graphical" memory are represented by the SDL_Surface structure. So if you wanted to load an image with SDL, or create a video screen, it would be loaded into an SDL_Surface structure by SDL. The structure itself contains some useful information pertaining to the surface, such as it's width/height, the raw pixel data, etc. It should be noted, that all SDL_Surfaces should be freed with a call to SDL_FreeSurface() when you're done with them (except for the surface returned by SDL_SetVideoMode(), which is freed with a call to SDL_Quit()).

Back to the SDL_SetVideoMode() function... after you call it with your desired window width, height, bits-per-pixel value, and any desired flags (I passed SDL_SWSURFACE, to tell SDL to create the video surface in system, instead of video, memory), you'll get a pointer to an SDL_Surface that represents the video screen. Consider it the sandbox in which all of your toys (graphics) are placed.

Here's where it starts getting cool. Want to load an image from a file? Easy! Just call SDL_LoadBMP() with the path to the image, and it'll be loaded into an SDL_Surface:

     SDL_Surface *image = SDL_LoadBMP("image.bmp");
if (image == NULL) {
/* Error. You know the drill. */
}

If you want to load images that are in formats other than .BMP, just download the SDL_Image library and call IMG_Load() instead.

To draw that image on screen, make a call to SDL_BlitSurface() with the image's surface (SDL_Surface), the screen's surface, and the X/Y coordinate you'd like the image to be placed at on the screen. Here's a little convenience function to do just that:

int blit_surface(SDL_Surface *src, SDL_Surface *dest, Sint16 x, Sint16 y) {

SDL_Rect offset;
offset.x = x;
offset.y = y;
offset.w = offset.h = 0;

return SDL_BlitSurface(src, NULL, dest, &offset);
}

src is the source surface (in our case, our image); dest is the destination surface -- the screen -- and x and y are of course the X/Y coordinates that the source should start being drawn at on the destination. Note that in SDL, as the X coordinate increases, it moves to the right; as the Y coordinate increases, it moves downward.

After you blit the image onto the screen, you've gotta tell SDL to update the screen so you can see the changes. A simple call to SDL_UpdateRect() will do this:

     blit_surface(image, screen, 25, 30);
SDL_UpdateRect(screen, 25, 30, image->w, image->h);

The above snippet blits the image onto the screen at X/Y coordinate 25, 30, and then tells SDL to update the part of the screen that has changed. To update the entire screen, you can pass the screen, and then all zeros, to the function.

Finishing up

So that's basically that, as far as the basic video screen-creating and graphic loading/drawing functions are concerned. As you can see, SDL doesn't have much of a learning curve, which I find pretty attractive. That certainly doesn't mean game programming is easy; it just means that you don't have to worry about having to deal with an API that's a pain in the butt to use.

What now?

You may be wondering: "How do I detect when a certain key is pressed, make graphics move around on the screen, detect a collision, create a game loop, cap the frame rate, etc?" Don't worry, I'll be covering /all/ of those issues, plus a lot of other stuff, in the next part of the introduction, which will probably be posted in no more than a couple of days.

EDIT (4/22/09): The second part of this article can be read here.

EDIT (5/12/09): Fixed a broken link.

Posted by Patrick on April 20, 2009 at 5:39pm | 6 Comments
Tagged: , , and

 1