SDL Tutorials

Chapter 8 – Working with SDL Surfaces

SDL_Surfaces are the foundation of the SDL Graphics API. In this chapter I will introduce some miscellaneous functions used to process and optimize SDL_Surfaces.

Lets start with processing SDL_Surfaces. First comes color-keys. What are color-keys? Well, let’s say you have a picture like this:

Notice it has a white back-ground. Now we blit it onto the screen in an SDL Application:

    /* Loading the images */
    SDL_Surface* mario = NULL;
    mario = SDL_LoadBMP("mario.bmp");

    SDL_Rect rect_mario = { 100, 100 };
    SDL_BlitSurface(mario, NULL, screen, &rect_mario);

Here we run into a problem. How do we remove the white background? One way to do this is to use image formats like *.PNG that have an alpha channel (I’ll cover that later). The second way is to use color-keys. The RGB Value for white is {255, 255, 255}. So if we set the color-key of the surface to white, all the white pixels will be treated as transparent. Here’s the function you use to set the color key of a surface:

int SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key);

The surface parameter is obvious. The flag parameter is usually SDL_SRCCOLORKEY, you can also clear any color-key you have set previously by passing 0 as the flag. The last value is a Uint32 representation of the RGB color, use the SDL_MapRGB() function to get this. Now let’s set a color-key to our previous image:

    Uint32 white = SDL_MapRGB(mario->format, 255, 255, 255);
    SDL_SetColorKey(mario, SDL_SRCCOLORKEY, white);

And voila! No more white background.

As for alpha blending. It is a kind of mixture (blend) of the surface and alpha pixels to produce a transparency effect. Here is the function you use:

int SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha);

The surface parameter is (obviously) the surface you want to manipulate. The flag is usually SDL_SRCALPHA (for more values, see the docs). The final parameter, alpha is for setting how much transparent you want the surface to be. 0 (SDL_ALPHA_TRANSPARENT) represents a completely transparent surface, while 255 (SDL_ALPHA_OPAQUE) represents a completely opaque surface. A number between these two gives a varying degrees of partial transparency. (The batteries of my wireless keyboard have just run out so there may be some typos).

Adding the following line to our example program:

    SDL_SetAlpha(mario, SDL_SRCALPHA, 100);

We make the image partially transparent.

Now let’s discuss optimizing an SDL_Surface. To speed up the process of editing and blitting SDL_Surfaces, there are some functions.

SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface);
SDL_Surface *SDL_DisplayFormatAlpha(SDL_Surface *surface);

The first function converts a given surface to the display format (i.e the format of the video surface). This makes way for faster blitting on the screen. Note that SDL_DisplayFormat returns a new surface and the previous surface should be deleted with SDL_FreeSurface() if you no longer intend to use it. Something like:

SDL_Surface* opt_surf = SDL_DisplayFormat(surf);
SDL_FreeSurface(surf);

SDL_DisplayFormatAlpha() does essentially the same thing as the former function. In addition it adds an alpha channel to the surface. For both functions color-keys and alpha values should be set before you call the functions.

This tutorial was pretty boring ¬_¬. But at least now, since I’ve covered this, we can move onto more interesting topics. Perhaps even create some fun games!

Advertisements

Chapter 7 – Images in SDL

Images are a crucial part of modern games. They are used for sprites, backgrounds, effects, tiles, intros and a lot of other stuff. Below is a screen-shot from SNK’s “The King of Fighters 2002” (Kyo is about to hit Iori with his Orochi Nagi).

In SDL, images are loaded as SDL_Surfaces in order to make it easier to use them in graphics. The SDL library itself only supports loading Bitmap (*.BMP) images. These images are loaded directly from a path in the file-system. Images are loaded using the SDL_LoadBMP function.

SDL_Surface *SDL_LoadBMP(const char *file);

This function loads a Windows Bitmap from file, converts it to an SDL_Surface and returns a pointer to the SDL_Surface. It returns a NULL on failure. Sample usage:

SDL_Surface* img = NULL;

img = SDL_LoadBMP("my_image.bmp");
if (!img) { /* handle errors */ }

Let’s create a sample program that loads some images.

#include <SDL/SDL.h>

int main(int argc, char* argv[])
{
    ...

    /* Loading the images */
    SDL_Surface* img_kyo = NULL;
    SDL_Surface* img_iori = NULL;

    img_kyo = SDL_LoadBMP("kyo.bmp");
    if (!img_kyo) fprintf(stderr, SDL_GetError());

    img_iori = SDL_LoadBMP("iori.bmp");
    if (!img_iori) fprintf(stderr, SDL_GetError());

    /* Blitting the images on the screen */
    SDL_Rect rect_kyo = { 10, 10 };
    SDL_BlitSurface(img_kyo, NULL, screen, &rect_kyo);

    SDL_Rect rect_iori = { 300, 10 };
    SDL_BlitSurface(img_iori, NULL, screen, &rect_iori);

    /* Flipping the screen to display the images */
    SDL_Flip(screen);

    ...

    /* While the program is running */
    while (!quit)
    {
        /* Check for new events */
        while(SDL_PollEvent(&event))
        {
            /* If a quit event has been sent */
            if (event.type == SDL_QUIT)
            {
                /* Quit the application */
                quit = 1;
            }
        }
    }

    /* Always free the surfaces you create */
    SDL_FreeSurface(img_kyo);
    SDL_FreeSurface(img_iori);

    ...

    return 0;
}

Here’s a screenshot of the application:

Now to explain the code.

    /* Loading the images */
    SDL_Surface* img_kyo = NULL;
    SDL_Surface* img_iori = NULL;

    img_kyo = SDL_LoadBMP("kyo.bmp");
    if (!img_kyo) fprintf(stderr, SDL_GetError());

    img_iori = SDL_LoadBMP("iori.bmp");
    if (!img_iori) fprintf(stderr, SDL_GetError());

Here we load the two bitmap images, kyo.bmp and iori.bmp, which are located in the same directory as the application. As I said before, the SDL_LoadBMP() function returns a NULL on failure, so I check for a NULL value after loading each image. If everything worked fine, the images have now been loaded and converted to SDL_Surfaces. That means we can use all the graphics functions on them that we use on normal surfaces.

    /* Blitting the images on the screen */
    SDL_Rect rect_kyo = { 10, 10 };
    SDL_BlitSurface(img_kyo, NULL, screen, &amp;rect_kyo);

    SDL_Rect rect_iori = { 300, 10 };
    SDL_BlitSurface(img_iori, NULL, screen, &amp;rect_iori);

Now that we have loaded the images successfully, we copy them onto the video surface. By the way, blit is actually shot from Bit Blit, which stands for Bit BLock Image Transfer. Anyway, I blit img_kyo, at (10, 10) and img_iori at (300, 10) so that they are displayed right next to each other.

    /* Flipping the screen to display the images */
    SDL_Flip(screen);

When we copied the images in the last section, they were actually copied to the back-buffer. To make them visible onto the screen, we have to copy (or replace) the back-buffer onto the front-buffer. This is done internally by SDL_Flip().

    /* Always free the surfaces you create */
    SDL_FreeSurface(img_kyo);
    SDL_FreeSurface(img_iori);

As I told earlier, SDL_Surfaces are reference counted. So you should always free a surface you created. Since we created img_kyo and img_iori, we now have to free them.

Chapter 6 – Mouse Input in SDL

I intended to teach this before the graphics chapter but I figured it would be difficult to understand the sample program which will be written in this one without having some knowledge of how graphics work in SDL. Okay, then. Let’s get on with it. Mouse input is used in a lot of games ranging from those waste-of-time point & click flash games that I hate so much (but sometimes play when I’m bored), to main-stream First-Person Shooters, such as Counter-Strike.

In SDL, receiving and handling mouse input is pretty similar to receiving keyboard input. However unlike the keyboard, There are two events for the mouse. The SDL_MouseMotionEvent and the SDL_MouseButtonEvent. This is because the mouse sends to kinds of messages. Firstly, when you drag the mouse (or touch-pad, whatever you use), it sends a mouse motion message along with it’s position. Secondly, when you press a button on the mouse, it send a mouse button message.

First, let’s discuss SDL_MouseButtonEvent. The SDL_MouseButtonEvent has two event types, SDL_MOUSEBUTTONDOWN and SDL_MOUSEBUTTONUP, which are sent on a mouse-button press and mouse-button release respectively.

Here’s a sample program that exits when the left mouse-button is pressed.


#include <SDL/SDL.h>

int main(int argc, char* argv[])
{
    ...

    /* An SDL_Event struct */
    SDL_Event event;

    /* A bool to check if the program has exited */
    int quit = 0;

    /* While the program is running */
    while (!quit)
    {
        /* Check for new events */
        while(SDL_PollEvent(&event))
        {
            /* If a quit event has been sent */
            if (event.type == SDL_QUIT)
            {
                /* Quit the application */
                quit = 1;
            }
            
            /* If a button on the mouse is pressed. */
            if (event.type == SDL_MOUSEBUTTONDOWN)
            {
                /* If the left button was pressed. */
                if (event.button.button == SDL_BUTTON_LEFT)
                    /* Quit the application */
                    quit = 1;
            }
        }
    }
    ...
}

Now, to explain the code:

            /* If a button on the mouse is pressed. */
            if (event.type == SDL_MOUSEBUTTONDOWN)

As I explained earlier, the event type SDL_MOUSEBUTTONDOWN is sent when a mouse button is pressed.

                /* If the left button was pressed. */
                if (event.button.button == SDL_BUTTON_LEFT)

event.button is an SDL_MouseButtonEvent. To understand this, we need to take a closer look at the SDL_MouseButtonEvent structure.

typedef struct{
  Uint8 type;
  Uint8 button;
  Uint8 state;
  Uint16 x, y;
} SDL_MouseButtonEvent;

The type member is the event type, which is either an SDL_MOUSEBUTTONUP, or an SDL_MOUSEBUTTONDOWN. The button member is the mouse button which was pressed (SDL_BUTTON_LEFT, SDL_BUTTON_MIDDLE or SDL_BUTTON_RIGHT). The state is either SDL_PRESSED, on a button-press, or SDL_RELEASED, on a button-release. The x and y members are the x and y co-ordinates of the mouse cursor on the screen.

Now, in the code, we checked whether button equalled SDL_BUTTON_LEFT, i.e whether the left mouse button was pressed.

                    /* Quit the application */
                    quit = 1;

If the left mouse button is pressed, quit the program.

Now, let’s look at SDL_MouseMotionEvent. It has one event type, SDL_MOUSEMOTION. It is sent when the mouse is moved. To explain it better, here’s a sample program. However the program has a lot of bright flashing colours. I’ve heard these things are dangerous for photo-sensitive people . I sure hope this doesn’t cause someone an epileptic seizure or something. I should better write some kind of license above the source code that tells people to use it “at their own risk”, before someone sues me or something.

/**
Warning: Program not suitable for photo-sensitive people (or people with epilepsy).

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY  WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

In simple language, if this program harms you in any way whatsoever, it's not my fault!
**/

#include <SDL/SDL.h>

int main(int argc, char* argv[])
{
    ...

    /* An SDL_Event struct */
    SDL_Event event;

    /* A bool to check if the program has exited */
    int quit = 0;

    /* While the program is running */
    while (!quit)
    {
        /* Check for new events */
        while(SDL_PollEvent(&event))
        {
            /* If a quit event has been sent */
            if (event.type == SDL_QUIT)
            {
                /* Quit the application */
                quit = 1;
            }

            /* If the mouse is moving */
            if (event.type == SDL_MOUSEMOTION)
            {
                SDL_PixelFormat* fmt = screen->format;
                /* If the mouse is moving to the left */
                if (event.motion.xrel < 0)
                    SDL_FillRect(screen, NULL, SDL_MapRGB(fmt, 255, 0, 0));
                /* If the mouse is moving to the right */
                else if (event.motion.xrel > 0)
                    SDL_FillRect(screen, NULL, SDL_MapRGB(fmt, 0, 255, 0));
                /* If the mouse is moving up */
                else if (event.motion.yrel < 0)
                    SDL_FillRect(screen, NULL, SDL_MapRGB(fmt, 0, 0, 255));
                /* If the mouse is moving down */
                else if (event.motion.yrel > 0)
                    SDL_FillRect(screen, NULL, SDL_MapRGB(fmt, 0, 255, 255));
            }
        }

        SDL_Flip(screen);
    }
    ...
}

The explanations…

            /* If the mouse is moving */
            if (event.type == SDL_MOUSEMOTION)

This is the event type of SDL_MouseMotionEvent and is sent when the mouse moves. Let’s take a closer look at the SDL_MouseMotionEvent:

typedef struct{
  Uint8 type;
  Uint8 state;
  Uint16 x, y;
  Sint16 xrel, yrel;
} SOL;

Let’s get through this quickly. First is the type member, which (you guessed it!) is the event type, in this case SDL_MOUSEMOTION. The second member, state is the mouse-button state (more on that later). The x and y members signify the position of the mouse cursor on the screen. The xrel and yrel members represent the relative motion of the mouse. xrel is a positive number if the mouse is moving towards the right and negative if it is moving towards the left. Similarly, yrel is a positive number if the mouse is moving down and a negative number if it is moving up.

                SDL_PixelFormat* fmt = screen->format;
                /* If the mouse is moving to the left */
                if (event.motion.xrel < 0)
                    SDL_FillRect(screen, NULL, SDL_MapRGB(fmt, 255, 0, 0));
                /* If the mouse is moving to the right */
                else if (event.motion.xrel > 0)
                    SDL_FillRect(screen, NULL, SDL_MapRGB(fmt, 0, 255, 0));
                /* If the mouse is moving up */
                else if (event.motion.yrel < 0)
                    SDL_FillRect(screen, NULL, SDL_MapRGB(fmt, 0, 0, 255));
                /* If the mouse is moving down */
                else if (event.motion.yrel > 0)
                    SDL_FillRect(screen, NULL, SDL_MapRGB(fmt, 0, 255, 255));

Not much to understand here. As I explained earlier, xrel and yrel can be used to find out the direction of the mouse motion. I fill the screen with a different colour for every direction the mouse moves. If you move the mouse up, the screen will become red. If you move it down, it will become green. If you move it left, it will become blue. And if you move it down it will become a greenish-blue colour, the name of which I do not know.

Another long chapter. I wonder why my posts seem to be getting longer and longer. Perhaps it’s because I’m writing in more detail, discussing more stuff, or just yapping more than usual.

Chapter 5 – Graphics in SDL

Now for the fun part! A game really can’t be fun without good graphics. Although text-based games are (to some extent) playable, it takes graphics to really spice a game up. The picture below shows a scene from the game “Cave Story” for the PC (I have heard they ported it to Wii and DS too).

The SDL graphics interface is based on SDL_Surfaces. I described SDL_Surface in this chapter. The screen is also treated as a surface in SDL. Therefore, you can draw directly on the screen (the video surface) just like you’ll draw on any other SDL_Surface. A pointer to the video surface is returned by the SDL_SetVideoMode() function. You can also get it later by calling SDL_GetVideoSurface(). Here are some of the more common graphics functions.

int SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color);

You can fill a given rectangle on the surface (dstrect), or even the entire surface (if you pass dstrect as NULL) with a solid color using this function. It returns 0 on success and -1 on failure. Use SDL_MapRGB() or SDL_MapRGBA() to convert an rgb or rgba value to a Uint32 in the last parameter. Here’s a sample use:

/* fill a surface with red */
if (SDL_FillRect(surf, NULL, SDL_MapRGB(255, 0, 0)) < 0)
{
    /* handle error */
}

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

This copies the contents of one surface (src) onto another (dst). srcrect is a rectangle on the source surface that you want to copy (use NULL to copy the entire surface). The dstrect is the position on the destination surface where the source surface should be copied. It returns 0 on success and -1 on failure. Sample usage:

SDL_Rect srcrect = { 20, 30, 40, 40 };
SDL_Rect dstrect = { 100, 100 };

int rtn = SDL_BlitSurface(src, &srcrect, dst, &dstrect);
if (rtn < 0) { /* handle error */ }

SDL_Surface *SDL_CreateRGBSurface(Uint32 flags, int width, int height, int depth, Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask);

Creates a new RGB Surface. To see what flags to use, see here. You usually use the video surface’s Rmask, Gmask, Bmask and Amask to create the new surface. The function returns a pointer to the newly created SDL_Surface or a NULL on failure. Note that you should always free a surface after creating it. More about that later. The width and height is (obviously) the width and height of the surface to be created. Sample usage:

SDL_Surface* screen = SDL_GetVideoSurface();
SDL_PixelFormat* f = screen->format;

SDL_Surface* surf = SDL_CreateRGBSurface(SDL_HWSURFACE, 300, 200, f->BitsPerPixel,
                         f->Rmask, f->Gmask, f->Bmask, f->Amask);

if (!surf) { /* handle errors */ }
void SDL_FreeSurface(SDL_Surface *surface);

This frees up the resources and memory taken up by a previously created surface. Since SDL_Surfaces are reference counted, you don’t have to worry about accidentally freeing a surface in use somewhere else. If it’s in use, it won’t be freed. However take care to only call this function once for every surface (repeated calls may mess up the reference counting system). I really doubt you need to see the sample usage for this one, it’s clear as crystal.

int SDL_Flip(SDL_Surface* screen);

On a double-buffered display this function copies the back-buffer onto the front buffer, or replaces the back buffer with the front buffer if page flipping works (Perhaps that is why it’s called “Flip”?). See this chapter for a discussion on double-buffering. You should call this for updating the screen after drawing on it. Your drawing won’t be visible till you flip the screen.

Here’s a sample program to illustrate the above functions. Irrelevant code is replaced with “…”. Also note that I have skipped all error checks.

#include <SDL/SDL.h>

int main(int argc, char* argv[])
{
    ...

    /* A pointer to the video surface */
    SDL_Surface* screen = NULL;

    /* Create a double buffered window 640 pixels wide and 480 pixels long */
    screen = SDL_SetVideoMode(640, 480, 16, SDL_HWSURFACE|SDL_DOUBLEBUF);

    ...

    /* Create a 2D 3 X 3 array of surfaces */
    SDL_Surface* boxes[3][3];

    register int x;
    register int y;

    for (x = 0; x < 3; x++)
    {
        for (y = 0; y < 3; y++)
        {
            /* Create each surface */
            SDL_PixelFormat* format = screen->format;
            boxes[x][y] = SDL_CreateRGBSurface(SDL_HWSURFACE, 50, 50,
                                    format->BitsPerPixel,
                                    format->Rmask, format->Gmask, format->Bmask,
                                    format->Amask);

            /* Fill each surface with a random color */
            Uint32 color = SDL_MapRGB(screen->format,
                                      rand() % 255, rand() % 255, rand() % 255);

            SDL_FillRect(boxes[x][y], NULL, color);

            /* Blit each surface on the screen */
            SDL_Rect pos = { x * 50, y * 50 };
            SDL_BlitSurface(boxes[x][y], NULL, screen, &pos);
        }
    }

    /* Flip the screen */
    SDL_Flip(screen);

    /* While the program is running */
    while (!quit)
    {
        /* Check for new events */
        while(SDL_PollEvent(&event))
        {
             ...
        }

        ...
    }
    
    /* Destroy all surfaces in the array */
    for (x = 0; x < 3; x++)
        for (y = 0; y < 3; y++)
            SDL_FreeSurface(boxes[x][y]);

    /* Destroy the video surface */
    SDL_FreeSurface(screen);

    return 0;
}

Now, what does the code do?

    /* Create a 2D 3 X 3 array of surfaces */
    SDL_Surface* boxes[3][3];

This creates a 2D Array with dimensions 3 X 3. I could have created a normal array with a capacity of 9, but 2D arrays are simpler for this kind of stuff. This array is to hold the SDL_Surfaces we are about to draw.

    for (x = 0; x < 3; x++)
    {
        for (y = 0; y < 3; y++)
        {

We loop through each row with the x for-loop, and each column with the y for-loop.

            /* Create each surface */
            SDL_PixelFormat* format = screen->format;
            boxes[x][y] = SDL_CreateRGBSurface(SDL_HWSURFACE, 50, 50,
                                    format->BitsPerPixel,
                                    format->Rmask, format->Gmask, format->Bmask,
                                    format->Amask);

This creates each surface with a width of 50 and height of 50.

            /* Fill each surface with a random color */
            Uint32 color = SDL_MapRGB(screen->format,
                                      rand() % 255, rand() % 255, rand() % 255);

            SDL_FillRect(boxes[x][y], NULL, color);

Fills each surface with a random color. Use srand(time(0)) to get new colors on every run. Not much more to say here.

            /* Blit each surface on the screen */
            SDL_Rect pos = { x * 50, y * 50 };
            SDL_BlitSurface(boxes[x][y], NULL, screen, &pos);

Draws the surface according to it’s x and y value.

    /* Flip the screen */
    SDL_Flip(screen);

Copies the back buffer onto the front after we drew the surfaces on it.

    /* Destroy all surfaces in the array */
    for (x = 0; x < 3; x++)
        for (y = 0; y < 3; y++)
            SDL_FreeSurface(boxes[x][y]);

Frees all the surfaces we created. Whew! This was one tiring tutorial. Let me get some rest before writing the next chapter. I’ll try to make this one more detailed too.

Chapter 4 – Keyboard Input in SDL

You can’t really make a game without some kind of input. Whether it’s just the plain old keyboard or 3 button joypad like in the 70’s and 80’s. Or it’s something like the DualShock (R) and DualShock (R) 2 from the 90’s and early 2000’s. Or it’s those awesome motion controllers and XBox Kinect (TM).

A game always requires input of some kind or other. So, let’s start from the good ol’ keyboard. One way to detect keyboard presses and releases is to track the event loop’s SDL_KEYUP and SDL_KEYDOWN event types. SDL_KEYDOWN is sent when a key is pressed. Similarly, SDL_KEYUP is sent when a key is released. Then you can use the SDL_KeyboardEvent in the SDL_Event union to check which key was pressed or released.

Let’s take a closer look at the SDL_KeyboardEvent.

typedef struct{
  Uint8 type;
  Uint8 state;
  SDL_keysym keysym;
} SDL_KeyboardEvent;

Firstly, like most events, it contains type. It also contains state which is for knowing whether it was a key press, or a key release (it has two values, SDL_PRESSED and SDL_RELEASED). The last member, keysym is the one that is used to find out which key was pressed or released. Looking at the SDL_keysym structure:

typedef struct{
  Uint8 scancode;
  SDLKey sym;
  SDLMod mod;
  Uint16 unicode;
} SDL_keysym;

We see that there are four members. First there comes the scancode. It shouldn’t really be used for cross-platform games. Why? Because it’s platform-specific. That means it is different on different platforms. Even if you use a single platform, it may not be the same on different keyboards. For example, The scan-codes for USB keyboards are different from those of PS/2 Keyboards. Let’s say you create a game which runs perfectly on your Dell PS/2 Keyboard. Now, you decide to share it with your buddy, who has a laptop with a built-in keyboard, or a USB keyboard, or even a slightly newer keyboard model. The game won’t work!

Second is sym, which is a virtual key code (like the VK_* codes in the Windows API). Now a virtual key-code is a kind of platform-independent wrapper over the hardware specific key-codes. No matter what platform or keyboard you use, the virtual key-code will remain the same. Virtual key-codes should be used for cross-platform development. In fact, if you’re developing games, you should almost always use these. Here is a complete list of SDL Virtual key-codes.

Third is mod, this checks whether modifier keys such as SHIFT or CTRL are pressed currently. The last member, unicode, is a unicode representation of the character.

Let’s add a key-press handler in our sample program.

#include <SDL/SDL.h>

void handle_keys(SDL_Event* event, int* quit)
{
    if (event->type == SDL_KEYDOWN)
    {
        if (event->key.keysym.sym == SDLK_ESCAPE)
        {
            *quit = 1;
        }
    }
}

int main(int argc, char* argv[])
{
    /* skipped all irrelevant code */
    ...

    /* An SDL_Event struct */
    SDL_Event event;

    /* A bool to check if the program has exited */
    int quit = 0;

    /* While the program is running */
    while (!quit)
    {
        /* Check for new events */
        while(SDL_PollEvent(&event))
        {
            /* If a quit event has been sent */
            if (event.type == SDL_QUIT)
            {
                /* Quit the application */
                quit = 1;
            }

            handle_keys(&event, &quit);
        }
    }

    ...
}

Let’s explain the code now.

    if (event->type == SDL_KEYDOWN)

Check if the event it is a keyboard event. I discussed the relation between events and their types in the last chapter. If the event type is SDL_KEYDOWN, we can deduce that it is a keyboard event and that is is a key press (if the type was SDL_KEYUP, it would have been a key release). Okay, so we now know that a key on the keyboard has been pressed. So, which key? For that we have:

        if (event->key.keysym.sym == SDLK_ESCAPE)

As I explained earlier, we check the virtual key-code which is stored in the SDL_KeyboardEvent‘s keysym member to check which key has been pressed. Here I compare it with SDLK_ESCAPE. If this evaluates to true, then that means the key pressed was the “Escape” key, usually found on the top-left corner of the keyboard.

*quit = 1;

Not much to explain here, we quit the application when the escape key is pressed. Setting the value of quit to true ends the game loop and the program exits.

            handle_keys(&event, &quit);

We have to regularly call our key handler function in the event loop for it to work.

Although the above method is pretty commonly used for handling keyboard input, it is dependent on the event loop. This may present some difficulties. A non event-loop dependent way is to use the SDL_GetKeyState() function. The advantage here is that SDL_GetKeyState() can be used anywhere in your program, not just inside the event loop. Let’s rewrite the program to use SDL_GetKeyState() instead.


#include <SDL/SDL.h>

int main(int argc, char* argv[])
{
    ...

    Uint8* keys = NULL;

    /* While the program is running */
    while (!quit)
    {
        /* Check for new events */
        while(SDL_PollEvent(&event))
        {
            ...
        }

        keys = SDL_GetKeyState(NULL);
        if (keys[SDLK_ESCAPE]) quit = 1;
    }
  
    ...
    return 0;
}

To explain the above code…

       keys = SDL_GetKeyState(NULL);

SDL_GetKeyState() actually returns a pointer to an array which is indexed by all SDL virtual key-code values. The value at the index of every key-code is either a 0 (false), if it is not pressed or a 1 (true) if it is pressed. So, the best way to check whether a certain key is pressed is to check if the value of the array at that key-code’s index evaluates to true. Which we do in the next line:

        if (keys[SDLK_ESCAPE]) quit = 1;

If it evaluates to true (which means that the escape key is pressed), we set the value of quit to true and end the main loop.

Chapter 3 – The SDL Event Loop

In SDL, input and responses are handled with events. Here’s the definition of SDL_Event

typedef union{
  Uint8 type;
  SDL_ActiveEvent active;
  SDL_KeyboardEvent key;
  SDL_MouseMotionEvent motion;
  SDL_MouseButtonEvent button;
  SDL_JoyAxisEvent jaxis;
  SDL_JoyBallEvent jball;
  SDL_JoyHatEvent jhat;
  SDL_JoyButtonEvent jbutton;
  SDL_ResizeEvent resize;
  SDL_ExposeEvent expose;
  SDL_QuitEvent quit;
  SDL_UserEvent user;
  SDL_SywWMEvent syswm;
} SDL_Event;

Every event in the SDL_Event union holds its specific information. For example, SDL_KeyboardEvent holds the key code of the key pressed. The type member tells you which kind of event has been received. For example, a key-press, a mouse-click, a timer tick etc.

Now event event has it’s own event type which is stored in the type member. For example an SDL_QuitEvent has the type SDL_QUIT and an SDL_KeyboardEvent has the types SDL_KEYDOWN and SDL_KEYUP. (You know, this could be nice work-around for RTTI in C).

In an SDL application, you have to constantly loop through the event stack and respond to every new event. Something like this:

while(RUNNING)
{
    while(SDL_PollEvent(EVENT))
    {
         /* handle the events */
    }
}

Now let’s rewrite our sample program from the previous tutorial and add an event loop to it.

#include <SDL/SDL.h>

int main(int argc, char* argv[])
{
    /* A pointer to the video surface */
    SDL_Surface* screen = NULL;

    /* Initialize SDL's video sub-system */
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
        fprintf(stderr, SDL_GetError());

    /* Quit SDL on exit */
    atexit(SDL_Quit);

    /* Create a double buffered window 640 pixels wide and 480 pixels long */
    screen = SDL_SetVideoMode(640, 480, 16, SDL_HWSURFACE|SDL_DOUBLEBUF);

    /* Error check */
    if (!screen) fprintf(stderr, SDL_GetError());

    /* An SDL_Event */
    SDL_Event event;

    /* A bool to check if the program has exited */
    int quit = 0;

    /* While the program is running */
    while (!quit)
    {
        /* Check for new events */
        while(SDL_PollEvent(&event))
        {
            /* If a quit event has been sent */
            if (event.type == SDL_QUIT)
            {
                /* Quit the application */
                quit = 1;
            }
        }
    }

    /* Destroy the video surface */
    SDL_FreeSurface(screen);

    return 0;
}

Now the window can be dragged around on the screen and can be closed by pressing the ‘x’ button at the top. It has become interactive. Now for the walkthrough.

    /* An SDL_Event */
    SDL_Event event;

Here we declare an SDL_Event in order to store the value received by the SDL_PollEvent() function. This variable will be used extensively throughout our game loop.

    /* A bool to check if the program has exited */
    int quit = 0;

A variable to check if the program has been closed by the user. It’s value has been initially set to 0 (i.e false), and will be set to 1 (true) when an SDL_QuitEvent gets sent. This is to ensure that the program only exits when the window is closed down.

    /* While the program is running */
    while (!quit)
    {

This keeps the program looping and handling events until the window is closed by the user. Once quit becomes true the loop ends and the program exits.

        /* Check for new events */
        while(SDL_PollEvent(&event))
        {

This constantly checks for new events and stores them in the event variable we declared earlier. We add handlers for the events inside the loop. We check which event has been sent by the type member I discussed earlier. For a complete list of event types, see here.

            /* If a quit event has been sent */
            if (event.type == SDL_QUIT)
            {
                /* Quit the application */
                quit = 1;
            }

This is the handler for an SDL_QuitEvent. It sets quit to true in order to end the while loop so that the program exits.

Chapter 2 – Getting Started with SDL

Okay then. Let’s to get started by creating a sample program.

#include <SDL/SDL.h>

int main(int argc, char* argv[])
{
    /* A pointer to the video surface */
    SDL_Surface* screen = NULL;

    /* Initialize SDL's video sub-system */
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
        fprintf(stderr, SDL_GetError());

    /* Quit SDL on exit */
    atexit(SDL_Quit);

    /* Create a double buffered window 640 pixels wide and 480 pixels long */
    screen = SDL_SetVideoMode(640, 480, 16, SDL_HWSURFACE|SDL_DOUBLEBUF);

    /* Error check */
    if (!screen) fprintf(stderr, SDL_GetError());

    /* Wait 5 seconds */
    SDL_Delay(5000);

    /* Destroy the video surface */
    SDL_FreeSurface(screen);

    return 0;
}

Now, to give you a brief walkthrough…

    /* A pointer to the video surface */
    SDL_Surface* screen = NULL;

Here we declare an SDL_Surface pointer to hold the SDL_Surface object that represents the screen. Using SDL_Surfaces is a generic way of drawing stuff. Something like HDCs in GDI. The same code can be used to draw on the screen that you use to draw in memory.

Surfaces can be copied onto one another (SDL_BlitSurface), or filled with a color (SDL_FillRect) etc. Here’s the declaration:

typedef struct SDL_Surface {
    Uint32 flags;                           /* Read-only */
    SDL_PixelFormat *format;                /* Read-only */
    int w, h;                               /* Read-only */
    Uint16 pitch;                           /* Read-only */
    void *pixels;                           /* Read-write */
    SDL_Rect clip_rect;                     /* Read-only */
    int refcount;                           /* Read-mostly */

  /* This structure also contains private fields not shown here */
} SDL_Surface;

Onto the next snippet.

    /* Initialize SDL's video sub-system */
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
        fprintf(stderr, SDL_GetError());

This initializes SDL and it’s various sub-systems. I used the SDL_INIT_VIDEO flag to initialize the video sub-system. See here for a list of flags to use with this function. To simply initialize everything just use the SDL_INIT_EVERYTHING flag. The function returns 0 on success and -1 on failure (hence the if ... < 0 statement for error checking). Also note that stderr is already redirected to a text file stderr.txt by SDL.

    /* Quit SDL on exit */
    atexit(SDL_Quit);

This is to ensure that the SDL_Quit()function is called before the program exits. This un-initializes all previously initialized sub-systems and shuts down SDL.

    /* Create a double buffered window 640 pixels wide and 480 pixels long */
    screen = SDL_SetVideoMode(640, 480, 16, SDL_HWSURFACE|SDL_DOUBLEBUF);

This creates a window with a width of 640 and height of 480 pixels. The third parameter is bits-per-pixel, you usually use values like 16, 24 and 32. The last parameter are the display flags. I used SDL_HWSURFACE because I wanted a hardware-surface (i.e a surface stored in video memory instead of system memory). Hardware surfaces tend to be faster than software ones. Secondly, the SDL_DOUBLEBUFflag tells SDL we want a double buffered window mode. Double Buffered means we want SDL to draw first draw on a separate surface in memory (the back-buffer) and then copy the back buffer onto the screen (front-buffer), instead of directly drawing on the screen. This allows for smoother graphics.

    /* Error check */
    if (!screen) fprintf(stderr, SDL_GetError());

The SDL_SetVideoMode() function returns a NULL on failure. So this is just a simple error check.

    /* Wait 5 seconds */
    SDL_Delay(5000);

The SDL_Delay function waits for a specific number of milliseconds. It isn’t very accurate however and shouldn’t be used in doing precise calculations. Over here we just use it to give us 5 seconds (1 s = 1000 ms) to look at our window before it closes.

    /* Destroy the video surface */
    SDL_FreeSurface(screen);

The SDL_FreeSurface() function frees up the memory and resources taken up by a previously created surface. Freeing surfaces you have created is good practice. The surface should also be assigned a NULL value after freeing to ensure that it doesn’t do something unpredictable if accidentally used after it is freed. Since SDL_Surfaces are reference counted, you don’t have to worry about accidentally freeing a surface which is being used somewhere else. If it’s in use, it isn’t freed and deleted. However take care to call SDL_FreeSurface() only once for every surface you create. Since we already added SDL_Quit to atexit() we don’t need to call it here.

To compile SDL applications, you have to include SDL’s include directory to your compiler’s search paths, and link with SDL.dll. On MinGW GCC, I usually use this makefile for compiling SDL Apps:

CC=gcc
COMPILER_DIRS=-I
LINKER_DIRS=-L -L

#Change this when adding more source files
SOURCES=<source files="" />
OBJECTS=$(SOURCES:.cpp=.o)

CFLAGS=-Wall $(COMPILER_DIRS) $(LINKER_DIRS)
LIBS=-l mingw32 -l SDLmain -l SDL
TARGET=-mwindows
EXECUTABLE=bin\game.exe

$(EXECUTABLE): $(OBJECTS)
	$(CC) $(OBJECTS) $(CFLAGS) -o $@ $(LIBS)

%.o : %.cpp
	$(CC) -c $< -o $@ $(CFLAGS)

clean:
	del *.o

, and   should be replaced with SDL’s include, lib and bin directories. For example if SDL is in C:\, should be C:\SDL\include. Replace with your source files.

If the app is successfully built, it should display a blank unresponsive window for about 5 seconds and then shut down. That’s pretty much the infrastructure of an SDL Application.

Chapter 1 – Introduction to SDL

Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer. It is used by MPEG playback software, emulators, and many popular games, including the award winning Linux port of “Civilization: Call To Power.”

SDL supports Linux, Windows, Windows CE, BeOS, MacOS, Mac OS X, FreeBSD, NetBSD, OpenBSD, BSD/OS, Solaris, IRIX, and QNX. The code contains support for AmigaOS, Dreamcast, Atari, AIX, OSF/Tru64, RISC OS, SymbianOS, and OS/2, but these are not officially supported.

SDL is written in C, but works with C++ natively, and has bindings to several other languages, including Ada, C#, D, Eiffel, Erlang, Euphoria, Go, Guile, Haskell, Java, Lisp, Lua, ML, Objective C, Pascal, Perl, PHP, Pike, Pliant, Python, Ruby, Smalltalk, and Tcl.

SDL is distributed under GNU LGPL version 2. This license allows you to use SDL freely in commercial programs as long as you link with the dynamic library.

The above passage is from the main page of SDL’s website.

Some FAQs…

Why SDL? SDL is cross-platform, has a simple enough API (I used simple, not good), comes with a comparatively permissive license (you can create commercial apps with it as long as you link dynamically with SDL.dll), integrates well with OpenGL (though my knowledge in that field is limited) and supports most of the stuff required for a game (albeit with extensions). So, since I can’t see why we should not use SDL, we might as well use it.

What exactly is this Simple something Layer? Well. As it’s name suggests, it’s a cross-platform wrapper that provides graphics, input-handling, sound etc. It has several back-ends for various platforms.


SDL? Isn’t that pre-historic stuff? Well, yeah, I agree. The SDL community has ceased to be as active as it once was. SDL 2.0 is taking ages to get officially released. There are no recent additions (except for bug fixes). The API is imperative (not that that’s a bad thing, but it’s pretty tiring sometimes). They still haven’t added support for multiple windows. And the list goes on… (I should write an article on the various short-comings of SDL). But at any rate, SDL is time-tested, has been used in a lot of commercial and open-source projects and is ‘good enough’.

Why don’t you use SFML / Allegro / etc. ?. It’s WAY better than this obsolete junk. Yet another library war begins! (wait a min, I’ll get popcorn). Seriously, why do people compare libraries? Admittedly SDL lacks stuff that some other library may have. But, you can make games with pretty much all of them. The only reason I picked SDL instead of all the others for this tutorial is well… because it was the easiest to quickly set up. Maybe I’ll do tutorials on them too some other time.

How dare you let that SFMl / Allegro / etc. fan-boy trick you into admitting SDL’s inferiority? SDL is way better than all of that useless garbage! REAL programmers use SDL! The return blow. Guess the war has already begun in spite of my efforts to prevent it…