GAME PROGRAMMING IN LINUX FOR WINDOWS PROGRAMMERS - PART 2

Basics of SDL

Prepare Yourself

What do you think of Ubuntu Linux so far? It's actually a pretty cool operating system to program in as you've seen. There wasn't any web surfing to find libraries, it was all linked in the package manager so SDL was only a few clicks away. It is really the version of Linux for human beings... (Although I might not really be human *cough*) I'm glad I never opened the terminal in the last tutorial. Let's go over the checklist of things you should know, and go back over them if you're not sure about any one:

- Using Ubuntu's default interface (called GNOME), basically creating new files and folders
You create new files and folders quickly from the right click "drop menu".

- Creating a .c file and writing your program
Just create a new file with .c, .cpp, or .h at the end

- Creating a basic Makefile that compiles a single file
Remember to always name it Makefile without any dots or file extensions

- Using the External Tools plugin which you enabled in GEdit to use Tools->Build to compile the first thing in the Makefile
You don't always have to have the Makefile in the view, you can do it right after you were done editing the .c file as long as the .c file is in the same directory as the Makefile

You should be able to at least create hello worlds and guessing games with whatever c knowledge you might have. Probably not, but you should at least learn a little c. In fact, if you are rusty in c, I recommend you practice the above steps to practice writing c so that compiling the demos in this tutorial is easy and you understand what's going on.

Working With SDL

Foreward:
SDL was made to make it easy to make games for any system, hence why I picked it for Linux. It's great to write something that will compile on other systems like Mac and Windows, because even though you're writing a Linux game, many people still use Windows. This sort of reminds me of a tutorial I originally learned game programming from, except it was for DirectX. He wrote like he talked to make it fun to read, but instead of kawaii references to Anime or animals, his was full of 80s movie quotes and strange SCI-Fi jokes. Anyways, he made the comparison that Microsoft might be the evil guys, but they got all the powerful tools. So while he's got the "imperial star destroyer", we've got the "half-converted rebel transport" as he puts it. But my half-converted rebel transport is really cool and kawaii looking, and it's got a really cute login screen! The author of that Windows game programming book knows who he is, and this is practically the good and holy version of his ungodly tome, but you'll find pure and good game programming knowledge in both of them so give it a read if you come across that book even though it's sort of old now.

Let's go!
Alright, let me explain a bit of what SDL does. SDL stands for Simple DirectMedia Layer... Kind of like a free open source version of DirectDraw only much easier to use. There's a blob of HTML files in the installation directory... Somewhere... But you can probably find the SDL docs on the internet, you won't need them here but it's good for reference anyway. Hold on to your hats, knees, arms, ears, tail, or what have you because it's going to get bumpy again as we learn new things!

First off, when writing an SDL game, the SDL runtimes are installed in your operating system and probably by default so most of your Linux pals could probably just run your game right from the get go. If not just tell them to search for "sdl" in the Synaptic Package Manager like you did. What we installed was the developer files, the libSDL and the SDL.h that's buried in /usr/include/SDL/SDL.h or whatever I put the last tutorial. A windows version of the game is compiled, but when giving the game to your Windows buddies, you should just place SDL.dll right alongside the .exe file so that they can run it right there.

Hm... Now... What should be a good first SDL demo... *thinks* well, for now let's get a blank window up again. No, we're not going to use the hack-together from the last tutorial, I want to walk you through each step of making an SDL window, alright? Here are two functions:

- SDL_Init
- SDL_Quit

You simply call SDL_Init where you want the video system to start and SDL_Quit where you want the video system to end. Just be sure to call SDL_Quit so that the system is closed properly and there's no memory leaks. SDL_Init has a bunch of flags for initializing just the parts of SDL that you need. You need to OR these together with the | character, so that

SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO);

Initializes the video and timer. Now we need a window, it's best to make windowed games with optional fullscreen since fullscreen doesn't work quite right on all Linux systems. This is the function we need:

- SDL_SetVideoMode

This creates a primary surface to draw on top of. This is the main display surface, and it's the one the player gets to see. What we need is two of these working in a swap mode called double buffering. This way, the player sees our pretty picture when it's done, and not in the middle of drawing. The function call:

SDL_SetVideoMode(320,240,0,SDL_HWSURFACE | SDL_DOUBLEBUF);

Will open a window holding a 320x240 primary surface inside using your video card hardware for performance (SDL_HWSURFACE as opposed to SDL_SWSURFACE) and it also asks for a double buffer using SDL_DOUBLEBUF. Later there is SDL_OPENGL and SDL_OPENGLBLIT for combining the use of OpenGL, but it's something for advanced users so don't worry about it. We need an event loop to hold the window open, but not lock up the system. Look at the function:

- SDL_PollEvent

This function returns 1 if there's events. Events... Events? In SDL, events are simple things such as closing a window, using the mouse or keyboard, and related events. You need a variable of type SDL_Event and pass it's reference to the function. It modifies the varaible so that you can see what kind of event was retrieved. For example:

SDL_Event sdl_event;
SDL_PollEvent(&sdl_event)

Gets any events, and places the information about an event in sdl_event. You need to call this multiple times to get all events... It might be easier to write our own little event handler function!


/* Event handler, returns 0 when SDL_QUIT was issued */
int handle_sdl_events()
{
SDL_Event sdl_event;
/* Handle events */
while(SDL_PollEvent(&sdl_event) == 1)
{
if(sdl_event.type == SDL_QUIT)
return 0;
}
/* No quit message was encountered */
return 1;
}


This way we can handle all we want using if statements or a switch statement. This function returns 1 when the program should continue, and 0 when it should not. This makes writing our main loop so easy! Now, let's give it a nice spin, compile the following code:


/* Includes */
#include "/usr/include/SDL/SDL.h"

/* Event handler, returns 0 when SDL_QUIT was issued */
int handle_sdl_events()
{
SDL_Event sdl_event;
/* Handle events */
while(SDL_PollEvent(&sdl_event) == 1)
{
if(sdl_event.type == SDL_QUIT)
return 0;
}
/* No quit message was encountered */
return 1;
}

/* Main */
int main()
{
/* Initialize SDL */
if(SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) != 0)
return 1; /* This is an error */
/* Set the video mode we want */
SDL_SetVideoMode(320,240,0,SDL_HWSURFACE | SDL_DOUBLEBUF);
/* Begin loop */
while(handle_sdl_events())
{
/* Handle one frame of the game right here */
}
/* Exit SDL */
SDL_Quit();
/* Exit */
return 0;
}


You'll get a blank window just as we needed. You can dress up the window with the functions:

- SDL_WM_SetCaption
- SDL_WM_SetIcon

I won't use these until I finish the first demo. Alright, let's write a function that processes everything in the game for one frame.


/* Processes everything in the game for a frame */
void game_main()
{
}


And now, we need to start the frames going using SDL_Flip, but it needs a pointer to an SDL_Surface. Thankfully, the SDL_SetVideoMode function from before returns the one we need, so we create the variable, set it equal to the return of the set video function, and then call SDL_Flip on it. Lastly, let's slow down the loop to some frames per second using the timer functions right after the flip. First we need to know how many milliseconds the game took to render a frame. First we use SDL_GetTicks to record the number of ticks, and then find the difference between the ticks after the loop to see how much time has passed. The loop should look like this now:


/* Begin loop */
while(handle_sdl_events())
{
/* Record ticks */
ticks = SDL_GetTicks();
/* Handle one frame of the game right here */
game_main();
/* Flip buffers */
SDL_Flip(screen);
/* Wait for the next frame */
while(SDL_GetTicks()-ticks < 1000/60);
}


What this does is it checks to see if the function took longer than 1/60th of a second (since a millisecond is 1/1000th of a second, 1000 milliseconds is one second) and if it did not, it should wait until the rest of the 1/60th of a second has passed. The function does wrap around after about 49 days, so if you're paranoid, here's the code to transform it into a slight jerk instead of a freeze:


if(SDL_GetTicks()-ticks > 0) /* Prevents wrap-around error */
{
if(SDL_GetTicks()-ticks > 0) /* Prevents wrap-around error */
while(SDL_GetTicks()-ticks < 1000/60);
}


Now, let's get some pictures to happen. Classically, setting a pixel is done by locking a surface, modifying it directly, and letting it go. Let's write a function that writes a red pixel that's 20 pixels from the top of the window and 10 pixels from the left. Remember that the screen is an integer coordinate grid where the origin is at the top left and positive x and y are twords the right and bottom of the screen (for all you math people).

Surfaces don't always have the same width in memory as they do physically, but there's a variable called pitch in the surface which holds the width of the surface in memory in bytes. Multiplying this with the Y coordinate and then adding the X coordinate will give us the location of the pixel to set.


/* Draws things using direct access to the surface */
void draw_pixels(SDL_Surface *screen)
{
Uint32 *pixel;
int width;
/* Lock the screen */
SDL_LockSurface(screen);
/* Set a red pixel at (10,20) */
pixel = (Uint32*)screen->pixels;
width = screen->pitch/sizeof(Uint32);
pixel[width*20+10] = SDL_MapRGB(screen->format,255,0,0);
/* Done, release the screen */
SDL_UnlockSurface(screen);
}


Placing this in the main loop after game_main(), it does draw a red pixel where I said... But... I set the video mode to whatever the current video format is. We could either set it to 32-bit color or check the pixel format. Try setting it to 16 in the set video function (replace the 0) and see that the dot is further to the right than it should be. Even though our function checks the pixel format for the RGB setting, it still doesn't address the correct location.

Let's write a function that sets a pixel in 8, 16, and 32 bit modes which will come in handy when reading images from other bitmap formats or our own. It's not that hard and once we wrote it, it all becomes easy. The function needs the void* to the pixels, the width of each line in the surface and the pixel format. Then the x and y coordinates along with the pixel color (Uint32 value). Pay attention to how I have to modify the width to reflect the size of the pointer to the pixels on the surface. In 8-bit surfaces, I don't have to do anything since each pixel is a byte, where each pixel is more than one byte in other modes.


/* Sets a pixel in a surface, paying attention to pixel format */
void set_sdl_pixel(SDL_PixelFormat *what,void *pixel,int width,int x,int y,int r,int g,int b)
{
Uint8 *bit8;
Uint16 *bit16;
Uint32 *bit32;
/* Address memory according to the width */
switch(what->BitsPerPixel)
{
case 8:
bit8 = (Uint8*)pixel;
bit8[width*y+x] = SDL_MapRGB(what,r,g,b);
break;
case 16:
bit16 = (Uint16*)pixel;
bit16[(width/2)*y+x] = SDL_MapRGB(what,r,g,b);
break;
case 32:
bit32 = (Uint32*)pixel;
bit32[(width/4)*y+x] = SDL_MapRGB(what,r,g,b);
break;
}
}

/* Draws things using direct access to the surface */
void draw_pixels(SDL_Surface *screen)
{
void *pixel;
int width;
SDL_PixelFormat *format;
/* Lock the screen */
SDL_LockSurface(screen);
pixel = screen->pixels;
width = screen->pitch;
format = screen->format;
/* Set the pixels we need to set */
set_sdl_pixel(format,pixel,width,10,20,255,0,0);
/* Done, release the screen */
SDL_UnlockSurface(screen);
}


We've written the function, so we don't ever have to write that ever again! It works now in the common formats surfaces use, 24-bit is a weird format that causes many problems, so you might want to have the function try a 32-bit mode anyway. It's not likely we will actually write pixels to the main surface, it's useful for custom bitmap formats being read and put into surfaces. Anyway, let's finish with a demo of swarming dots. Take a look at the complete code and play around with it. Try doing strange things with drawing pixels. If you want to crash the program, try drawing outside of the window! Always keep in mind the program might try to draw a pixel outside the window so always limit your drawing to inside of the window surface.

See how the game is initialized in one function, handled in another, and every dot is drawn in the draw_pixels function.


/*
dots.c
A Swarm of Flying Dots
written by WolfCoder (2008)
*/

/* Defines */
#define NUM_DOTS 128

/* Includes */
#include <stdlib.h>
#include "/usr/include/SDL/SDL.h"

/* A dot has a velocity and a position */
typedef struct
{
/* Velocity */
double vx,vy;
/* Position */
double x,y;
}dot;

/* All the dots */
dot dots[NUM_DOTS];

/* Event handler, returns 0 when SDL_QUIT was issued */
int handle_sdl_events()
{
SDL_Event sdl_event;
/* Handle events */
while(SDL_PollEvent(&sdl_event) == 1)
{
if(sdl_event.type == SDL_QUIT)
return 0;
}
/* No quit message was encountered */
return 1;
}

/* Sets a pixel in a surface, paying attention to pixel format */
void set_sdl_pixel(SDL_PixelFormat *what,void *pixel,int width,int x,int y,int r,int g,int b)
{
Uint8 *bit8;
Uint16 *bit16;
Uint32 *bit32;
/* Address memory according to the width */
switch(what->BitsPerPixel)
{
case 8:
bit8 = (Uint8*)pixel;
bit8[width*y+x] = SDL_MapRGB(what,r,g,b);
break;
case 16:
bit16 = (Uint16*)pixel;
bit16[(width/2)*y+x] = SDL_MapRGB(what,r,g,b);
break;
case 32:
bit32 = (Uint32*)pixel;
bit32[(width/4)*y+x] = SDL_MapRGB(what,r,g,b);
break;
}
}

/* Draws things using direct access to the surface */
void draw_pixels(SDL_Surface *screen)
{
void *pixel;
int width;
SDL_PixelFormat *format;
int i;
/* Clear the screen */
SDL_FillRect(screen,NULL,SDL_MapRGB(screen->format,0,0,0));
/* Lock the screen */
SDL_LockSurface(screen);
pixel = screen->pixels;
width = screen->pitch;
format = screen->format;
/* Draw the dots */
for(i = 0;i < NUM_DOTS;i++)
{
/* Don't draw the pixel if it's outside the window */
if(dots[i].x >= 0 && dots[i].x < 320 && dots[i].y >= 0 && dots[i].y < 240)
set_sdl_pixel(format,pixel,width,(int)dots[i].x,(int)dots[i].y,255,255,0);
}
/* Done, release the screen */
SDL_UnlockSurface(screen);
}

/* Initializes the game */
void game_init()
{
int i;
/* Set all the velocities to random values */
for(i = 0;i < NUM_DOTS;i++)
{
/* Set to zero, but make sure the dot eventually gets initialized with a non-zero speed */
dots[i].vx = 0;
dots[i].vy = 0;
while(dots[i].vx == 0 && dots[i].vy == 0)
{
/* Random velocity with real numbers */
dots[i].vx = (rand()%100-50)/20;
dots[i].vy = (rand()%100-50)/20;
}
/* Random locations on the screen */
dots[i].x = rand()%320;
dots[i].y = rand()%240;
}
}

/* Processes everything in the game for a frame */
void game_main()
{
int i;
/* Handle the actions of all the dots */
for(i = 0;i < NUM_DOTS;i++)
{
/* Check for X collision with the window */
if(dots[i].x >= 320 || dots[i].x < 0)
dots[i].vx = -dots[i].vx;
/* Check for Y collision with the window */
if(dots[i].y >= 240 || dots[i].y < 0)
dots[i].vy = -dots[i].vy;
/* Move dot */
dots[i].x += dots[i].vx;
dots[i].y += dots[i].vy;
}
}

/* Main */
int main()
{
SDL_Surface *screen;
unsigned long ticks;
/* Initialize SDL */
if(SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) != 0)
return 1; /* This is an error */
/* Set the video mode we want */
screen = SDL_SetVideoMode(320,240,0,SDL_HWSURFACE | SDL_DOUBLEBUF);
/* Title the window */
SDL_WM_SetCaption("Swarming Dots","dots");
/* Initialize the game */
game_init();
/* Begin loop */
while(handle_sdl_events())
{
/* Record ticks */
ticks = SDL_GetTicks();
/* Handle one frame of the game right here */
game_main();
/* Draws the current pixel state */
draw_pixels(screen);
/* Flip buffers */
SDL_Flip(screen);
/* Wait for the next frame */
if(SDL_GetTicks()-ticks > 0) /* Prevents wrap-around error */
while(SDL_GetTicks()-ticks < 1000/60);
}
/* Exit SDL */
SDL_Quit();
/* Exit */
return 0;
}




Wow! It's a little jerky (or possibly a lot, depending on your system) but not bad. Linux games are more possible and easy to do than you think. The DirectDraw version of this requires half a page just to set up the double buffers and video format and surfaces, and then the windows, ect. It's much more complicated than SDL, so it's actually easier to write games for Linux than for Windows in a way, of course you can use SDL in Windows as well.

Have fun messing around with the dots, but don't get too serious since the set_pixel function is actually too slow for it to be practical for frame-by-frame movement. We'll be needing this function when loading images and the like later, so hold on the set_sdl_pixel function and keep it safe. As an exercise, try looking at the SDL docs and figure out how to add input to the event handler, and have the user control a single pixel. You may even be able to make a simple game with this little knowledge if you're clever enough, I've done it before lots of times. We'll be replacing everything with images pretty soon, so don't worry.

So, this makes your first step into making some cool games for Linux (since I personally think it's pretty lacking). This problem starts with you, but I commend you for wanting to write Linux games to solve this problem. OpenGL has all the bells and whistles of the latest and greatest in video technology, so even though we're writing 2D games, you can still push your hardware in Linux to make games. Step by step is how we do it^^!

That's right, just a little at a time and hopefully there will be a new batch of Linux game developers, including myself. Sweetest thing about this is, you can put this code in Microsoft Visual Studio, make a few changes (you might have to write a normal Win32 application using int WINAPI WinMain) and drop the SDL.dll right next to the program and there's the program! I don't have a Mac, but it goes something like that too. Not only can we unlock the fun in our hardware, but the games can be for many operating systems, widening your audiance. Even if you end up being the only one making games for Linux, it doesn't matter! It's platform-independent anyway so everyone gets to play.