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.

Even faster row-insertion in GTK+: Sort it yourself!

A little followup to my last blog post, I actually discovered that temporarily disabling sorting in your list or tree store before populating it:

     gtk_tree_sortable_get_sort_column_id(sortable, &sort_col, &sort_order);
gtk_tree_sortable_set_sort_column_id(sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
GTK_SORT_ASCENDING);

... and resetting the sort column after populating:

     gtk_tree_sortable_set_sort_column_id(sortable, sort_col, sort_order);

... is slightly faster than just calling gtk_list_store_insert_with_values()... about 8-10% faster by my calculation, actually. So it's definitely worth doing.

But still, even with this optimization, and the optimization I talked about in my last blog post, populating a sorted store with thousands of rows was just still way too slow IMO. Unacceptably slow.

So the other day I wondered if it would be faster to:

  • Turn off sorting permanently by setting the sort column ID to GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID.
  • Manually sort the data in memory before populating the store.
  • Insert the rows into the unsorted store in the order that the data was sorted in memory.

I figured it was worth a shot... it didn't take too long to implement at all, and I was happy to find I was right: This method is just barely slower than populating an unsorted store without sorting the abbreviations in memory first, and about 10x faster overall -- very, very noticeable -- than when letting GTK+ do the sorting for me. A *huge* speedup. Totally sweet.

Of course, it's slightly more inconvenient, and there's also small bit of work involved, to visually simulate what GTK+ does when you click a sortable column's button, but it's not difficult at all... it's basically a matter of manipulating the TreeViewColumn's arrow widget with gtk_widget_show() and gtk_arrow_set().

Another benefit to handling the sorting yourself: By default, GTK+ darkens the sort column if there are more than 3 sortable columns in a tree view, and there's not any way to avoid this yet (though I submitted a patch that created a style property that would let you disable this). Handling the sorting yourself means no darkened sort column.

I'm really happy about this major speedup, because I had a long list of abbreviations (over 13,500) for medical transcriptionists that I really wanted to include with Breevy, but before this optimization (<= 2.05), they took far too long to load in the main window, so I put off including them until I could find a way to speed things up.

But now, with this speedup included in version 2.06 (out now), they load in about half a second on a 6+ year old Pentium 4 @ 2.8GHz... so the medical transcription list is now included with Breevy. 8)

So to wrap this post up: If you're going to be inserting thousands of rows into a GtkTreeStore or GtkListStore, and need some sort of sorting, I would definitely recommend handling the sorting yourself instead of letting GTK+ do it. I love GTK+, but this is one area where I think GTK+ could use some improvement, performance wise. hahahah

Side note: GTK+'s default column sort function uses g_utf8_collate() when comparing string data. This function is pretty darn slow (it's the nature of the beast, apparently). If you don't need any of it's features, I would definitely implement your own sorting function for your sorted columns and use strcmp/strcasecmp instead. You'll get a pretty noticeable speedup from this optimization alone, though not nearly as much as if you were to handle all of the sorting yourself.

You might check out the g_utf8_collate_key() function if you still want to use g_utf8_collate().

Posted by Patrick on November 17, 2009 at 3:32pm | 0 Comments
Tagged: , and

Faster sorted-store row insertion in GTK+.

So I was adding a huge list of abbreviations (over 15K) to Breevy about a week ago and noticed that it took *way* too long for the GtkListStore to be populated. Way too long, as in over a minute on a 3GHz P4.

After a bit of investigating I finally figured out that the bottleneck was the fact that the list store was a sorted store, and that it appeared GTK+ was resorting the store after each row insertion. I turned off sorting, and populating the store was almost instant.

So first I worked around this issue by setting the sort column ID for the list store to GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID before populating. This disabled any sorting during the insertions. Then, after the insertions, I set the sort column ID back to what it was before, triggering a final sort of all of the inserted rows. I knew this was a bit hackish, but it worked.

But then I found out how to do it The Right Way: by calling the gtk_list_store_insert_with_values() function. Basically, you should use this instead of a gtk_list_store_append() and gtk_list_store_set() when inserting rows into a sorted store because these two latter functions basically tell the list store to resort itself after each new row is inserted (i.e., number of sorts == number of rows inserted)... something that's not necessary, and something that takes a heckuva long time.

The speedup I experienced using the insert_with_values() function was absolutely massive. Thanks to the folks on #gtk+ on irc.gnome.org for pointing the function out to me.

Posted by Patrick on November 10, 2009 at 4:03pm | 0 Comments
Tagged: and

Why does the .NET Framework have to be so big?

I recently had to upgrade to .NET 3.5 from version 2.0.

The download size was 60MB. That's huge -- and that's just the size of the upgrade. If I wanted to download the full version of .NET 3.5 the size would be nearly 200MB. Holy smokes!

After the download completed, the installation process took a little over 5 minutes... a relatively long period of time for a software installation.

I think that the .NET Framework is an excellent idea (though I think it'd be cooler if implemented as a natively-executing C/C++ library) and from what I hear and read it makes programming much easier for the developer, but why does it have to be so large, and why must the installation process take so long?

I think users still appreciate small downloads, fast installations, and quick application start times.

Posted by Patrick on August 4, 2009 at 9:28pm | 0 Comments
Tagged: and

"Dynamic" label wrapping in GTK

As it stands, the wrapping functionality of a GtkLabel isn't the best. You can call gtk_label_set_line_wrap() on your label, and it will wrap... but it'll wrap at a fixed width, and not dynamically. In other words, the label won't rewrap it's text as the parent container of the label (for example, the program window) changes size, as does text in a GtkTextView, or text in your typical text editor.

Here's an example of how GTK wraps a label normally:

GTK label wrapping without workaround

Here's an example of how the wrapping will look if you use the workaround I describe below:

GTK label wrapping with workaround

For the longest time I wondered why this was, and how to fix it, until I finally asked if there was a solution a while back on the #gtk IRC channel. I was provided with an example for a workaround, and thought I'd share it. It's hackish, for sure, and it's not perfect, but all you have to do is connect your GtkLabel (with line wrapping enabled, using gtk_label_set_line_wrap()) to a callback for the "size-allocate" signal:

     g_signal_connect(G_OBJECT(label), "size-allocate",
G_CALLBACK(wrapped_label_size_allocate_callback), NULL);

The callback should look like this:

void wrapped_label_size_allocate_callback(GtkWidget *label,
GtkAllocation *allocation,
gpointer data) {

gtk_widget_set_size_request(label, allocation->width, -1);
}

Apparently, (and according to the GTK+ docs) GtkLabels can't/don't detect when their parent has modified their size allocation. By connecting the label widget to the "size-allocate" signal, we can detect when this modification occurs, and then call gtk_widget_set_size_request() to tell GTK we want the label to utilize all of that allocated space.

There's one flaw with this method, and that is, you'll no longer be able to make your parent GtkWindow any smaller than the current width/height of the label, because the GtkLabel will be forcing it to keep a minimum width/height of however much space it requested with the gtk_widget_set_size_request() call. So you'll never be able to shrink the parent window, and if you make the window bigger, it'll stay at least that big, forever -- at least, until the window is recreated. Which isn't necessarily bad, especially if you've forbidden window resizing with a call to gtk_window_set_resizable().

A workaround for this issue is to utilize the gtk_window_set_policy() function, to unconditionally allow both window growing and shrinking:

     gtk_window_set_policy(GTK_WINDOW(win), TRUE, TRUE, FALSE);

Unfortunately, this brings up issues of it's own: it'll allow the user to make the window as small as he/she wants to (even 1x1), instead of restricting it to the minimum size necessary to show the entirety of all of the widgets in the window, as is the default GTK functionality. You can "fix" this by calling gtk_window_set_geometry_hints() with a minimum size for the window:

     GdkGeometry geom;
geom.min_width = 175;
geom.min_height = 50;

gtk_window_set_geometry_hints(GTK_WINDOW(win), GTK_WIDGET(win),
&geom, GDK_HINT_MIN_SIZE);

The only caveat is that you're forced to provide a hardcoded value.

A better solution?

In many situations, including in some of mine, these benefits of the workarounds outweigh the caveats. If there's anyone out there who knows of fix for the label wrapping issue that gives you the best of both worlds -- "dynamic" wrapping, as well as the ability to make the window shrink while still showing the entirety of all widgets, without forcing a minimum hardcoded value -- I'd love to hear your solution. I've had a few ideas, but all seem overly complex...

Edited on 4/25/09 to make the windows in both screenshots look a bit nicer.

Posted by Patrick on April 24, 2009 at 1:47pm | 11 Comments
Tagged: , , , and

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

 1 2 Next →