Originally Published: Monday, 25 June 2001 Author: Josh Boudreau
Published to: develop_articles_tutorials/Development Tutorials Page: 1/1 - [Std View]

Programming Linux Games: An Introduction to SDL Development

Welcome to Game Week on Linux.com! All this week we'll be featuring and playing games. The game week feature article by Linux.com writer Josh "bitwize" Boudreau is a succinct introduction to programming with the SDL (Simple DirectMedia Layer) library, essential to understanding Linux game development. So all you game programmers or would be game programmers: read up!

SDL (Simple DirectMedia Layer) is a cross-platform development library providing an API useful to game developers. It gives access to video, audio, timing functions, event handling and threads. SDL supports many platforms; Linux, Windows, MacOS, Solaris, IRIX and FreeBSD are all supported: this means that when your application uses the SDL library functions, they will work when you compile your application on a different supported platform. SDL is written in C and it supports C and C++ out of the box; bindings for Ada, Eifel, ML, Perl, PHP, Perl, Python and Ruby have also been created.

This article provides an introduction to a few of the features of the SDL library and will use the C programming language for the source code examples. I will assume the reader has basic knowledge of the C programming language and knows how to work with the GNU programming tools.

Installing The SDL Library

If your Linux distribution didn't come with SDL you can download the library from www.libsdl.org. SDL's web site has binary packages and also a source code distribution if a package for your distribution is not available. If you downloaded a binary package you can install it with your distribution's package manager tool (for example, RPM on RedHat). I'll give instructions on how to compile and install SDL from source code, in case they didn't provide a package for you. Be sure to download the development version. The runtime version of SDL is only used for running programs that were compiled with SDL. The development version contains the libraries used to compile and link your program but also contains the runtime libraries.

tar zxvf SDL-1.2.1.tar.gz
cd SDL-1.2.1
./configure --prefix=/usr
make
su
make install
exit

These commands will build and install SDL in /usr. The library will go in /usr/lib and the C header files will be put in /usr/include/SDL. You can change the install location by passing a different directory to the --prefix argument while running the configure script.

Compiling SDL Applications

SDL programs need to be linked with the SDL library and also pthread since the SDL library has thread support. Each application also needs to include the SDL.h header file. Here's a sample application that we will compile using SDL.

#include <stdio.h>
#include "SDL.h"

int main(int argc, char *argv[])
{
printf("hello world\n");
return 0;
}

While this application doesn't really use any SDL function, we still include the SDL.h header file and we will link it with the SDL library as an example. If you installed the SDL library you should have a script called sdl-config. This script is used when compiling applications to tell the build environment where the SDL library and C header are located. By running sdl-config with --libs and --cflags as arguments, you'll see that the script outputs gcc options telling us where the library and headers are and which library we should link to. To compile the example program I listed above you can use the following command.

gcc `sdl-config --cflags --libs` program.c

This will compile and link your applications with the appropriate libraries and give you an executable named a.out in the same directory. Take note that the sdl-config script is not enclosed between quotes, those are backsticks (The same key as the tilde). You should be able to run the application by typing ./a.out.

Initializing SDL

To use any of the SDL functions in your program you must first initialize the library with the SDL_Init() function. Depending on the features your application will use, you can decide to initialize all, or only certain subsystems of the SDL library. The SDL_Init() function takes a single argument which is a list of flags that can be bitwise or'd together to decide which subsystem to initialize. Here are the flags that can be passed to the SDL_Init function.

SDL_INIT_TIMER
SDL_INIT_AUDIO
SDL_INIT_VIDEO
SDL_INIT_CDROM
SDL_INIT_JOYSTICK
SDL_INIT_EVERYTHING
SDL_INIT_NOPARACHUTE
SDL_INIT_EVENTTHREAD

The names are pretty self-descriptive on what they initialize except for the SDL_INIT_NOPARACHUTE flag. When SDL_INIT_NOPARACHUTE is passed to SDL_Init(), SDL will provide a signal catcher, meaning that if your application segfaults SDL will clean up after itself. If we wanted to initialize SDL to use video and audio functions we could use the SDL_Init() function like this:

SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

As you can see, each flag can be bitwise or'd together. SDL defines SDL_INIT_EVERYTHING to initialize all the subsystems of the library. Another useful function you can use to initialize SDL subsystems is the SDL_InitSubSystem() function. This function can be used, for example, if your application needs video and audio but joystick support is optional. Here's an example code snippet.

int main(int argc, char *argv[])
{
if( SDL_Init(SDL_INIT_VIDEO | SDL_INIT_VIDEO) < 0)
{
fprintf(stderr, "Unable to initialize Video and Audio: %s\n", SDL_GetError());
return -1;
}

/*(you can call Video and Audio function here)*/

if(use_joystick = 1)
{
if(SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0)
{
fprintf(stderr, "Unable to initialize Joystick: %s\n", SDL_GetError());
return -1;
}
}

/*(Now Joystick functions are available)*/
return 0;
}

In the example above I also show how to use the SDL_GetError() function. This function works in a similar way the Unix errno system works. When an error occurs SDL_GetError() will get the last error that happened and previous errors are overwritten. SDL_GetError() returns a string describing the error.

Video

SDL provides many functions to use video and graphics features. Before jumping into video functions I will explain a common data type we will be using. Most video and graphic functions take a SDL_Surface structure. You could think of this data type as a drawable surface. The SDL_Surface structure holds information about our drawable surface like width, height and the actual data of the pixels used by our surface. SDL_Surface structures are used both to give access to the current screen and also provide storage for graphics.

To start using video functions you must first call SDL_SetVideoMode(). This function will create a window and return a pointer to the video surface. SDL_SetVideoMode()'s argument specify the width, height and bps of our video surface. This function also takes unsigned int flags to specify which type of video surface we want. SDL_SetVideoMode() is defined like this:

SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags);

Here are a few flags you can pass to the function, for more you should read the SDL_SetVideoMode man page by typing man SDL_SetVideoMode at your shell prompt.

SDL_SWSURFACE
SDL_HWSURFACE
SDL_DOUBLEBUF
SDL_FULLSCREEN
SDL_OPENGL

SDL_SWSURFACE will create a video surface in system memory while SDL_HWSURFACE will create one in video memory. SDL_DOUBLEBUF will create a double buffered window and SDL_FULLSCREEN will set the specified video resolution instead of creating a window of the same size. SDL_OPENGL is used when you need OpenGL rendering. Here's an example using the SDL_SetVideoMode() function.

SDL_Surface *screen;

screen = SDL_SetVideoMode(800, 600, 16, SDL_SWSURFACE | SDL_FULLSCREEN);
if(screen == NULL)
{
fprintf(stderr, "Unable to set video mode: %s\n", SDL_GetError());
return -1;
}

Sometimes when you access the video memory to change pixel values the video surface will need to be locked. SDL provides a macro to check if the video memory needs to be locked, or not. This macro is SDL_MUSTLOCK(SDL_Surface). Even if you know the video surface does not need locking, it's a good idea to check anyway by enclosing all the drawing functions between if statements checking if locking is needed. Here's a small example.

SDL_Surface *screen;

screen = SDL_SetVideoMode(800, 600, 16, SDL_SWSURFACE | SDL_FULLSCREEN);

if(SDL_MUSTLOCK(screen))
SDL_LockSurface(screen);

/*(You can write and access video memory here)*/

if(SDL_MUSTLOCK(screen))
SDL_UnlockSurface(screen);

Like I mentioned previously, graphics are also loaded into SDL_Surface structures. SDL support BMP graphics files and they can be loaded with the SDL_LoadBMP() function. SDL_LoadBMP() takes a single argument which is the filename that we would like to load. It then returns a pointer to a SDL_Surface structure which holds the image data. Once loaded, images can be written on the screen with the SDL_BlitSurface() function. SDL_BlitSurface is defined like this:

int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);

Here we see another data structure, SDL_Rect. This structure hold four integer values, x, y, w and h. This data structure is used to tell functions about x,y positions, width and height. SDL_BlitSurface takes two SDL_Rect other than the source and destination surfaces and are used to tell which part of the image we want to blit and also where on the screen we would like the image to be positioned. If the SDL_Rect structures in the SDL_BlitSurface function are NULL, the function assumes we want to blit the whole surface at 0,0. Here's an example demonstrating the use the SDL_LoadBMP and SDL_BlitSurface.

SDL_Surface *screen;
SDL_Surface *image;

screen = SDL_SetVideoMode(800, 600, 16, SDL_SWSURFACE);
if(screen == NULL)
{
fprintf(stderr, "Unable to set video mode: %s\n", SDL_GetError());
return 0;
}

image = SDL_LoadBMP("graphic.bmp");
if(image == NULL)
{
fprintf(stderr, "Unable to load graphic file: %s\n", SDL_GerError());
return 0;
}

while(1)
{
if(SDL_MUSTLOCK(screen))
SDL_LockSurface(screen);

SDL_BlitSurface(image, NULL, screen, &rect);

if(SDL_MUSTLOCK(screen))
SDL_UnlockSurface(screen);

SDL_UpdateRect(screen, 0,0,0,0);
}

In this example we set up our video mode and load a graphic file. Once loaded the program goes into the never-ending loop and displays our graphic on the screen. The new function SDL_UpdateRect() is used to update the part of the screen we blitted the image on. I pass all zeros to the function to update the whole screen. If you want to only update part of the screen you can pass x, y, w, h values. If you would have specified SDL_DOUBLEBUF as a video flag, you would use SDL_Flip(screen) which would flip the buffers and display the graphics.

Event Handling

One of the best features of SDL is its ability to handle user input and events. SDL handles events by sending and receiving event messages through a queue. When your application receives a new event it is placed at the end of the queue. One of the data types we will see is the SDL_Event structure. This structure holds information on the type of event and its values.

To read new events from the queue we will be using the SDL_PollEvent() function, which takes the first event from the queue and stores it into a SDL_Event structure. It's also possible to send events with the SDL_PushEvent function, which lets us define custom events which we can send to the event queue. Most application use a switch statement to perform different functions depending on which type of event they have received. Here's an example of event handling using the SDL_PollEvent() function.

SDL_Event event;
SDL_Event tmp_event;

while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_KEYDOWN:
switch(event.key.keysym.sym)
{
case SDLK_ESCAPE:
printf("User Hit ESC, quitting...\n");
tmp_event.type = SDL_QUIT;
SDL_PushEvent(&tmp_event); break;

case SDLK_RIGHT:
move_player(right);
break;

case SDLK_LEFT:
move_player(left);
break;

default:
printf("User hit an unhandled key.\n");
break;
}
break;

case SDL_QUIT:
exit(0);
break;

default:
break;
}

In the above example, the code enters a while loop and doesn't return until we have read and processed every event in the queue. When an event is read we do a switch on its type and perform different operations depending on the value. I also showed an example of using SDL_PushEvent() when the user hits the escape key. When the escape key is hit, the program fills in tmp_event with an SDL_QUIT message and then sends it to the queue. When our program reads this SDL_QUIT message it will exit.

Time Synchronization

The timer subsystem of SDL can be used to synchronize your game at a constant speed or frame rate. SDL provides a timer in millisecond resolution. SDL_GetTicks() is used to get the number of milliseconds since the application has started and SDL_Delay() is used to pause for the specified time in milliseconds. Implementing synchronization is quite easy and here's an example showing how it works.

#define INTERVAL 30

unsigned int time_left(void)
{
static unsigned int next_time = 0;
unsigned int now;

now = SDL_GetTicks();
if(next_time <= now)
{
next_time = now+INTERVAL;
return 0;
}
return next_time-now;
}

while(game_is_running)
{
DoGameStuff();
SDL_Delay(time_left());
}

The synchronization is done in a way similar to a countdown timer, where the time_left() function calculates the time it took our program to process the game loop and if there's time left it pauses for that amount of time. If we we're to do only SDL_Delay() with a fixed amount of time the game loop might take longer on some iterations and it would not be synchronized to a constant speed.

Conclusion

This article only scratched the surface of a few features of SDL and if you are serious about developing applications with SDL you should read the API documentation at www.libsdl.org which describes all the functions. To get information on a specific function you can also read the man pages by typing man functionname at your shell prompt.

SDL Web Site: www.libsdl.org