SDL Lesson 1: The Foundation
From DmWiki
| Table of contents |
Introduction
This is the first installment of a multi-part series that will greatly assist the aspiring game programmer get into SDL and code games with it. Typically after having a firm grasp of fundamental to intermediate features of a particular language, say C or C++, a programmer who's into the game development field goes to graphics afterwards. One of the options is to go with DirectX. But to most people the architecture of the DirectX API may seem a little too complex for someone just getting into graphics. A popular alternative to 3D graphics programming is OpenGL which SDL can interface with.
SDL is a portable (or cross-platform) API that deals with 2D graphics, keyboard and mouse input, font rendering, audio, etc. It was created by Sam Lantinga in 1998. He got the idea of creating SDL while porting an application from Windows to Macintosh. He then used SDL to port Doom to BeOS. SDL was mainly coded in C but has bindings to many other languages. It is easy to integrate C++ code into a SDL application. Most people code C++ with SDL apps. This article will focus on writing C++ code using the SDL API.
Most people who believe that the DirectX API is too complex take the alternative route of SDL. Back then, however, Allegro was the main alternative but in the minds of most game programmers SDL is a better designed library and much easier to work with. SDL mainly handles 2D graphics though. It is possible to interface 3D but you'd have to use OpenGL for 3D and let SDL handle the keyboard/mouse input and window management.
Installation
To download the SDL development kit go here (http://www.libsdl.org/download-1.2.php). As a programmer you should be concerned with only the links below Development Libraries. Click on the link as it pertains to you. For instance, if you're on a Windows platform and you're using Microsoft Visual C++.NET or Visual C++ 6 you should be concerned with the link, SDL-devel-1.2.8-VC6.zip. It doesn't make sense to be on Linux and download a Windows OS package. This article will focus on using SDL with Visual Studio .NET 2003.
After downloading the compressed archive that contains the SDL header files and libraries, extract those files that reside in the archive onto your local disk drive. After successful extraction, open Microsoft Visual Studio.NET without opening a solution or project. In the menu, go to Tools and then Options. Before you could use SDL you must tell the compiler or IDE that you're using where the SDL files are located. What we're about to do is inform Visual C++.NET 2003 where the include files for SDL are.
After going to Tools->Options a dialog box should pop up. Go to the projects folder on the side and then to VC++ Directories underneath. Under Show Directories for: select include files in the drop-down menu like so:
Click on the folder icon to create a new line/directory. Click on the triple dots to bring up a browse folder dialog window. In this window, you should search for the include folder in the SDL folder where you extracted the compressed archive. After adding a new directory remain on the Tools->Option window. Select Library Files from the drop-down menu under Show Directories For: and add a new directory but this time for the lib folder in SDL.
Now MSVC++.NET knows where your SDL files are and you're ready to code. Setup on VC++6 may differ slightly. To start a new project that allows you to write SDL apps you should first create what is called a solution. In MSVC++ 6, this is called a workspace. What it is basically is literally a workspace, or a collection of projects where it is easy to switch between them. After creating a blank solution you're now able to add some projects into it. In Microsoft Visual C++.NET hold Ctrl+Shift+N and a new project dialog box should appear like so:
Come up with a short and comprehensible name for your project. Your new project's folder will be created in the same folder your solution file resides in. After selecting a console application type of Win32 project and hitting "OK" a second dialog window will be brought up. Make sure your settings matched those of the following screen shot:
Now you're almost set. Be sure to copy SDL.dll into your system32 folder. You could also copy it into your project folder, but it should be in the same directory as the executable after you've compiled it. More on that later.
For a complete guide for SDL installation on all systems and IDEs, check this page (http://gpwiki.org/index.php/C:How_to_set_up_your_SDL_Build_Environment)
The Initiation
After installation you should now be ready to code SDL apps. While in MSVC++.NET open your solution. Your recently created project should be under the solution. Hold Ctrl+Shift+A to add a new item to your project. Here, you will add a C++ file (.cpp), you have to name it and choose a location. A suggested name would be Main.cpp and saving it in the same directory as your project file. It wouldn't hurt if you created a new folder in your project's directory labeled "Source" or "Src", a folder where you would store all of your .cpp and header files in.
A new file called Main.cpp (or whatever you named it) should have now been added to your project in the solution explorer. In the solution explorer right-click your project file and then go to properties. Expand the C/C++ folder on the left panel. Next to Runtime Library select Multi-threaded DLL. Hit the apply button but don't exit this dialog window just yet. Make sure that the configuration for your project is on Active (Debug). SDL uses what are called threads. Your program is basically a process which could be divided into threads--portions of a program that can run independently or concurrently with other threads. The SDL API provides a collection of routines that deal with threads. Even though you're not using these routines, your SDL app will still be using threads. So because of this, it is necessary to set the Runtime Library to Multi-threaded DLL. When you're finish coding your game or demo, it is also necessary to change your project into a release build. The lib files that came with SDL are release-libs so using Multi-threaded Debug DLL isn't necessary. The DLL is included because your app will have to use it (SDL.dll).
Another important step you should take before coding SDL is link your project to SDL's library files. Remember the lib folder that was created after you extracted your SDL package? Those files in there are SDL's static library files. While still on your project's property page go to the Linker folder in the left panel and then go to input underneath. Highlight Additional Dependencies and click on the triple dots. A new dialog window should pop up asking you to type in the libraries to link to. In the empty white text box add SDL.lib and SDLmain.lib and hit the ok button.
Now you're really ready. But in order to determine that you should make an attempt to compile the following code:
#include <iostream>
///////////////////////////////////////////////////////////////////////////////////
// Namespace access permission.
using namespace std ;
///////////////////////////////////////////////////////////////////////////////////
// Main entry point.
int main( int argc, char* argv[] )
{
cout << "Hello World!" ;
cin.get() ;
return 0 ;
}
///////////////////////////////////////////////////////////////////////////////////
Obviously, learning C++ is a crucial prerequisite. If you do not understand the previous code segment then this tutorial isn't for you. If your compiler reports any errors please e-mail me. In order to use SDL you have to obviously include its header file(s). For now you will concern yourself with SDL.h. With SDL.h now included you're allowed to use its basic functionality. First you must initiatilize the SDL library with SDL_Init (Uint32 flags). Uint32 is a typedef unsigned integer that SDL uses frequently. Here are some initiation flags you could use with SDL_Init:
| Flag | Description |
|---|---|
SDL_INIT_AUDIO | Initiates the audio subsystem. |
SDL_INIT_TIMER | Initiates the timer subsystem. |
SDL_INIT_VIDEO | Initiates the video subsystem. |
SDL_INIT_EVERYTHING | Initiates all subsystems. |
The flag constants are pretty self-explanatory. There are more however. For a full list of initiation flags please refer to the SDL documentation (http://www.libsdl.org/cgi/docwiki.cgi/). This tutorial is only concerned with initiating the SDL library and using it to blit images. In that sense, we should only be concerned with SDL_INIT_VIDEO for now. The SDL_Init function returns an integer. If it returns 0 then everything is okay, but otherwise something went wrong. Knowing this, we could easily check for errors upon initiation.
Initiating the library isn't so fun. Through SDL's facility, it is possible to create a window or video mode. To create a graphical surface structure that will act as a base surface where all drawing will occur, we make a call to SDL_SetVideoMode. The function has four parameters: The width, height, bits-per-pixel, and 32-bit video flag(s); For a full list of video flags refer to SDL's documentation (it should've come with the SDK package). If Bits-Per-Pixel is 0 then the video mode will use the current display's bpp. As you might have already guessed, SDL has a struct that represents a graphical surface structure, SDL_Surface. SDL_SetVideoMode returns a pointer to one, otherwise, it returns null if there is a problem. At the end of your SDL application you should manually make a call to SDL_Quit, which obviously shuts down all of the initiated subsystems and frees the SDL_Surface* created with SDL_SetVideoMode.
Let's now place all of the SDL routines we learned into action like so:
#include <SDL/SDL.h>
#define FAILURE 1
#define SUCCESS 0
///////////////////////////////////////////////////////////////////////////////////
// Main entry point.
int SDL_main( int argc, char* argv[] )
{
if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
return FAILURE ;
SDL_Surface* Screen = NULL ;
Screen = SDL_SetVideoMode( 640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF ) ;
if( Screen == NULL )
return FAILURE ;
SDL_Quit() ;
return SUCCESS ;
}
///////////////////////////////////////////////////////////////////////////////////
A more systematic way for checking and processing errors would be to use a throw and catch mechanism:
#include <iostream>
#include <SDL/SDL.h>
///////////////////////////////////////////////////////////////////////////////////
// Namespace access permission.
using std::cerr;
///////////////////////////////////////////////////////////////////////////////////
// Main entry point.
int SDL_main( int argc, char* argv[] )
{
try
{
SDL_Surface* Screen = NULL ;
if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
throw SDL_GetError() ;
Screen = SDL_SetVideoMode( 640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF ) ;
if( Screen == NULL )
throw SDL_GetError() ;
// code here
}
catch( char* strError )
{
cerr << strError << '\n' ;
}
SDL_Quit() ;
return 0 ;
}
///////////////////////////////////////////////////////////////////////////////////
We'll go over the code step-by-step. iostream is included because cerr will be used--which is console error, an output stream. Thus, instead of using the entire namespace the compiler will only use cerr. The main entry point function is similar to SDL_main so don't become confused by this. After the opening try block, a graphical surface structure is declared that will be used to create the main window in. SDL_Init is checked for failure and if so, a string is thrown (SDL_GetError() returns a pointer to char). Whatever string SDL_GetError() returns is later cought below. Afterwards, a surface is created and checked if it's NULL. If it is NULL, then an error string is thrown.
Inside the catch scope, whatever error string that was thrown in the try scope is outputted. After all of this, SDL is shut down and control is returned. When you run the application you will notice that a blank black 640x480 window appearing for a split-second and then disappear, which concludes the program. An easy solution to this if it were a console app is to use cin.get(), but that wouldn't work in SDL. Fortunately, there are functions similar to this in SDL's facility that will hold up the window until the user hits a certain key. Doing this with SDL is much more complex than using cin.get().
First off we will need to create a while loop. A chunk of the program will run within this loop every frame until previously declared boolean variable changes value. Evidently, we need something that will change the value of this boolean variable; perhaps the user hits the ESC key? SDL has a struct called SDL_Event and with an instance of it, we could poll events (i.e. look for events or more specially, input events). If a particular event occurs then we could process it. For the following code fragment, replace the line in the previous code that has "//code here" with this:
SDL_Event Event ;
bool bFinish = false ;
while( !bFinish )
{
while( SDL_PollEvent(&Event) )
{
if( Event.type == SDL_QUIT )
bFinish = true ;
}
}
With the above code fragment added to the one before, your SDL app should run until you hit the 'X' button on the upper-right corner of the window. All your window should show is nothing but black. As you can see, bFinish is initialized to false prior to the while loop. This method is called priming the pump. The while-loop will go on as long as bFinish is false. The user hitting the 'X' button on the window will cause the variable to change value to true. That, of course, will conclude the while-loop. In the while-loop scope, there resides another while loop. The inner loops polls for events. When there isn't an input event going on then that inner loop will conclude. But in case something did happen we have an if statement to check what happened. The if statement checks if the event type was an SDL_QUIT. Hitting the X button on the corner of the window will cause an event type of SDL_QUIT.
It would be more fun if we could run the program until the user hit's a button. Say...the ESC key?
while( SDL_PollEvent(&Event) )
{
if( Event.type == SDL_QUIT )
bFinish = true ;
if( Event.type == SDL_KEYDOWN )
{
if( Event.key.keysym.sym == SDLK_ESCAPE )
bFinish = true ;
}
}
All SDL key names begin with SDLK_ and you should read the SDL documentation for a list of all the keys supported. With the second if statement added your SDL app will run until either the user hits the ESC key or if the user presses the X button on the upper-right corner of the window. You may be wondering how to change the window caption title. Luckily, SDL provides window management routines. Changing the window caption is as simple as calling SDL_WM_SetCaption( "My SDL Application", NULL ). The first parameter is a string representing the window title you want to change to while the second parameter is another string representing the filename of the 32x32 icon you want to use.
Remember when you created the graphical surface structure with SDL_SetVideoMode? And how the fourth parameter uses the SDL_DOUBLEBUF flag? Well that is indicating that your application is going to use what is called double buffering. Your SDL app will have two buffers (a memory container to temporarily store data while other data is being processed) used for rendering. All drawing will take place in the back buffer and when you're done drawing you present the data that was in the back buffer to the primary buffer so that the user/game could see it. Flipping buffers is the action of presenting the back buffer. To perform this in SDL you simply make a call to SDL_Flip:
SDL_Flip( Screen );
Since you're not rendering anything now you will not see the true effect of this but you will see it when you try to move sprites around (i.e. perform animation). Another thing you should know about before getting into rendering images is the window to a particular color you want. To perform this, you simply make a call to SDL_FillRect treating the entire window as a rectangle to be filled. The first argument is the destination graphical surface structure you want to fill out. The second argument is a pointer to the SDL_Rect you want to fill out. You specify this if you want a section of the surface to be filled rather than the entire thing. You pass a NULL if you want the entire surface to be filled out. The third and last parameter is the 32-bit integer representing the color to use to fill the rect. You could pass SDL_MapRGB--a function that maps an RGB color value to a pixel format--as the third parameter or you could just use a 32-bit hex value. The following code fragment shows two different methods for filling the entire screen with blue.
SDL_FillRect( Screen, NULL, SDL_MapRGB( Screen->format, 0, 0, 255 ) ) ; SDL_FillRect( Screen, NULL, 0x0000FF ) ;
The last two digits of the hexadecimal value represent blue. The first two digits representing red and lastly, the middle two digits represent green. You could play around with the values to get an idea of that. Most SDL programmers prefer the first method because it's "safer" while the few prefer the second method simply because there is less typing involved. If you're able to figure out the color you want with the second method, then there really isn't any harm in using it. But if you're just starting out with SDL and aren't too comfortable just go along with the first method.
The Images
By now, you should be able to initiate the SDL library, poll for events, clear the screen to whatever color you want, and flip buffers. But now is the time to do something really fun and that's to render bitmap images. Go in your project folder (not your solution) and create a new folder called assets. Now, if you're on Windows (preferably Windows XP) go to Start->Run and type in mspaint. Create something random and save it as a bitmap (.bmp) file in your assets folder.
Back in MSVC++.NET, right-click your project in the solution explorer and then go to properties. While the arrow in the left panel is on General make sure in Intermediate Directory that is says $(ConfigurationName):
I assume that the bitmap image you created in mspaint is in your assets folder. Before you're able to render it in your SDL application you need to load it onto SDL's facility. As you might have guess it, you need to create another graphical surface structure. You declare an SDL_Surface* and make a call to SDL_LoadBMP, where it's only parameter is a string containing the bitmap's file location. What SDL_LoadBMP does is load a .bmp file into an SDL_Surface. Afterward, you could check for validity just like we did with the video screen/window. Observe the following code fragment:
// Loads Mario.bmp into an SDL_Surface
SDL_Surface* bmpMario = SDL_LoadBMP( "assets/Mario.bmp" ) ;
if( bmpMario == NULL ) // Check for validity.
throw SDL_GetError() ;
// render image
SDL_FreeSurface( bmpMario ) ; // Deletes the surface at the end of the program.
Checking for validity is crucial especially when working on large projects. You need to always check whether or not the images were loaded successfully. If you try to render an image you thought you had loaded, then some critical issues will arise. As you can see in the previous code fragment we loaded a bitmap file into an SDL_Surface, checked if it was loaded successfully, rendered the image, and then freed it. Whenever you load an image into SDL you need to free/delete it when you're done. You do not, however, free a surface that was created via SDL_SetVideoMode because SDL_Quit will do it for you. But for everything else calling SDL_FreeSurface is a necessity.
To render an image you will have to construct an SDL_Rect instance representing the image's dimensions. This rect image will be the source rect. You then have to construct another SDL_Rect presenting where on the destination surface (the window) you want the image blitted. This will be the destination rect. Now both rects and surfaces will be used in a function call to SDL_BlitSurface. This function performs a fast blit from the source to the destination surface. Think of double buffering where you take the data from the back buffer and present it to the primary. With SDL_BlitSurface, you take the data from the source surface (the source rect determines how much you take) and transfer it onto the destination source (again, the destination rect determines how much). Let's take a look at an example. Here I use a sub-program--a c++ function--to do the job:
// I use typedef to refer to the data type easily.
typedef SDL_Surface* SURFACE ;
void DrawImage( SURFACE src_surface, int x, int y, SURFACE dest_surface )
{
// Here, we construct a rectangle representing the entire image.
SDL_Rect src_rect ;
src_rect.x = 0 ;
src_rect.y = 0 ;
src_rect.w = src_surface->w ;
src_rect.h = src_surface->h ;
// We construct a rectangle representing where we want the
// image to be located on the destination surface.
SDL_Rect dest_rect ;
dest_rect.x = x ;
dest_rect.y = y ;
// This is where all the magic happens. The source surface
// is mapped onto the destination surface.
SDL_BlitSurface( src_surface, &src_rect, dest_surface, &dest_rect ) ;
}
Using this function should be simple and self-explanatory. You simply invoke a call to this function after you clear the window but before you flip the buffers. The first parameter is the surface which contains your image file. The next two parameters is the position in the screen's coordinates with the origin (0,0) being at the upper-left corner of the window. When you studied geometry in school you were alway taught that the origin was in the center of the graph. Well in computer graphics (2D anyways) the origin is in the upper-left corner of the moniter/screen.
SDL_Image
Working with only bitmap images may be boring. You might prefer a different image format over bitmap like PNG or JPEG. There is a sub-library that was designed to work with SDL called SDL_image. With SDL_image, you are able to load image formats other than bitmaps. To get the development kit go here: SDL_image (http://www.libsdl.org/projects/SDL_image/). Concern yourself with the links below Binary with devel in the name. Just like the steps in #The Installation section above, extract the files in the compressed archive in whereever the SDL includes and libs are. Copy the .dll files into your system32 folder or just make sure it's in the same folder as your executable. After that, add SDL_image.lib to additional dependacies in your project's property page. Another important modification that is needed to make to your program is to include the file SDL_image.h
SDL_image is a relatively small library so there isn't much to learn. Its main image loading routine is IMG_Load which is basically the same as SDL_LoadBMP but IMG_Load can load more image formats beyond bitmaps:
// Loads Mario.png into an SDL_Surface
SDL_Surface* imgMario = IMG_Load( "assets/Mario.png" ) ;
if( imgMario == NULL ) // Check for validity.
throw IMG_GetError() ; // Same as SDL_GetError but reports problems with image files.
DrawImage( imgMario, 0, 0, Screen ) ; // Invoke the drawing function we made earlier.
SDL_FreeSurface( imgMario ) ; // Deletes the surface at the end of the program.
Practice Exercises
These exercises are optional, but highly recommend, in order to put what you have just learned into practice. Since this tutorial covers the foundation of SDL, these exercises can be difficult for beginners. Feel free to e-mail me all of the solutions. Or you could post the solution in DevMaster.net's message board (http://www.devmaster.net/forums).
- Write a image drawing function as short as possible (shorter than the one in this article is possible :)).
- Create three small images, each with a different image format. Render all three images in your SDL app side-by-side.
- Write a class that wraps SDL's image routines. Have an Init method that returns bool and a draw function with less than four arguments. Have the destructor free the surface.
Conclusion
I hope this introductory tutorial helps you out a great deal. After fully reading this and comprehending all of the material you should be ready to play around and further explore the SDL API. You may also download the source code for this tutorial below.




