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.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s