PROGRAMMING Creating a game with SDL

Creating a game Programming with SDL

In this, the first in a series of articles, type fonts (TTF). Most of these libraries have been submitted by end users and Steven Goodwin looks at SDL.What it do not form part of the main SDL pack- age. This is not a problem in itself, but if is. How it works. And more impor- your primary purpose is to use SDL for cross-platform development work, be tantly, how to use it to write a brand sure to check the libraries’ availability new game, Explorer Dug. for your particular platform, since not all are fully supported. BY STEVEN GOODWIN The Windows heritage of SDL is very evident within the API, as much of the Figure 1:The opening image terminology (and many of the functions) have very close equivalents in DirectX. DL stands for Simple DirectMedia for , using the SDL project, by com- However, it does not emulate Windows Layer and is a cross-platform API panies that otherwise, are unwilling (or in any way. Instead, every call to the SDL Sfor programming multi-media unable) to release their source code to graphics API, for example, will make applications, such as games. It provides the world. direct use of a driver from the host oper- a stable base on which developers may Some of the most notable releases have ating system, rather from an emulated work, without being concerned with come from the aforementioned Loki system. how the hardware will deal with it – or Games and include Civilization: Call to On Windows, this means DirectX. even what hardware it is running on. Power, Descent 3 and Unreal Tourna- Under Linux, this functionality will Sam Lantinga started the SDL project ment. be provided by one of the graphic driver in 1998 as a way of a Windows Good SDL implementations are cur- libraries, such as X11 or DGA. MTRR application to the Macintosh. Soon after rently available for Linux (i386, PPC and (Memory Type Range Register, [2]) beginning the SDL project he went PS2), BSD, Windows, Macintosh OS 9 / control, for accelerated full-screen appli- to work for (the sadly lamented) Loki X, Solaris, IRIX and BeOS. Code for other cations, is also supported. See Box 1: Software, and then to Blizzard Entertain- platforms, such as the Amiga, Atari and Drivers. ment. SymbianOS, also exist, but are not cur- On a similar note, the audio API can Throughout this time he has continued rently supported directly by the core enlist the services of OSS, ESD or aRts to to work on SDL and, in the true spirit of developers. provide music and sound effects. By an open community, has been supported accessing, as opposed to emulating, the (with various platform ports and bug We Built This City system drivers, a high of perfor- fixes) by a loose knit group of developers As an API, SDL consists of five different mance can be achieved across all across the Internet. subsystems: video, audio, CDROM, joy- platforms. SDL is available under the Lesser GNU stick input and timers. Each one is Public License (LGPL), and as such does targeted at a different area of develop- Box 1: Drivers not require the source code of any appli- ment, and can be initialised and used SDL isn’t limited to X11 as an output driver. It cation using it to be released. independently of the others. In addition, can also use dga,fbcon and even aalib! To Although this might raise political con- SDL promotes the use of libraries that make use of this facility,you must make sure siderations for some people, it has meant can provide new functionality to the that the appropriate driver is compiled into the release of several commercial games basic API. SDL (which may require another ./configure, The two most common libraries are make, make install of the package).You can When builders go down SDL_mixer (which provides better audio then use the environment variable the pub they talk about handling), and SDL_Image which pro- SDL_VIDEODRIVER to indicate which driver football. Presumably you want to use. For example: vides support for a wider selection of therefore, when foot- HOR graphic file formats, including GIF, JPG, export SDL_VIDEODRIVER=aalib T ballers go down the ./explore pub they talk about PNG and TGA. The main SDL website [1] A list of the drivers provided with SDL can be builders! When Steven Goodwin goes lists 76 different libraries currently in found by typing: down the pub he doesn’t talk about production, providing a wide range of THE AU football. Or builders. He talks about ./configure --help | grep U feature sets from basic graphic primi- computers. Constantly… enable-video tives, to networking and support for true

66 August 2003 www.linux-magazine.com Creating a game with SDL PROGRAMMING

Do The Digs Dug tar xfz SDL-1.2.5.tar.gz will search. All other SDL header files SDL has many uses, and has bestowed are incorporated inside this one, to limit life upon emulators, music editors, DivX and installed with the equally familiar: maintenance. We will also need to link players and video processing tools. How- the main SDL library (libSDL) into our ever, it is within the field of games that ./configure application. Initially we shall only be SDL has risen above its peers. There are make using functionality from this library. hundreds of SDL games available on the make install We’ll add other libraries as time goes on, Internet and most of these are available and our requirements grow. Until then, with full source code. We shall be adding By default, this will place the required both of the above demands can be satis- to this collection with a small platform library and include files into /usr/local/ fied on the command line with: game called Explorer Dug. It will be pro- lib and /usr/local/include/SDL respec- grammed in ‘C’ (as it is probably the tively, although this can be changed by gcc -I/usr/include/SDL -lSDL U most widely understood), although SDL starting the process with: test/testsprite.c itself can be written in many others, such as PHP, Ruby, Perl, Java and LISP. ./configure -prefix=/put/sdl/U If you change (or forget!) the location of We shall primarily focus on the features somewhere/else your include and library paths, you can of SDL and how to use them to produce use the sdl-config program to provide a game. We shall also highlight the areas There are a wealth of other configuration this information (see Box 2: SDLCON- of game development that require more options available here which allow you FIG). work from us, the programmer. OK? to enable (or disable) various drivers Good! Let’s start… (such as ALSA), or include framebuffer All Around the World support. Apart from providing additional Our first foray into SDL programming Thunderball drivers, you should have little cause to will be the games “Welcome” screen. The first task is to download and install re-./configure since SDL is very stable This introduces a number of fundamen- SDL. The current stable version is 1.2.5, and easy to setup, and invariably works tal concepts, such as surfaces, blitting and is available from [3] as a gzipped out of the box. and screen updates, that will reoccur tarball. In addition to the source code The SDL package also contains a com- throughout any SDL application you distribution there are two different prehensive set of man pages (section 3) write. binary versions available: runtime and detailing the parameters and available As you read the code, you will notice development. Naturally, the latter pro- flags for each function. that every SDL function and structure vides better debugging facilities which is begins with the SDL_ prefix. In addition, more useful for us, as developers. How- Build that wall we shall wrap these functions up inside ever, I recommend working from source The SDL package comes with a small our own (all prefixed with ‘ex’) to encap- as this allows us better customisation, test suite that checks the integrity of sulate more functionality and provide a and full use of a debugger to step into each subsystem. These are held in the common place where game code and the SDL code itself. This is not only help- test directory, and provided the install SDL code meet. This will help in debug- ful in tracking down bugs, but can also succeeded, should ‘just work’. In cases ging. be very illuminating – especially some of where things do not work as expected, The first stage is to initialise SDL. the comments! look for the error messages each test out- There are two functions to do this, The tar.gz is unpacked in the usual puts to the command line as this can tell SDL_Init and SDL_InitSubSystem. way: you if there’s a problem with your hard- SDL_Init can initialise one (or more) ware, or its setup. Fortunately, if your subsystems and should always be the Box 2: SDLCONFIG machine is capable of running a desktop, first SDL function in your program. sdl-config is a small program that comes it should be up to spec for anything Although you can initialise additional bundled with SDL to report the base,or pre- you’ll be doing with SDL! subsystems later you must always start fix,location of your SDL installation. It can All SDL programs can start with either with an SDL_Init, because this contains also be used to give you the correct flags for the line: other initialisation components that both the compiler and linker. SDL_InitSubsystem does not have. An $ sdl-config #include "SDL.h" example of these components would be: Usage: sdl-config [-- the threads are initialised, the last error prefix[=DIR]] [--exec- or: code is cleared, and the parachute is prefix[=DIR]] [--version] (optionally) deployed (see Box 3: Para- [--cflags] [--libs] [--static- #include "SDL/SDL.h" chute). libs] $ sdl-config --libs The former is preferred for reasons of SDL_Init(SDL_INIT_VIDEO | U -L/usr/local/lib -Wl,- cross-platform portability, although it SDL_INIT_AUDIO); rpath,/usr/local/lib -lSDL - does require you to add the SDL direc- lpthread tory to the list of include paths that gcc Is exactly the same as:

www.linux-magazine.com August 2003 67 PROGRAMMING Creating a game with SDL

Box 3: Parachute SDL_Quit(); Box 4: Video Functions } The parachute is a way of trapping signals These functions can provide some useful (such as segmentation faults,bus errors, In the same way as you can initialise insights into what video modes are possible broken pipes and floating point exceptions) from SDL.The man pages have full proto- which give SDL an opportunity to call subsystems individually, you can also types and explanations for these functions. SDL_Quit and free its resources.The close them down in a similar manner: parachute is automatically installed on SDL_GetVideoInfo Retrieve information about the initialisation. If you don’t want to make use SDL_QuitSubSystemU video hardware of it,you should start your application with: ( SDL_INIT_VIDEO ); SDL_VideoDriverName Get the name of the video driver SDL_Init( SDL_INIT_VIDEO | SDL_ListModes Enumerates all available SDL_INIT_NOPARACHUTE); There is no harm in closing down a sys- screen resolutions Your application can provide its own signal tem that hasn’t been initialised, although SDL_VideoModeOK Determines if a specific video handlers if necessary.Although remember the final SDL function should always be mode is available that for portability,not all platforms support SDL_Quit, as this will remove the para- all signals and some platforms may not chute signal handlers and terminate any support signals at all. remaining threads. The screen is initialised with the spe- cial command SetVideoMode, although SDL_Init(SDL_INIT_VIDEO); The 1st Time I Saw Your Face the surface it returns is no different to SDL_InitSubSystemU All graphics in SDL are stored in ‘sur- any other. (SDL_INIT_AUDIO); faces’. It is a term borrowed from DirectX, and means ‘graphical memory’. SDL_Surface *pScreen; At the moment, we only have a use for The screen is a surface. The background pScreen = SDL_SetVideoModeU the video subsystem, so we will initialise picture is a surface. Our Explorer Dug (640, 480, 16, SDL_HWSURFACE); that and check for any errors. character is a surface and so on. Surfaces SDL adopts the standard of using can be created and destroyed many Here we’ve set up a 640x480 video sur- negative values to indicate an error code. times throughout the life of the game. face. The 16 refers to the bit depth, The value of this number indicates the However, there can only be one screen which reflects the total number of precise error. surface. It is this screen surface that will colours available. In this case, 65536 However, because humans are more appear on your monitor, and so anything (2^16), which is a good compromise au-fait with words, SDL also provides the that you want to be visible must be between visual quality and speed. The SDL_GetError function to return the tex- drawn onto this surface. usual options are 8, 16, and 24, although tual name of the last error. You should Surfaces can be of any arbitrary size, SDL will also support 12 and 15 (see Box use this after any function that generates and are held in one of two places: video 5: Depth Charge). an error code. It should also be called memory, or system memory. Video Usually in software development the immediately, because if a subsequent memory is the fastest, as it lives on the programmer is expected to work within SDL function fails the previous ‘last graphics card itself. This is also known the limits of the current hardware, and error’ will get lost. as a hardware surface. System memory degrade gracefully. Games, however, fol- Closing the system down is as simple (or software surface) is part of your nor- low their own set of rules! We’ve chosen as calling the SDL_Quit function. mal RAM, and marked internally by SDL the screen size and bit depth to show off as ‘holds graphic data’. our graphics in the best possible light. If void exRelease(void) { Listing 2: First screen 01 SDL_Surface *pImg; Listing 1: Initialise 02 01 BOOL exInitSDL(void) 03 if ((pImg = SDL_LoadBMP("welcome.bmp"))) 02 { 04 { 03 if 05 /* We have successfully loaded the image */ (SDL_Init(SDL_INIT_VIDEO) < 0) 06 04 { 07 SDL_BlitSurface(pImg, NULL, pScreen, NULL);/* blit 05 everything */ fprintf(stderr, "Couldn't init 08 SDL_UpdateRect(pScreen, 0,0,0,0); /* the whole SDL video: %s\n", screen */ SDL_GetError()); 09 SDL_Delay(2*1000); /* 2 seconds 06 return FALSE; */ 07 } 10 08 11 /* And then we must free the surface */ 09 return TRUE; 12 SDL_FreeSurface(pImg); 10 } 13 }

68 August 2003 www.linux-magazine.com Creating a game with SDL PROGRAMMING

there aren’t enough colours, for example, Also, don’t create a software surface Everything I Own essential graphics like the key or the exit for the screen just because you’re using In addition to the screen, we need some gate might be difficult (or impossible) to X11. If it is appropriate to create a hard- surfaces of our own, onto which we can see. This is unfair on the player and so ware surface for a particular buffer (as it we draw our graphics. There are two it’s acceptable to exit the game if this res- is for the main screen), you should still main ways of creating our own surfaces. olution is unobtainable. request SDL_HWSURFACE. You might be The first is to manually create a surface On the other hand, if you are using using X11, but other users might not be, with one of the two following functions: SDL for non-game applications, or you’re and you want to give them the best game not worried about image degradation, you can. SDL_Surface *SDL_CreateRGBU you can use whichever video mode the It is also this final parameter that Surface(Uint32 flags, int widthU user has set up on their desktop by set- allows your window to resize , int height, int depth, Uint32U ting the bit depth to zero: (SDL_RESIZABLE), exist without a frame Rmask, Uint32 Gmask, Uint32 U (SDL_NOFRAME) or support Open GL Bmask, Uint32 Amask); pScreen = SDL_SetVideoModeU rendering (SDL_OPENGL). Often, the (640, 480, 0, SDL_HWSURFACE); first instinct with a game is to make it SDL_Surface *SDL_CreateRGBU printf("The bit depth is set toU full screen (SDL_FULLSCREEN). SurfaceFrom(void *pixels, int U %d\n", pScreen->format->U However, during development we shall width, int height, int depth, U BitsPerPixel); remain with a windowed version as it is int pitch, Uint32 Rmask, Uint32U easier to work with. Additionally, it is Gmask, Uint32 Bmask, Uint32 U For other interesting information con- possible to render your machine unus- Amask); cerning the video component, see Box 4: able if you are running the game Video Functions. full-screen, since it is not always possible These are rarely used because you have The final parameter is a set of bitwise to switch to another virtual desktop and to write each pixel of the image (in the flags. These specify attributes for the kill the program! This can also happen if right format) directly into the surface. surface, and the window in which it is you hit a breakpoint in the debugger, or Additionally, there are usually a lot of displayed. The SDL_HWSURFACE flag fail to provide a means of quitting the pixels! requests that the screen surface should game. Remember that, like a debugger, A much easier way is to draw images be created in video memory, if possible. the SDL parachute traps various signals with the GIMP, and save them as a BMP. If we find however, that the driver does (like Ctrl+C), and so is not always You can then load the image, format it not support hardware surfaces (as in the available. correctly, and copy it into a brand new case of X11), or that the hardware mem- Every surface that is created (and that surface. SDL provides this rather gener- ory is full, the surface will be still be includes the screen) must be freed with ous functionality with a single function created – but in software. Do not be con- the SDL_FreeSurface function, once it call: cerned about where the surface is has ceased to be of any use: created, all functionality is identical in SDL_Surface *SDL_LoadBMPU either case. SDL_FreeSurface(pScreen); (const char *file);

Box 5:Depth Charge When the bit depth is something other than issues. However, this is not something you now,can still produce high quality graphics 8, the pixel data is stored in a “packed for- often have to worry about,since SDL will on very limited hardware.You can also pro- mat”.This is so-called because the colour is convert between the formats automatically duce a lot of clever (and very fast) effects by represented by three numbers,one for each during a blit. If you do need to understand changing the colours in the palette without of the red,green and blue components, the internal format (as we will see later in having to change every pixel on-screen. which are then packed together (in a single this series) you’ll be pleased to know that The biggest problem with 8 bit palletised Uint16 or Uint32) to illustrate the colour. SDL provides a SDL_MapRGB function to help surfaces is that you can only have 256 specific With a bit depth of 24 (sometimes called you. In truth, this problem with packed for- colours for the whole image. If your back- true colour),each of the RGB components mats also manifests itself with 24 bit colour ground uses one set of 256 colours,and your occupy 8 bits.This is highest resolution modes,but that is less pronounced. main character uses 256 different ones, then you’re likely to need,but is usually an overkill The rules change when specifying a bit some of the colours will get changed auto- for games.Things get more complicated, depth of 8. Instead of splitting the 8 bits up matically by SDL.While this is no bad thing, unfortunately,with the most common into RGB components,each value (from 0 to the results can be unpredictable,and there- packed format,16-bit colour. Here, the RGB 255) references a separate table that indi- fore make your artwork look a little less components use 5,6,and 5 bits each,or 5,5 cates the actual colour.This table is called impressive than it would be otherwise. On and 6,or 6,5 and 5! The exact format of the the palette (which uses a full 24 bits to hold the positive side, however,moving 8 bit data surface can vary depending on where it is the colour information),and can be setup around in memory is twice as fast as moving stored,and which graphics card you are with the SDL_SetColors and SDL_SetPalette 16 bit data,and so is often a good trade off using.The order will also vary if you are using functions. Although it has fallen out of use for handheld devices. a PowerPC,for example,because of endian by games programmers for several years

www.linux-magazine.com August 2003 69 PROGRAMMING Creating a game with SDL

SDL only provides support for BMP files This is very useful, as we can draw We can request that only a small rectan- within the standard library. Although graphics at positions like (-4, -2) or (600, gular portion of the screen be updated SDL_image provides support for a num- 450), which allow our graphics to slide (which is naturally faster if we are only ber of other formats, our game will be smoothly off the screen. SDL will work changing a small portion of the surface). limited to BMPs, and make use of several out how to render the visible portion As an alternative, we can choose to pass surfaces loaded in this way. Each surface optimally. zeroes to each of the parameters and will hold a particular set of graphics: one update the whole area in one hit. For for the backdrop, one for the player, one SDL_Rect srcrect = U example: for the enemies, and so on. {600, 450, 64, 64}; The final game image will then be con- SDL_UpdateRectU structed by a number of surface- Will only blit from 600, 450 to 639, 479 (pScreen, 0, 0, 0, 0); to-surface copies (the screen is just a despite the extents being 663, 513. surface, remember), as governed by the If you wish to blit the entire surface We’ve now blitted some graphics to our game logic. This copying process called (as we do for the welcome screen) then screen and caused the monitor to update blitting, and is short for BLock Image pass srcrect as NULL. itself with our image. So, if we add a Transfer. Because we are unable to stretch and small delay, have completed our first blit, only the x,y co-ordinates of dstrect screen. Ballroom Blitz are used, and refer to the top left corner Creating a game level, or animating We can blit between any two surfaces, of the destination area. Passing NULL as characters, is simply a case of more blits, and that includes from one surface to the destrc will tell SDL to start blitting in more places, from more surfaces. It itself. We can also blit from one portion from (0, 0). If the extents of the blit involves nothing more than we have of one surface, to a different portion of exceed the destination surface the image already seen. another surface. The only limitation is will be clipped as normal. This clipping Unfortunately, there are good, and that size of both portions (the source, area can be limited artificially with: bad, ways of doing this. Next month we and the destination) must be of the same will look at the good ways, showing you size. The blit operation can (if both sur- void SDL_SetClipRect(U how surfaces can be used to create the faces are in video memory) be SDL_Surface *surface, U game screen, and animate some bad performed by the hardware, which is SDL_Rect *rect); guys! ■ incredibly fast. Not all graphic drivers, however, support , And any future blit to that surface will Types so for a brief reminder, see Box 1: DRI- only occur to the area within specified To ensure cross-platform compatibility, the VERS. rectangle. As above, passing a NULL as standard C types like short and int,are not There is only one function for blitting. the SDL_Rect pointer will tell SDL to use used. Instead,SDL defines its own,which are It sits alone, and so should be easy to the entire surface area, which effectively then used throughout the API.These types befriend! removes the clipping area. This feature can then be changed on new platforms to allows us to keep an area of the screen ensure that a Uint16,for example,is always int SDL_BlitSurface(U pure and sacred, regardless of what 16 bits. U SDL_Surface *src, game components try to blit themselves typedef unsigned char Uint8; U SDL_Rect *srcrect, there, to preserve information such as typedef signed char U SDL_Surface *dst, the score, or number of lives. Sint8; SDL_Rect *dstrect); typedef unsigned short Uint16; Get Fresh at the Weekend typedef signed short Sint16; Since this function has no capacity to So we can now load an image into a sur- typedef unsigned int Uint32; stretch or rotate the surface you may face, and blit that surface to another typedef signed int wish to resort to either the SDL_gfx [4], surface (such as the screen). In order to Sint32; or SGE [5] libraries. We shall not use see our handy work, we must impart one either of them here however, as stretch- more revelation. The screen surface we ing bitmaps is a very time consuming have setup is not the image that is visible INFO process and we’re aiming for maximum in the window. That image is controlled [1] SDL:http://www.libsdl.org speed. We shall therefore generate all by the driver (such as X11), not SDL. In [2] Memory Type Range Register:http:// our graphics to the exact size that we order to see the image, we must tell SDL www.linuxvideo.org/user/mtrr.txt need them. to update the driver with the graphics [3] SDL download:http://www.libsdl.org/ SDL_Rect is a simple structure to indi- from our surface. To do this we use the download-1.2.php cate the size of the area we want to blit, SDL_UpdateRect function. [4] SDL_gfx library:http://www.ferzkopp. given as x, y, width and height. SDL will net/Software/SDL_gfx-2.0/ automatically clip our co-ordinates inter- void SDL_UpdateRect(U [5] SGE library:http://www.etek.chalmers.se/ nally if we exceed the boundaries of SDL_Surface *screen, Sint32 x, U ~e8cal1/sge/index.html either the source, or destination, surface. Sint32 y, Sint32 w, Sint32 h);

70 August 2003 www.linux-magazine.com