Exploring SDL

From Wikibooks, open books for an open world
Jump to: navigation, search


SDL Overview[edit]

Cross-platform graphics[edit]

SDL is a cross-platform application programming interface (API) that allows you to code graphics across multiple platforms. Much of its workings is done behind the scenes leaving you, the programmer, an easier interface to those internal workings.

Cross-platform programming is achieved by dynamically checking which Operating System the user is running. This is done by the use of conditional macros:

#ifdef _WIN32
/*Windows specific code here*/
#endif
 
#ifdef _APPLE_
/*Macintosh OS code here*/
#endif
 
#ifdef _linux_
/*Linux code here*/
#endif

These macros check the existence of predefined variables stored within the OS's compiler libraries. Depending on which are defined, the corresponding code would be executed for that particular system. This method also prevents OS specific code from conflicting with each other.

The reason for this separation is because Operating systems have different ways of displaying graphics. Even though the code is different in each OS, most perform similar tasks such as creating a window, rendering to the window, grabbing user input, etc.. SDL brings these tasks together on a unified interface to allow you to basically code, compile, and run your program on multiple platforms.

SDL Video[edit]

SDL Video Surfaces[edit]

A video surface is a block of video memory that holds the pixel data. Each pixel is represented by a memory location within the memory block the surface is stored. This block of memory usually is a simple array, sometimes called linear memory. The main properties of a surface are its width and height in pixels and also its pixel format, which is the amount of data set aside for each pixel. This pixel size determines how many colors are available to be displayed onto the surface.

Pixel Format Size (number of colors)
Indexed (palette) 1 byte – 256 colors
High Color 2 bytes – 65536 colors
True Color 3-4 byes – 16777216 colors

(extra byte used for alpha values, or unused)

Main Display Surface[edit]

The main video surface refers to the image on the user's screen. Like SDL Video surfaces, the main properties of this surface are its dimensions (width and height) and its pixel format.

Hello SDL intro program[edit]

Sdl Intro Screenshot
#include <stdio.h> 
#include <stdlib.h>
#include "SDL/SDL.h"
 
int main(int argv, char **argc)
{
	SDL_Surface *display;
	SDL_Rect sDim;
 
	/*initialize SDL video subsystem*/
	if(SDL_Init(SDL_INIT_VIDEO) < 0){
		/*error, quit*/
		exit(-1);
	}
 
	/*retrieve 640 pixel wide by 480 pixel high 8 bit display with video memory access*/
	display = SDL_SetVideoMode(640, 480, 8, SDL_HWSURFACE);
	/*check that surface was retrieved*/
	if(display == NULL){
		/*quit SDL*/
		SDL_Quit();
		exit(-1);
	}
 
	/*set square dimensions and position*/
	sDim.w = 200;
	sDim.h = 200;
	sDim.x = 640/2;
	sDim.y = 480/2;
 
	/*draw square with given dimensions with color blue (0, 0, 255)*/
	SDL_FillRect(display, &sDim, SDL_MapRGB(display->format, 0, 0, 255));
 
	/*inform screen to update its contents*/
	SDL_UpdateRect(display, 0, 0, 0, 0);
 
	/*wait 5 seconds (5000ms) before exiting*/
	SDL_Delay(5000);
 
	SDL_Quit();
 
	exit(0);
}

Setting up SDL2[edit]

As of October 2014, SDL2's current stable release was 2.0.3. Setting up this library is similar to the SDL 1.2 setup, with a minor bug that is supposed to be resolved in the next release (2.0.4). Until then, however, there is a workaround. The bug is in the SDL2 header include folder, specifically the SDL_platform.h file. This error (along with its solution) is better explained here. Here is the link for the replacement file: SDL_platform.h.

Compiling SDL intro[edit]

Copy the SDL header files from the development package into your compiler's include directory. For better organization, create a new folder in your include directory called “SDL” and copy all the header files there.

To link the SDL library from a gcc compiler, use this syntax:

gcc sdl_prog.c -o sdl_out -lSDL

With Windows, you will also have to link the SDLmain library:

gcc sdl_prog.c -o sdl_out -lSDL -lSDLmain


Program Explanation[edit]

At the top of your SDL program, include standard C header files to allow input/output as well as memory allocation:

#include <stdio.h> 
#include <stdlib.h>

Assuming you copied the SDL header files to the SDL folder in your include directory, put this header inclusion following the C headers:

#include “SDL/SDL.h”

Create the main entry point function and include its command line parameters. SDL requires these parameters internally for initialization.

int main(int argc, char **argv) 
{

Initializing SDL is done with the SDL_Init function which takes a single parameter which system to initialize.

int SDL_Init(Uint32 flags);

If the function succeeds the return value is 0.l If, for any reason, initialization fails the return value is -1. The systems available are: Video, Audio, CD-Rom, Timer, and Joystick. You specify the system you want initialized with these flag values which can be OR'ed ( | ) to allow initialization of multiple systems:

SDL_INIT_VIDEO
SDL_INIT_AUDIO
SDL_INIT_CDROM
SDL_INIT_TIMER
SDL_INIT_JOYSTICK

For now, all we need to initialize is the video system, so we include just that flag as the parameter for the SDL_Init function. Also, we check to make sure the return value not -1, so we know it successfully initialized. If initialization failed we immediately exit the program with a call to the C function exit:

	if(SDL_Init(SDL_INIT_VIDEO) < 0){
		/*error initializing*/ 
		exit(0)
	}

Note that calling the SDL_Init function, regardless of supplied flags, also automatically initializes the Event handling, File input/output and Thread systems, which we will cover later on.


Now we come to initializing the main display surface. The main display is represented as a SDL Surface. Lets take a quick glance at the SDL_Surface structure:

SDL Surface Structure
 
typedef struct SDL_Surface {
        Uint32 flags;                           /* Read-only */
        SDL_PixelFormat *format;                /* Read-only */
        int w, h;                               /* Read-only */
        Uint16 pitch;                           /* Read-only */
        void *pixels;                           /* Read-write */
 
        /* clipping information */
        SDL_Rect clip_rect;                     /* Read-only */
 
        /* Reference count -- used when freeing surface */
        int refcount;                           /* Read-mostly */
 
        /* This structure also contains private fields not shown here */
} SDL_Surface;


  • flags – Sets certain capabilities for the surface to support. This will be covered when we set the display surface
  • format – Description of pixel data
  • w,h – Dimensions (width and height) of surface
  • pitch – Length of each line of the surface (scanline). This is measured in bytes. Note that the pitch doesn't always match the surface width.
  • pixels – The actual surface data to be used for rendering.
  • clip_rect – Clipping rectangle for restricting the drawing of the surface to set dimensions.

To retrieve the main video display with a given bit depth and dimensions, you use the SDL_SetVideoMode function:

SDL_Surface *SDL_SetVideoMode(int width, int height, int bpp, Uint32 flags);

Width and height refer to the pixel dimensions of the screen you wish to set. The bits per pixel can be set to the standard 8, 16, 24, 32 and also 0 which matches the user's current display bit depth. The flags parameter refers to extra capabilities you wish the surface to support. Here I will cover a few of the main ones:

SDL_HWSURFACE SDL_HWSURFACE allows storage of surface data (ie. Screen, bitmaps, fonts) directly onto the video memory. SDL_SWSURFACE stores surface data on main system memory, which tends to be slower.
SDL_DOUBLEBUF This allows double buffering support for smoother animation. Note that this flag only works if OR ed with the SDL_HWSURFACE flag. One can also achieve this effect without specifying this flag as will be shown later.
SDL_FULLSCREEN Allows SDL to take control of the windowing system and display, at the given resolution, the whole screen. Upon exit, SDL returns control to the OS and its default resolution.
SDL_HWPALETTE Gives access to the color palette. Only works in 8-bit indexed mode.

In this example, the only flag we set is SDL_HWSURFACE flag to allow storage of SDL_Surface data onto video memory instead of main system memory, which offers better performance. If a user's machine is not able to support video memory storage, SDL will fall back to storing on system memory.

This function, upon success, will return a pointer to the SDL_Surface requested. If, for any reason, it was unsuccessful, it will return NULL. Be sure to always check the return value to be sure the video mode has been set:

	if(display == NULL){ 
		/*quit SDL*/ 
		SDL_Quit(); 
		exit(-1); 
	}

The function SDL_Quit shuts down all SDL subsystems you initialized with the SDL_Init function. Since we only initialized video, SDL_Quit will only shut down that system.

The next bit of code sets the SDL Rect structure to be used to determine where to draw.

typedef struct{
  Sint16 x, y;
  Uint16 w, h;
} SDL_Rect;
* x, y – Position of upper left corner of rectangle in relation to the display surface coordinate system.
* w, h – Width and height of rectangle

The display is surface is set up as a grid with the origin (x=0, y=0) being the upper-left hand corner of the display. Each entry to this grid is the color value of the pixel at that location. We defined the rectangle in this example to position it's upper left corner (origin) at 320 to the right and 240 pixels down with a width of 200 pixels and a height of 200 pixels.

	/*set square dimensions and position*/ 
	sDim.w = 200; 
	sDim.h = 200; 
	sDim.x = 640/2; 
	sDim.y = 480/2;

SDL Coordinate System

After we set the dimensions of the rectangle, we are able to fill it with a solid color using the SDL_FillRect function.

int SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color);
* dst – SDL_Surface pointer to draw to. Specifying the main display surface will cause drawing to the screen.
* dstrect – Rectangle structure pointer containing upper-left position and dimensions of rectangle to fill with color
* color – Color to fill rectangle structure with

Color is specified by using the SDL function SDL_MapRGB:

Uint32 SDL_MapRGB(SDL_PixelFormat *fmt, Uint8 r, Uint8 g, Uint8 b);
* fmt – Pixel structure defining pixel color, depending on the depth. Specifying the display surface's structure member format, matches the color to that display's pixel format.
* r,g,b – Individual color components to specify final color.

The returned value of this function is the color value matched to the surface pixel format specified. This value can be used as the color parameter in SDL drawing functions. Note that if the display depth is under 24 bit, this function returns the closest match to the specified color, due to the limited space in the pixel format on bit depths that small (8 or 16 bit).

/*draw square with given dimensions with color blue (0, 0, 255)*/
 
      SDL_FillRect(display, &sDim, SDL_MapRGB(display->format, 0, 0, 255));

After we draw to the main display surface, we need to instruct SDL to refresh the contents of the surface in order to ensure we get the current state of the surface. The function SDL_UpdateRect updates a rectangular portion of the given surface. If we wish to update the whole surface, we set the position and dimensions to 0, which instructs SDL to update the whole surface.

void SDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Sint32 w, Sint32 h);
      /*inform screen to update its contents*/
 
      SDL_UpdateRect(display, 0, 0, 0, 0);

After finally getting something drawn on the screen, we need to prevent SDL closing on us as soon as we run it by introducing time-based delay. For now, we will instruct SDL, after drawing to and updating the display surface, to wait 5 seconds before exiting using the SDL Time function SDL_Delay, which accepts as a single parameter the number of milliseconds (in the case of 5, 5000ms), to wait before moving on to the next line of code.

void SDL_Delay(Uint32 ms);
	/*wait 5 seconds (5000ms) before exiting*/
 
	SDL_Delay(5000);

After delaying the application, we close all SDL systems (for this example, just the video subsystem) with the SDL_Quit function and exit the program with a success code (0):

void SDL_Quit(void);
	SDL_Quit();
 
	exit(0);


Looping and basic input[edit]

In the previous example, the duration of time which the program run was determined by the amount of milliseconds we want to delay. This method is effective for allowing us to view a single frame of graphics, but not if we wish to view multiple frames, such as an animation being drawn. An ideal frame loop would allow us to repeat drawing and updating of the screen at set time intervals. Lets look at a generic C implementation of a loop. Note that there is no timing mechanism in this one, causing the loop to repeat at intervals determined by the speed of the drawing and updating of the screen.

int loop = 1;
while(loop){
    if(quit_event()){
        loop = 0;
    }
    draw_and_update_screen();
}

This loops causes a screen update as quickly as the computer allows. The loop finally exits when the proqram receives an event that the user wants to quit, and the loop variable is set to 0 to evaluate to false when the loop restarts. Note that even after receiving a quit event, one more update of the screen occurs before the loop exits.

Before we implement this loop, we have to understand how SDL handles events, such as keyboard or mouse input. Events are handled using the SDL_Event union:

typedef union{
  Uint8 type;
  SDL_ActiveEvent active;
  SDL_KeyboardEvent key;
  SDL_MouseMotionEvent motion;
  SDL_MouseButtonEvent button;
  SDL_JoyAxisEvent jaxis;
  SDL_JoyBallEvent jball;
  SDL_JoyHatEvent jhat;
  SDL_JoyButtonEvent jbutton;
  SDL_ResizeEvent resize;
  SDL_QuitEvent quit;
  SDL_UserEvent user;
  SDL_SywWMEvent syswm;
} SDL_Event;

To determine what type of event occurred (ie. Key press, mouse click) we check the value of the type member. Some of the other events handled include joystick input and display resizing (changing resolution and/or bit depth at program run-time). For now we will cover just the keyboard and mouse event types:

SDL_KEYDOWN Whenever a user pushes down on a key
SDL_KEYUP User releases key
SDL_MOUSEMOTION Mouse is moved
SDL_MOUSEBUTTONDOWN Mouse button clicked
SDL_MOUSEBUTTONUP Mouse button released

If the type member has the value of SDL_KEYDOWN or SDL_KEYUP, the key member of the SDL_Event union is set, which is a KeyboardEvent structure pointer:

If we can check for just a key-press event, without even knowing the key pressed, we can implement a first version of our loop. If you recall from the example earlier, the loop's basic tasks are:

initialize boolean variable (int) to true (1) If variable is true enter loop Within loop If quit event (keyboard press) occurs set variable to false (0) Draw and update screen

Rewriting the SDL intro program earlier, to implement this loop you need to declare an SDL_Event instance along with an integer variable to hold loop value:

	SDL_Event event; 
	int loop = 1;

After the code that sets the SDL_Rect structure, we use a while conditional to check if the loop needs to be run

	while(loop){

We now check for the keyboard event. If any key is pressed, the program will set the loop variable to 0 and program will continue on the line after the while loop.

Now we fill in the SDL_Event union instance we created. The SDL_PollEvent() function takes as parameter an SDL_Event pointer (variable reference), and if an event occurred (ie. Keyboard, mouse,) the appropriate member of the event union is filled. This function returns 1 if an event is being handled, otherwise it returns 0 to indicate no events are being processed.

int SDL_PollEvent(SDL_Event *event);

event – SDL_Event pointer to fill with appropriate event data Returns: 1 if event is being processed, 0 if no events processed


/*check if events are being processed*/ 
		while(SDL_PollEvent(&event)){ 
			/*check if event type is keyboard press*/ 
			if(event.type == SDL_KEYDOWN){ 
				loop = 0; 
			} 
		}

After checking the keyboard events, we can draw and update our screen using the functions we learned from the earlier example:

		/*draw square with given dimensions with color blue (0, 0, 255)*/ 
		SDL_FillRect(display, &sDim, SDL_MapRGB(display->format, 0, 0, 255)); 
 
		/*inform screen to update its contents*/ 
		SDL_UpdateRect(display, 0, 0, 0, 0); 
 
	}

The complete loop, including initialization variables is shown below:

SDL_Event event; 
	int loop = 1;
 
	while(loop){ 
		/*check if events are being processed*/ 
		while(SDL_PollEvent(&event)){ 
			/*check if event type is keyboard press*/ 
			if(event.type == SDL_KEYDOWN){ 
				loop = 0; 
			} 
		} 
 
		/*draw square with given dimensions with color blue (0, 0, 255)*/ 
		SDL_FillRect(display, &sDim, SDL_MapRGB(display->format, 0, 0, 255)); 
 
		/*inform screen to update its contents*/ 
		SDL_UpdateRect(display, 0, 0, 0, 0); 
 
	}


This loop works well, except that it will not allow any keyboard input, since all keys, when pressed, will send an SDL_KEYDOWN event type. If we wanted to link exiting the program to a specific keyboard key press (ie. Escape key to exit), we would follow the event handler program as listed earlier, but instead of setting the loop variable to 0 when a key is pressed, we will create another if conditional to check which key was pressed and respond accordingly. Here is a generic pseudo code outline to describe the new loop we are trying to implement.


  • Declare boolean loop variable and set to true
  • Enter Loop IF loop variable IS TRUE
    • If Keyboard key was pressed (SDL_Event.type == SDL_KEYDOWN)
    • If Keyboard key pressed is <Escape> Key (SDL_Event.key.keysym.sym == SDLK_ESCAPE)
      • Set loop variable to FALSE
  • Draw and Update Screen


So, using the same code earlier, in the event handler we check if a keyboard key was pressed (regardless of the specific key). Within this If conditional, instead of immediately setting the loop variable to 0, you instead check the value of the exact keyboard key pressed. The keyboard values are stored in the SDL_Event structure SDL_KeyboardEvent member, key

typedef union{
  Uint8 type;
  SDL_ActiveEvent active;
  SDL_KeyboardEvent key;
  SDL_MouseMotionEvent motion;}

The SDL_KeyboardEvent structure is much simpler than the SDL_Event union that holds it:

typedef struct{
  Uint8 type;
  Uint8 state;
  SDL_keysym keysym;
} SDL_KeyboardEvent;
  • type - same as the SDL_Event type member that was checked in our first version of our loop. Since this is a keyboard structure, the only values for this member is SDL_KEYDOWN or SDL_KEYUP
  • state – state of the keyboard. Can be SDL_PRESSED or SDL_RELEASED. Note the similarity in function between this member and the type member
  • keysym – SDL_keysym structure that holds the actual key value that was pressed, or released
typedef struct{
  Uint8 scancode;
  SDLKey sym;
  SDLMod mod;
  Uint16 unicode;
} SDL_keysym;

The only field that is important here is the SDLKey member sym, which holds predefined identifier values for each key on the keyboard. The scancode member holds information about a keyboard key, but in a hardware dependent manner (ie. Scancode on one system isn't the same as a scancode from another). The sym member holds system independent key codes recognized across platforms. Here are some values of the SDLKey member:



SDLK_ESCAPE <Escape> key was pressed
SDLK_RIGHT | SDLK_DOWN | SDLK_LEFT Directional key pressed


So, to check if the <Escape> key was pressed, indicating user wants to quit, compare the value of the SDLKey sym member to the predefined value of the <Escape> key (SDLK_ESCAPE).

if(event.key.keysym.sym == SDLK_ESCAPE){ 
					loop = 0; 
				}

Place this within the conditional that checks for SDL_KEYDOWN event type:

	while(SDL_PollEvent(&event)){ 
			/*check if event type is keyboard press*/ 
			if(event.type == SDL_KEYDOWN){ 
				if(event.key.keysym.sym == SDLK_ESCAPE){ 
					loop = 0; 
				} 
			} 
		}

The latest loop in your program should now be implemented as so:

while(loop){ 
		/*check if events are being processed*/ 
		while(SDL_PollEvent(&event)){ 
			/*check if event type is keyboard press*/ 
			if(event.type == SDL_KEYDOWN){ 
				if(event.key.keysym.sym == SDLK_ESCAPE){ 
					loop = 0; 
				} 
			} 
		} 
 
		/*draw square with given dimensions with color blue (0, 0, 255)*/ 
		SDL_FillRect(display, &sDim, SDL_MapRGB(display->format, 0, 0, 255)); 
 
		/*inform screen to update its contents*/ 
		SDL_UpdateRect(display, 0, 0, 0, 0); 
 
	}

Time-based Delay[edit]

When implementing a game loop, its necessary to be able to run it at a constant frame rate. SDL's timing mechanisms are accurate to the millisecond. Considering there are 1000 milliseconds in one second, by being able to retrieve the current millisecond (or 'tick') since program execution, one can implement a time-based loop set at a constant rate.

If you wished to set your program to update every 20 frames a second as its frame rate, determining the amount of ticks needed to elapse before a frame update could be found by dividing the number of milliseconds (ticks) in a second (1000) by the desired frame rate:

tick_count = (1000 / frame_rate)

To get the desired tick count for a 20 frame / second frame rate:

tick_count = (1000/ 20) = 50 ticks (ms)

Using the SDL_Delay function, which takes as a single parameter the number of milliseconds to delay, the first implementation of our time-based loop can be implemented as:

/*set tick interval to 20 frames/ second (1000ms / 20 = 50 ticks)*/
#define TICK_INTERVAL    50
 
while(loop){
		/*check if events are being processed*/
		while(SDL_PollEvent(&event)){}
 
                             draw_and_update_screen();
                             SDL_Delay(TICK_INTERVAL);
/*end game loop*/
}

The main issue with this loop is that the [b]draw_and_update_screen()[/b] function may take a few milliseconds to complete depending on the complexity of the graphics being drawn. For example, if drawing and updating the screen every frame takes 5 milliseconds and adding to that the delay of the [b]tick interval[/b] at the end of the loop, our frame rate changes from updating every 50 ticks to updating every 55 ticks, throwing our constant rate off by 5 ticks.

To remedy this problem, one must be able to keep track of time elapsed from the beginning of the program and within the loop, taking into consideration the amount of time it takes to process the game loop.

SDL includes a function that allows you to retrieve the tick count since the program started running:

Uint32 SDL_GetTicks(void);
 
Get the number of milliseconds since the SDL library initialization.

So when the program first starts the return value of [b]SDL_GetTicks[/b] will be 0. If the program performs other processes that take 30 ticks and then retrieves the value of this function again, it will return 30.

To allow a consistent frame rate that will take into consideration the time it takes to run through the loop, we must keep track of time using two variables:

  • A variable that retrieves the current tick count using SDL_GetTicks
  • A static variable that holds the next tick count in which the frame would be updated that the current tick count needs to reach before updating to the next frame. The value of this variable would be the current tick count plus the tick interval (50 in our case for a 20 frames / second rate) to determine when the next frame needs to be drawn.
static Uint32 next_tick = 0;
Uint32 cur_tick;
 
/*retrieve program tick count once per frame*/
cur_tick = SDL_GetTicks();

[i] The type [b]Uint32[/b] represents a 4 byte (32 bit) integer that SDL defines in their library for cross-platform compatibility. This type is similar to declaring a variable to [b]unsigned int[/b] on 32-bit machines. [/i]

Once these variables are declared and set we would perform a comparison to see if the current tick count has reached the value of the next tick count to indicate frame increment (update to next frame).

if(next_tick <= cur_tick){

This will evaluate to true when the current tick count is equal to or exceeds the next tick count, allowing the frame to update. If so, we need to update the next tick count to be used for the next frame update by adding the tick interval value (in our case 50) to it:

next_tick = cur_tick + 50;

If the above comparison evaluates to false, that means the frame isn't ready to be updated and there is still time left in the frame. To retrieve that time left simply calculate the difference between the next tick count and the current one.

int time_left = next_tick – cur_tick;

You would pass this value to the [b]SDL_Delay[/b] function to keep the program running at a constant rate.

Here is the next implementation of the time loop implemented inline (note that we will put all this logic into a separate function later):

        #define TICK_INTERVAL 50
/*declare variables at beginning to keep track of current and next tick count*/
	Uint32 next_tick = 0;
	Uint32 cur_tick = SDL_GetTicks();
/*variable to hold number of ticks to delay after update of screen*/
	Uint32 tick_delay = 0;
 
              while(loop){
		/*check if events are being processed*/
		while(SDL_PollEvent(&event)){}
 
                            draw_and_update_screen();
                            /*retrieve tick count up to now (after time taken to draw and update screen)*/
		cur_tick = SDL_GetTicks();
		if(next_tick <= cur_tick){
                        next_tick = cur_tick + TICK_INTERVAL;
			tick_delay = 0;
		}else{
			tick_delay = (next_tick - cur_tick);
		}
                            SDL_Delay(tick_delay);
            /*end game loop*/
            }

Time-based animation example[edit]

This example will take the program we did at the beginning of this chapter, adding the event handling and time delay concepts we have discussed throughout this chapter.

Example Code: Animating a Rectangle[edit]
   #include <stdio.h> 
#include <stdlib.h>
#include "SDL/SDL.h"
 
#define TICK_INTERVAL    50
 
Uint32 TimeLeft(void)
{
	Uint32 next_tick = 0;
	Uint32 cur_tick;
 
	cur_tick = SDL_GetTicks();
	if(next_tick <= cur_tick){
		next_tick = cur_tick + TICK_INTERVAL;
		return 0;
	}else{
		return (next_tick - cur_tick);
	}
}
 
int main(int argv, char **argc)
{
	SDL_Surface *display;
	SDL_Rect sDim;
	/*direction of moving block (0 - left, 1, -right)*/
	int dir = 0;
 
	SDL_Event event;
	int loop = 1;
 
	/*initialize SDL video subsystem*/
	if(SDL_Init(SDL_INIT_VIDEO) < 0){
		/*error, quit*/
		exit(-1);
	}
 
	/*retrieve 640 pixel wide by 480 pixel high 8 bit display with video memory access*/
	display = SDL_SetVideoMode(640, 480, 8, SDL_HWSURFACE);
	/*check that surface was retrieved*/
	if(display == NULL){
		/*quit SDL*/
		SDL_Quit();
		exit(-1);
	}
 
	/*set square dimensions and position*/
	sDim.w = 200;
	sDim.h = 200;
	sDim.x = 640/2;
	sDim.y = 480/2;
 
	dir = 1;
 
	while(loop){
		/*check if events are being processed*/
		while(SDL_PollEvent(&event)){
			/*check if event type is keyboard press*/
			if(event.type == SDL_KEYDOWN){
				if(event.key.keysym.sym == SDLK_ESCAPE){
					loop = 0;
				}
			}
		}
 
		/*update rectangle position*/
		if(dir){
			if((sDim.x + sDim.w) < 640){
				sDim.x += 1;
			}else{
				dir = 0;
			}
		}
 
		if(!dir){
			if(sDim.x > 0){
				sDim.x -= 1;
			}else{
				dir = 1;
			}
		}
 
		/*clear display with black*/
		SDL_FillRect(display, NULL, SDL_MapRGB(display->format, 0, 0, 0));
 
 
 
		/*draw square with given dimensions with color blue (0, 0, 255)*/
		SDL_FillRect(display, &sDim, SDL_MapRGB(display->format, 0, 0, 255));
 
 
 
		SDL_Delay(TimeLeft());
		/*inform screen to update its contents*/
		SDL_UpdateRect(display, 0, 0, 0, 0);
 
	}
 
	SDL_Quit();
 
	exit(0);
}

The first addition to the program is the dir variable which, if it has a positive value (1), will cause the rectangle we defined in the beginning of this chapter to be moved to the right by one pixel/frame. If dir is 0, which happens when the rectangle is moving offscreen, the rectangle is moved to the left by 1 pixel/frame. The time interval is set to update at 50 ticks/second (20 frames/sec) so the rectangles velocity in relation to the screen coordinates is 20 pixels/second.

/*direction of moving block (0 - left, 1, -right)*/
	int dir = 0;

Within the game loop, before we draw the rectangle to the screen, we have the conditionals to determine which direction the rectangle should be moving (1-right, 0-left) depending on whether it reached its boundaries on the screen.

/*update rectangle position*/
		if(dir){
			if((sDim.x + sDim.w) < 640){
				sDim.x += 1;
			}else{
				dir = 0;
			}
		}
 
		if(!dir){
			if(sDim.x > 0){
				sDim.x -= 1;
			}else{
				dir = 1;
			}
		}

Bitmaps and Sprites[edit]

This chapter will discuss the use of Bitmap image files and loading and displaying them in an SDL program. We will start with a discussion of the Windows Bitmap format (supported across all platforms).

Bitmap breakdown[edit]

A bitmap file, along with actual image data, contains information similar to what you find in an SDL surface. This includes width and height, along with bits per pixel (pixel format) of the image data. Along with that info is the size of the bitmap file (in bytes) along with information to help the programmer read in a bitmap file for use in Graphics programs.

As an SDL programmer, we don't need to worry too much about the bitmap file internals. SDL includes functions to read in a bitmap file and display it as an SDL surface.

SDL Bitmap Program[edit]

Using the following image (saved in .bmp format), the code listing I will show you will read in the file (assumed to be called 'clouds.bmp') and display it on the screen. Note that since the pixel format of this image is 24 bits (3 bytes) the Display surface bit depth will also be set to that amount for compatibility. (As an interesting exercise, after running the following code, try setting the bit depth of the Display surface to different values and see the results. Does it look right?)

SDL Clouds
#include <stdio.h> 
#include <stdlib.h>
#include "SDL/SDL.h"
 
#define TICK_INTERVAL    50
 
Uint32 TimeLeft(void)
{
	Uint32 next_tick = 0;
	Uint32 cur_tick;
 
	cur_tick = SDL_GetTicks();
	if(next_tick <= cur_tick){
		next_tick = cur_tick + TICK_INTERVAL;
		return 0;
	}else{
		return (next_tick - cur_tick);
	}
}
 
int main(int argv, char **argc)
{
	SDL_Surface *display;
	SDL_Surface *image;
	SDL_Event event;
	int loop = 1;
 
		/*initialize SDL video subsystem*/
	if(SDL_Init(SDL_INIT_VIDEO) < 0){
		/*error, quit*/
		exit(-1);
	}
 
	/*retrieve 640 pixel wide by 480 pixel high 24 bit display with video memory access*/
	display = SDL_SetVideoMode(640, 480, 24, SDL_HWSURFACE);
	/*check that surface was retrieved*/
	if(display == NULL){
		/*quit SDL*/
		SDL_Quit();
		exit(-1);
	}
 
	image = SDL_LoadBMP("clouds.bmp");
	if(image == NULL){
		SDL_Quit();
		exit(-1);
	}
 
	while(loop){
		/*check if events are being processed*/
		while(SDL_PollEvent(&event)){
			/*check if event type is keyboard press*/
			if(event.type == SDL_KEYDOWN){
				if(event.key.keysym.sym == SDLK_ESCAPE){
					loop = 0;
				}
			}
		}
 
		SDL_BlitSurface(image, NULL, display, NULL);
 
		SDL_UpdateRect(display, 0, 0, 0, 0);
	}
 
	SDL_FreeSurface(image);
	SDL_Quit();
	exit(0);	
}


The SDL function SDL_LoadBMP takes as a single parameter the filename of the bitmap file. If the file is loaded successfully, the function returns a pointer to an SDL surface (similar to the display surface) with the bitmap data loaded in. If for any reason (ie. Wrong filename or incompatible data) the function fails, it returns NULL. Just like when you checked the Display surface to see if it is NULL, you do the same with the bitmap surface.

	image = SDL_LoadBMP("clouds.bmp");
	if(image == NULL){
		SDL_Quit();
		exit(-1);
	}

To view the image, we must copy the image data to the display surface. To achieve that, we use the SDL_BlitSurface function:

int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect);

The first parameter is the bitmap image surface we want to display. The second parameter are the dimensions of the bitmap surface we wish to display. Setting it to NULL allows the whole bitmap to be displayed. The third parameter is the SDL surface we wish to display the bitmap onto. We pass the display surface here to display the bitmap on the screen. The fourth parameter is the dimensions of the display surface we wish to display the bitmap surface on. Setting it to NULL allows us to display the bitmap on the whole display surface.

After we are done with the bitmap (ie. Program closing), we need to clear out the memory the bitmap data is occupying. This is done through the use of the SDL_FreeSurface function which takes as a single parameter a pointer to an SDL surface to free up:

SDL_FreeSurface(image);

Bitmap Positioning and Transparency[edit]

Positioning a bitmap relative to the screen is mainly the same as we did with the blue rectangle from the previous chapter. Take for example the following bitmap image (from Ari Feldman's GPL collection):

SDL Bitmap Plane.png

To position this bitmap image with its upper-left hand corner at the center of the screen, we would create an SDL_Rect structure with the x and y fields set to the center of the screen:

SDL_Surface *plane;
SDL_Rect plane_rect;
/*load plane image as previously shown*/
 
plane_rect.x = 640/2;
plane_rect.y = 480/2;

The only issue here is retrieving the width and height of the image to complete the SDL_Rect structure. To set these values, use the SDL_Surface members 'w' and 'h' which, upon loading the bitmap, stores its dimensions automatically.

	plane_rect.w = plane->w;
	plane_rect.h = plane->h;

Then, within the game loop, you would call the SDL_BlitSurface function passing the SDL_Rect structure you defined as the fourth (destination rectangle) parameter:

	SDL_BlitSurface(image, NULL, display, NULL);
 
	SDL_BlitSurface(plane, NULL, display, &plane_rect);

Note the order in which the background and the plane bitmap is blitted. This order allows the plane to be displayed on top of the cloud bitmap we showed earlier.

The plane bitmap looks crude with the blue background behind it. When dealing with sprites (animated bitmaps) you need to be able to discard the background color for a cleaner look. SDL allows this color discard through the use of color keys. A color key is an individual color (or range of colors) which is ignored when drawing a bitmap. For the above graphic, the plane, the background is a solid blue (RGB 0 0 255). To set the color key so as to ignore that color use the SDL function SDL_SetColorKey:

int SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key);

This function takes as a first parameter the pointer to the SDL Surface to set the color key to. The second parameter is a flag to determine the type of color keying. Specifying SDL_SRCCOLORKEY instructs SDL that the included color key is to be transparent. The third parameter is the color value to set the color key to. This value can be retrieved with a call to SDL_MapRGB. This function returns -1 on error or 0 if successful. So, before the main loop and after the plane bitmap has been loaded, the color key can be set as so:

	if(SDL_SetColorKey(plane, SDL_SRCCOLORKEY,
	 SDL_MapRGB(plane->format, 0, 0, 255)) < 0){
		printf("\nUnable to set colorkey");
	}

SDL Bitmap Transparency.png

You can also have the color key predefined in a variable and then pass that variable to SDL_SetColorKey:

	Uint32 colorkey;
              ...
              colorkey = SDL_MapRGB(plane->format, 0, 0, 255);
	if(SDL_SetColorKey(plane, SDL_SRCCOLORKEY, colorkey) < 0){
		printf("\nUnable to set colorkey");
	}

Bitmap Animation[edit]

Bitmap animation is accomplished by taking an image and breaking into equal parts (dimension wise). Then at set time intervals (or rates) switching between the parts of the image to simulate animation. Take for example the following bitmap image of a plane in rotation:

Sdl sprite bitmap.png

Given the dimensions of this image being 256 pixels wide and 32 pixels high, splitting it into its eight parts gives each part dimensions of 32 by 32 pixels. When animating this image we would switch between these parts at a certain rate, similar to how a projector switches from frame to frame:

SDL Bitmap Animation.png

To transition this film reel concept into computer graphics, imagine the bitmap sprite as an ordered array of frames (0 – 7):

SDL Sprite Array.png

To perform the animation you would switch between frames 0 and 7. Keeping track of the positioning and dimensions of the rectangle representing is easy. Assuming you're keeping track of the bitmap's source and destination rectangle (source rectangle of image and destination rectangle onto display, respectively):

        /*frame (src) rectangle and destination rect*/
        SDL_Rect frect;
	SDL_Rect prect;
        /*cur frame of animation*/
	int curframe = 0;

Upon defining the rectangles, you can retrieve the x value of the source rectangle by multiplying the value of the current frame by the frame width:

	frect.x = curframe * FRAME_WIDTH;
	frect.y = 0;
	frect.w = FRAME_WIDTH;
	frect.h = FRAME_HEIGHT;

The destination rectangle is defined similar to previously in the chapter when we discussed positioning of bitmaps, except instead of using the dimensions of the whole image, we use just the frame dimensions:

              /*position bitmap at screen coordinates (50, 50)*/
              prect.x = 50;
	prect.y = 50;
	prect.w = FRAME_WIDTH;
	prect.h = FRAME_HEIGHT;

Blitting the image takes what we learned about displaying and positioning of bitmaps. Basically, we just provide the rectangles we defined earlier:

SDL_BlitSurface(plane_spr, &frect, display, &prect);

Updating the frame, we need to check to make sure when we increment it, it won't be out of array bounds as related to the frame layout of our image. The following shows the first method of updating the current frame in regards to the max number of frames (8):

if(++curframe >= NUM_FRAMES){
			curframe = 0;
		}

The downside to this method is that the animated sprite will update after every tick interval, making the animation quite too speedy. In order to slow it down, we introduce the concept of rates. A rate will be the number of tick intervals to elapse before a frame update to the sprite. Assuming we define the rate to update every 3 tick intervals:

int rate = 3;
	int cur_rate = 0;

Updating the current rate and eventual update of the frame can be achieved through the following method:

         if(++cur_rate > rate){
			curframe += 1;
			if(curframe >= NUM_FRAMES){
				curframe = 0;
			}
			cur_rate = 0;
		}

This checks if the current rate reached the rate initially set (in this case 3). If this evaluates to true, the current frame is incremented and the current rate is reset.

Example Code: Animating a bitmap sprite[edit]

#include <stdio.h> 
#include <stdlib.h>
#include "SDL/SDL.h"
 
#define TICK_INTERVAL    50
 
#define NUM_FRAMES 8
#define FRAME_WIDTH 32
#define FRAME_HEIGHT 32
 
Uint32 TimeLeft(void)
{
	Uint32 next_tick = 0;
	Uint32 cur_tick;
 
	cur_tick = SDL_GetTicks();
	if(next_tick <= cur_tick){
		next_tick = cur_tick + TICK_INTERVAL;
		return 0;
	}else{
		return (next_tick - cur_tick);
	}
}
 
int main(int argv, char **argc)
{
	SDL_Surface *display;
	SDL_Surface *image;
	SDL_Surface *plane;
	SDL_Surface *plane_spr;
 
	SDL_Rect frect;
	SDL_Rect prect;
	int curframe = 0;
	int rate = 3;
	int cur_rate = 0;
 
	SDL_Rect plane_rect;
	Uint32 colorkey;
 
	SDL_Event event;
	int loop = 1;
 
		/*initialize SDL video subsystem*/
	if(SDL_Init(SDL_INIT_VIDEO) < 0){
		/*error, quit*/
		exit(-1);
	}
 
	/*retrieve 640 pixel wide by 480 pixel high 32 bit display with video memory access*/
	display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE);
	/*check that surface was retrieved*/
	if(display == NULL){
		/*quit SDL*/
		SDL_Quit();
		exit(-1);
	}
 
	image = SDL_LoadBMP("clouds.bmp");
	if(image == NULL){
		SDL_Quit();
		exit(-1);
	}
 
	/*load plane bitmap*/
	plane = SDL_LoadBMP("plane.bmp");
	if(plane == NULL){
		printf("\nUnable to load plane bitmap");
	}
 
	/*set rectangle dimensions to center of screen*/
	plane_rect.x = 640/2;
	plane_rect.y = 480/2;
	plane_rect.w = plane->w;
	plane_rect.h = plane->h;
 
	/*set colorkey (transparent) to blue*/
	colorkey = SDL_MapRGB(plane->format, 0, 0, 255);
	if(SDL_SetColorKey(plane, SDL_SRCCOLORKEY, colorkey) < 0){
		printf("\nUnable to set colorkey");
	}
 
 
 
	/*load sprite bitmap*/
	plane_spr = SDL_LoadBMP("plane_spr.bmp");
	if(!plane_spr){
		printf("\nUnable to open plane sprite");
	}
 
	/*update color key for sprite bitmap*/
	colorkey = SDL_MapRGB(plane_spr->format, 0, 0, 255);
 
	if(SDL_SetColorKey(plane_spr, SDL_SRCCOLORKEY, colorkey) < 0){
 
			printf("\nUnable to set sprite color key");
	}
 
	prect.x = 50;
	prect.y = 50;
	prect.w = FRAME_WIDTH;
	prect.h = FRAME_HEIGHT;
 
	frect.x = curframe * FRAME_WIDTH;
	frect.y = 0;
	frect.w = FRAME_WIDTH;
	frect.h = FRAME_HEIGHT;
 
	while(loop){
		/*check if events are being processed*/
		while(SDL_PollEvent(&event)){
			/*check if event type is keyboard press*/
			if(event.type == SDL_KEYDOWN){
				if(event.key.keysym.sym == SDLK_ESCAPE){
					loop = 0;
				}
			}
		}
 
		/*update x position of frame*/
		frect.x = curframe * FRAME_WIDTH;
 
 
		SDL_BlitSurface(image, NULL, display, NULL);
 
		SDL_BlitSurface(plane, NULL, display, &plane_rect);
 
		/*Blit sprite based on frame dimensions (frect)*/
		SDL_BlitSurface(plane_spr, &frect, display, &prect);
 
		SDL_UpdateRect(display, 0, 0, 0, 0);
 
		/*update frame based on rate*/
		if(++cur_rate > rate){
			curframe += 1;
			if(curframe >= NUM_FRAMES){
				curframe = 0;
			}
			cur_rate = 0;
		}
 
 
		SDL_Delay(TimeLeft());
	}
 
	SDL_FreeSurface(image);
	SDL_Quit();
	exit(0);	
}

To do[edit]

  • Basic Graphics Library
  • Game Design: Sky War
  • Game Design: Graphics
  • Game Design: Artificial Intelligence
  • Complete Game code and explanation