Simple DirectMedia Layer: Gaming Platform for C++, Java, and More

Gentlemen, Start Your 3D Gaming Engines!

In gaming, more so than any other programming regime, it is important to specify the platform correctly from the start. Will you support Windows, Linux, and OS X? Indeed, many people would cite the revolutionary Quake game written for OpenGL in 1996 as the granddaddy of game platforms. However, to reach Quake-level gameplay standards you will also need world-class audio support, network connectivity, user-input device support, and real-time management capabilities, just to name a few... The solution to both problems, cross-platform support and the extras that make a game exciting, is Simple DirectMedia Layer (SDL). This is a cross-platform multimedia library designed to provide low-level access to audio, keyboard, mouse, joystick, OpenGL, and 2D video framebuffer. SDML supports almost every platform I can think of, including Linux, Windows, all MacOs variants, WinCE, Dreamcast, and others. Because it is distributed on GNU LGPL v2, you can use SDL freely in commercial programs as long as you link with the supplied DLL.

SDML shows up in MPEG players, hardware emulators, and many popular games, including the award winning Linux port of Civilization: Call To Power. SDML is written in C, but works with C++ natively, and has bindings to several other languages, including Ada, Eiffel, Java, Lua, ML, Perl, PHP, Pike, Python, and Ruby. The sky is the limit with SDL, which is the engine for my favorite Open Source flight simulator GL-117. In fact, there are now 568 games built on top of the SDL engine (of which 450 can build Win32 executables) registered on the SDML homepage.

Figure 1: The view from GL-117

ALIENS in Your Midst!

The best way to get inside a game engine is to look at some sample code. This time, you're going inside a 1980s Space Invaders clone game by Sam Lantinga, called simply aliens. Remarkably, the complete game source code is only 560 lines of code! I wish I could lay out the entire thing in this article, but it exceeds my boundaries just a little. Instead, I will focus with laser-like intensity on the specifics that matter the most and ignore bits which are rote or repetitious. [I'll be using line numbers from aliens.c if you want to follow along in Visual Studio.]

Figure 2: Screenshot of Aliens game

The first thing to notice is that "aliens" leverages code from two other SDL projects to help get the job done: SDL_mixer and SDL_image. SDL_mixer is a platform-independent sound mixing library plug-in for SDL apps. It allows your apps to play multiple samples along with music without having to code a mixing algorithm. For example, you would use this to allow gunfire noises to seamlessly mix into background music. It also simplifies loading and playing samples and music from all sorts of file formats. SDL_image is a platform-independent graphics loading plug-in for SDL apps. It allows your apps to read BMP, PNM (PPM/PGM/PBM), XPM, LBM, PCX, GIF, JPEG, PNG, TGA, and TIFF format files. So, be sure to download both packages if you want to follow along!

Simple DirectMedia Layer: Gaming Platform for C++, Java, and More

The main() Business

The main() function, shown below, is where the action starts, of course. In Line #524, you ask SDL_Init() to initialize the audio and video subsystems. Other options include turning on joystick, timer, and CD-ROM access as well as an option to run the event manager in a separate thread. In Line #531, you start using the SDL_Mixer manager with a call to Mix_OpenAudio(). In this example, you're using low-quality audio suitable for older/slower CPUs: 11Khz, 8-bit unsigned samples, one channel (mono), and a 512 byte buffer. Next, you open up the video with SDL_SetVideoMode() for 640x480 display using whatever bits per pixel is appropriate for the device. You ask for a system-memory buffer and request a "full screen" display, meaning that you want your window to dominate the windowing system. Other possible options include double-buffering (smoother animation), OpenGL context, resizable window, and asynchronous repaints.

521 main(int argc, char *argv[])
522 {
523    /* Initialize the SDL library */
524    if ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 ) {
525       fprintf(stderr, "Couldn't initialize SDL: %s\n",
                  SDL_GetError());
526       exit(2);
527    }
528    atexit(SDL_Quit);
529
530    /* Open the audio device */
531    if ( Mix_OpenAudio(11025, AUDIO_U8, 1, 512) < 0 ) {
532       fprintf(stderr,
533       "Warning: Couldn't set 11025 Hz 8-bit audio\n-
           Reason: %s\n",
534       SDL_GetError());
535    }
536
537    /* Open the display device */
538    screen = SDL_SetVideoMode(640, 480, 0,
          SDL_SWSURFACE|SDL_FULLSCREEN);
539    if ( screen == NULL ) {
540       fprintf(stderr, "Couldn't set 640x480 video mode: %s\n",
541       SDL_GetError());
542       exit(2);
543    }
544
545    /* Initialize the random number generator */
546    srand(time(NULL));
547
548    /* Load the music and artwork */
549    if ( LoadData() ) {
550       /* Run the game */
551       RunGame();
552
553       /* Free the music and artwork */
554       FreeData();
555    }
556
557    /* Quit */
558    Mix_CloseAudio();
559    exit(0);
560 }

Running the Game

The main logic loop is, of course, all inside RunGame(). This function is about 200 lines of code, so still too much to look at all at once, but you will examine some of the more important sections by way of explaining SDL features. Where I've deleted some code, I'll indicate with an ellipsis ( ... ) in the comments.

301 void RunGame(void)
302 {
303    int i, j;
304    SDL_Event event;
305    Uint8 *keys;
306
307    /* Paint the background */
308    numupdates = 0;
309    for ( i=0; i<screen->w; i += background->w ) {
310       SDL_Rect dst;
311
312       dst.x = i;
313       dst.y = 0;
314       dst.w = background->w;
315       dst.h = background->h;
316       SDL_BlitSurface(background, NULL, screen, &dst);
317    }
318    SDL_UpdateRect(screen, 0, 0, 0, 0);

In the first section (Lines #301 to 318), you are simply blit-ing slices of background color onto the screen buffer sequentially with SDL_BlitSurface() and finally set up a repaint with SDL_UpdateRect().

319
320    /* Initialize the objects */
321    // . . .
333    CreateAlien();
334    DrawObject(&aliens[0]);
335    UpdateScreen();
336
337    while ( player.alive ) {
338       /* Wait for the next frame */
339       WaitFrame();
340
341       /* Poll input queue, run keyboard loop */
342       while ( SDL_PollEvent(&event) ) {
343          if ( event.type == SDL_QUIT )
344             return;
345       }
346       keys = SDL_GetKeyState(NULL);
347
348       /* Erase everything from the screen */
349       // . . .
366       /* Decrement the lifetime of the explosions */
367       // . . .
373       /* Create new aliens */
374       if ( (rand()%ALIEN_ODDS) == 0 ) {
375          CreateAlien();
376       }
377
378       /* Create new shots */
379       if ( ! reloading ) {
380          if ( keys[SDLK_SPACE] == SDL_PRESSED ) {
381             for ( i=0; i<MAX_SHOTS; ++i ) {
382                if ( ! shots[i].alive ) {
383                   break;
384                }
385             }
386             if ( i != MAX_SHOTS ) {
387                shots[i].x = player.x +
388                   (player.image->w-shots[i].image->w)/2;
389                   shots[i].y = player.y -
390                      shots[i].image->h;
391                   shots[i].alive = 1;
392                   Mix_PlayChannel(SHOT_WAV,
393                      sounds[SHOT_WAV], 0);
394             }
395          }
396       }

Simple DirectMedia Layer: Gaming Platform for C++, Java, and More

Now, look at the input-management part of the RunGame() loop. The deceptively simple SDL_PollEvent() returns an SDL_Event object with notes about interesting things that might have happened. At this time, you check for an ESC key press with the special value of SDL_QUIT. Because this is a purely keyboard-driven game, you can use SDL_GetKeyState() to get all the info you need. Remember that the PC keyboard is designed to allow for multiple simultaneous key downs, so the result is returned in an array that you index by a keyboard code value such as SDLK_SPACE (in other words, spacebar).

397        reloading = (keys[SDLK_SPACE] == SDL_PRESSED);
398
399       /* Move the player */
400       //. . .
415       /* Move the aliens and the shots */
416       // . . .
443
444       /* Detect collisions */
445          for ( j=0; j<MAX_SHOTS; ++j ) {
446          for ( i=0; i<MAX_ALIENS; ++i ) {
447          if ( shots[j].alive && aliens[i].alive &&
448                Collide(&shots[j], &aliens[i]) ) {
449             aliens[i].alive = 0;
450             explosions[i].x = aliens[i].x;
451             explosions[i].y = aliens[i].y;
452             explosions[i].alive = EXPLODE_TIME;
453             Mix_PlayChannel(EXPLODE_WAV,
454                sounds[EXPLODE_WAV], 0);
455             shots[j].alive = 0;
456             break;
457          }
458       }
459    }

Stop here and quickly look at Mix_PlayChannel(), which adds explosion noises at appropriate conditions. You're actually using three channels simultaneously in this game: one each for music, shots, and explosions respectively because at any given moment you may need to output up to all three at once. Otherwise, you would be continuously cutting away from one sound to play another sound and the soundtrack would be a choppy mess. By the way, you loaded the sounds earlier with a call such as:

127    sounds[MUSIC_WAV] = Mix_LoadWAV(DATAFILE("music.wav"));

Okay, back to the code:

474       // . . .
475       /* Draw the aliens, shots, player, and explosions */
476       for ( i=0; i<MAX_ALIENS; ++i ) {
477          if ( aliens[i].alive ) {
478             DrawObject(&aliens[i]);
479          }
480       }
481       // . . .
494       UpdateScreen();
495
496       /* Loop the music */
498       if ( ! Mix_PlayingMusic() ) {
499          Mix_PlayMusic(music, 0);
500       }
506
507       /* Check for keyboard abort */
508       if ( keys[SDLK_ESCAPE] == SDL_PRESSED ) {
509          player.alive = 0;
510       }
511    }
512
513    /* Wait for the player to finish exploding */
514    while ( Mix_Playing(EXPLODE_WAV) ) {
515       WaitFrame();
516    }
517    Mix_HaltChannel(-1);
518    return;
519 }
520

All right, I haven't been able to show every single thing, such as UpdateScreen(), which walks through a list of all the bitmaps you are going to display and blits them to the screen. You can follow this on your own copy of the source!

Conclusion

Even putting together a conventional 2D shooter game in 500-odd lines of code with music and realtime play is a pretty good feat in my imagination. And of course the same code can be compiled and linked to run on Windows and Linux platforms and a lot more. In some respects, you've barely scratched the surface and if you are encouraged to give SDL a try, then I think I've met my goal for this article!

About the Author

Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for the Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries; just drop send an email to sysop@HAL9K.com



About the Author

Victor Volkman

Victor Volkman has been writing for C/C++ Users Journal and other programming journals since the late 1980s. He is a graduate of Michigan Tech and a faculty advisor board member for Washtenaw Community College CIS department. Volkman is the editor of numerous books, including C/C++ Treasure Chest and is the owner of Loving Healing Press. He can help you in your quest for open source tools and libraries, just drop an e-mail to sysop@HAL9K.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • 10 Rules that Make or Break Enterprise App Development Projects In today's app-driven world, application development is a top priority. Even so, 68% of enterprise application delivery projects fail. Designing and building applications that pay for themselves and adapt to future needs is incredibly difficult. Executing one successful project is lucky, but making it a repeatable process and strategic advantage? That's where the money is. With help from our most experienced project leads and software engineers, …

  • The latest release of SugarCRM's flagship product gives users new tools to build extraordinary customer relationships. Read an in-depth analysis of SugarCRM's enhanced ability to help companies execute their customer-facing initiatives from Ovum, a leading technology research firm.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds