Game Programming using SDL: Raw Graphics and Event Handling

If you have found yourself struggling with pixel-level graphics and/or user input for your games, this is the article for you. You will learn how to handle these tasks using SDL. This is the second part of a series on game programming with SDL.

Graphics and handling user inputs — the combination that creates the symphony we call a game. A game world where these two are out of phase ends in cacophony.

In the previous article I discussed the various parameters that go into creating a screen and loading bitmapped images onto the screen. That was pretty high-level as the work was done on structures that represent the actual screen and maps or sprites. But there are times when one has to get his or her hands dirty by working directly upon pixels.

The creators of SDL had already anticipated this requirement and built the capacities to work at the raw graphics level into the core itself. Thus a developer is redeemed from understanding system, platform and architecture specific nitty-gritty about manipulating the pixels. The other aspect of gaming that gives sleepless nights to developers is handling the user input, as the handling of input devices changes from system to system. To remove this burden from the minds of the developers, SDL provides an object-oriented approach to handling the events.

In this article, I will be discussing these two aspects of SDL. In the next two sections, the discussion will focus on pixel manipulation functions and their usages. The final section will focus on input handling. So now that the agenda for this article have been laid out, let’s get started.

{mospagebreak title=Raw Graphics: Writing Directly onto the Display}

Though the SDL Graphics APIs provide pretty high level functionality, abstracting off all the low-level details, there are times when abstraction is not required. For this purpose also there are ways to do what you want. These ways don’t exist as a library function but as separate functions that must be embedded into your program. The functions are freely available; for completeness I am including them here. These functions are:

  1. getpixel():

    This function is useful if pixel value must be obtained from given coordinates represented by X and Y values on the display. It works on a single pixel at a time. The first parameter is the surface from which the value has to be obtained. This is represented by a pointer to the SDL_Surface. The next two integer parameters represent the x and y coordinates from where the pixel value has to be obtained. The return value is a Uint32 representing the value of the pixel. Following is the code:

    /*
    * Return the pixel value at (x, y)
    * NOTE: The surface must be locked before calling this!
    */
    Uint32 getpixel(SDL_Surface *surface, int x, int y)
    {
      int bpp = surface->format->BytesPerPixel;
      /* Here p is the address to the pixel we want to retrieve */
      Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

      switch(bpp) {
        case 1:
          return *p;

        case 2:
          return *(Uint16 *)p;

        case 3:
          if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
          else
            return p[0] | p[1] << 8 | p[2] << 16;

        case 4:
          return *(Uint32 *)p;

        default:
          return 0; /* shouldn’t happen, but avoids warnings */
      }
    }

    The first thing to do is obtain the depth represented by BytesPerPixel. This is accomplished by the first statement:

    int bpp = surface->format->BytesPerPixel;

    The next statement is self explanatory. To get the address of the pixel, the pitch of the passed surface is multiplied by the value of the Y coordinate. The depth is multiplied by the X coordinate and the resulting values are added with pixel data to the surface represented by the pixel’s member of SDL_Surface. This calculation provides the actual address of the pixel. The SDL_Surface could be thought of as a multi-dimensional array. Hence the value could be accessed in the row-major and column-major format. That is done in the second statement:

    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    As the value returned by BytesPerPixels ranges from 1-4 according to the bytes needed to represent the pixel, it can be used for returning the values in the corresponding format i.e. 8, 16, 24 or 32. This is achieved by the switch-case block. That covers the getpixel function.
  2. putpixel():

    This is the same as getpixel(). Apart from the parameters accepted by the getpixel() function, this function accepts one more parameter — the address where the value has to be put. The following is the code for putpixel():

    /*
    * Set the pixel at (x, y) to the given value
    * NOTE: The surface must be locked before calling this!
    */
    void putpixel(SDL_Surface *surface, int x, int y, Uint32 pixel)
    {
      int bpp = surface->format->BytesPerPixel;
      /* Here p is the address to the pixel we want to set */
      Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

      switch(bpp) {
        case 1:
          *p = pixel;
          break;

        case 2:
          *(Uint16 *)p = pixel;
          break;

        case 3:
          if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
            p[0] = (pixel >> 16) & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = pixel & 0xff;
          } else {
            p[0] = pixel & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = (pixel >> 16) & 0xff;
          }
          break;

        case 4:
          *(Uint32 *)p = pixel;
          break;
      }
    }

    The way putpixel() works is almost the opposite of the way getpixel() works. With the former, the returned value is the pixel value corresponding to the coordinates whereas the latter places the pixel value according the coordinates. To accomplish this, first the BytesPerPixel of the passed SDL_Surface is extracted just as before. Then the pixel’s address (or pixel value) is calculated and, according to the value, returned by BytesPerPixels, then the value is placed. Since the calculated value is the address of the pixel, the passed pixel value can be directly assigned and the display will get the new value.

{mospagebreak title=Putting the Functions to Use}

Now that both functions have been explained, let’s see how to put one of them to use, i.e. putpixel(). For this I am defining a method called putyellowpixel() that places a yellow pixel at the center of the screen. It doesn’t accept any parameter nor does it return any value.

void putyellowpixel()
{
 
int x, y;
  
Uint32 yellow;

  /* Map the color yellow to this display (R=0xff, G=0xFF, B=0x00)
 
Note: If the display is palettized, you must set the palette first.
 
*/
 
yellow = SDL_MapRGB(screen->format, 0xff, 0xff, 0x00);

  x = screen->w / 2;
 
y = screen->h / 2;

  /* Lock the screen for direct access to the pixels */
 
if ( SDL_MUSTLOCK(screen) ) {
   
if ( SDL_LockSurface(screen) < 0 ) {
     
fprintf(stderr, "Can’t lock screen: %sn", SDL_GetError());
     
return;
   
}
 
}

  putpixel(screen, x, y, yellow);

  if ( SDL_MUSTLOCK(screen) ) {
   
SDL_UnlockSurface(screen);
 
}
 
/* Update just the part of the display that we’ve changed */
 
SDL_UpdateRect(screen, x, y, 1, 1);

  return;

}

To get the yellow color, the SDL_MapRGB() has to be used. The SDL_PixelFormat is the first parameter. It stores surface format information. The next three parameters correspond to the red, blue and green components of the color. The return value is the actual color corresponding to the passed color components in hexadecimal format, as follows:

yellow = SDL_MapRGB(screen->format, 0xff, 0xff, 0x00);

Once the color has been retrieved, the next step is to get the required x and y coordinates, which is achieved by the following statement:

x = screen->w / 2;
y = screen->h / 2;

then the screen surface is locked. If this is not done, then corruption of the SDL_Surface structure could happen, causing instability of the game as putpixel works on the address of the pixel directly. This is done by:

SDL_MUSTLOCK(screen);

The next step is to call the putpixel. Once putpixel has returned, then unlock the surface and update the surface. That completes placing a pixel directly onto the surface. The next section will focus on event handling with reference to the keyboard.

{mospagebreak title=Handling the Keyboard the SDL Way}

What I’ve discussed up until now covers only one aspect of providing interactivity. Even now the application doesn’t have the ability to handle user gestures provided through different input devices such as a keyboard, joy stick, etc. So now we’ll shift our focus to input handling.

Two of the most common input devices are the mouse and the keyboard. SDL has wrappers for each of these. In this section I will discuss keyboard handling. Before entering the world of keyboard events, it is best to understand the most recurring structures in keyboard handling jargon. They are:

  • SDLKey: This is an enumerated type that represents various keys. For example SDLK_a represents a lowercase "a," SDLK_DELETE is for the "delete" key and so on.
  • SDLMod: The SDLKey enumeration represents only keys. To represent key modifiers such as Shift and Ctrl, SDLMod enumeration is provided by SDL. KMOD_CAPS is one of the enumerations that can be used to find out whether the caps key is down or not. Other modifiers also have representations in SDLMod.
  • SDL_keysym: This is a structure that contains the information of a key-press. The members of this structure include scan code in hardware dependent format, the SDLKey value of the pressed key in the sym field, the value of modifier key in the mod field and the Unicode representation of the key in the Unicode field.
  • SDL_KeyboardEvent: From the name itself it is obvious that this structure describes a keyboard event. The first member, type, tells that the event is a key release or a key press event. The second member gives the same info as the first but uses different values. The last member is a structure itself — the SDL_keysym structure.

Now that the structures have been brought into the picture, the next step is to use these in handling the keyboard events. For this the logic is simple. The SDL_PollEvent is used to read the events. This is placed within the while loop. Then the value of the type member of SDL_Event variable, passed as the parameter to SDL_PollEvent, is checked to find the type of event, and then event processing can be done. In code it looks like this:

SDL_Event event;
.
.
/* Poll for events. SDL_PollEvent() returns 0 when  */
/* there are no 
more events on the event queue, our */
/* while loop will exit when 
that occurs. */
while( SDL_PollEvent( &event ) ){
  /* We are only worried about SDL_KEYDOWN and SDL_KEYUP events */
 
switch( event.type ){
   
case SDL_KEYDOWN:
     
printf( "Key press detectedn" );
     
break;

   
case SDL_KEYUP:
     
printf( "Key release detectedn" );
     
break;

    
default:
      
break;
 
}
}
.
.

If this is used this in the program developed in the last article, the exit condition of the program can be controlled. The new version would exit only at a key press.

void display_bmp(char *file_name)
{
 
SDL_Surface *image;

 
/* Load the BMP file into a surface */
 
image = SDL_LoadBMP(file_name);
 
if (image == NULL) {
   
fprintf(stderr, "Couldn’t load %s: %sn", file_name, SDL_GetError());
   
return;
  
}

  /*
 
* Palletized screen modes will have a default palette (a
  * standard
8*8*4 colour cube), but if the image is
  * palletized as well we can 
use that palette for a nicer
  * colour matching
 
*/
 
if (image->format->palette && screen->format->palette) {
   
SDL_SetColors(screen, image->format->palette->colors,
                 0,
image->format->palette->ncolors);
 
}

  /* Blit onto the screen surface */
 
if(SDL_BlitSurface(image, NULL, screen, NULL) < 0) {
   
fprintf(stderr, "BlitSurface error: %sn", SDL_GetError());
   
SDL_UpdateRect(screen, 0, 0, image->w, image->h);

    /* Free the allocated BMP surface */
   
SDL_FreeSurface(image);
 
}

  int main(int argc,char* argv[])
  {
   
/*variable to hold the file name of the image to be loaded
   
*In real world error handling code would precede this
    */
   
char* filename="Tux.bmp";

    /*The following code does the initialization for Audio and Video*/
   
int i_error=SDL_Init(SDL_INIT_VIDEO);

    /*If initialization is unsuccessful, then quit */
   
if(i_error==-1)
     
exit(1);

    atexit(SDL_Quit);

    /*
   
* Initialize the display in a 640×480 8-bit palettized mode,
   
* requesting a software surface
   
*/

    screen = SDL_SetVideoMode(640, 480, 8, SDL_SWSURFACE);
   
if ( screen == NULL )
   
{
     
fprintf(stderr, "Couldn’t set 640x480x8 video mode: %sn",
      
SDL_GetError());
     
exit(1);
   
}

    /*Handle the keyboards events here. Catch the SDL_Quit event to exit*/
   
done = 0;
   
while (!done)
   
{
      
SDL_Event event;

      /* Check for events */
     
while (SDL_PollEvent (&event))
     
{
       
switch (event.type)
       
{
         
case SDL_KEYDOWN:
           
break;
         
case SDL_QUIT:
           
done = 1;
           
break;
         
default:
           
break;
       
}
     
}

    /* Now call the function to load the image and copy it to the screen surface*/
   
load_bmp(filename);

}

If you run the above code the window won’t be closed until the close button is pressed. Though this code does nothing much in the area of interactivity, it is a beginning. So as you can see, it is really easy to handle keyboard events using SDL. It totally removes the dependence of developers on the operating system  for event handling. Also, as you can see, working at the raw graphics level is not that difficult.

This brings us to the end of this article on SDL programming. The next part will cover using OpenGL with SDL. Till next time.

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort