GAME PROGRAMMING IN LINUX FOR WINDOWS PROGRAMMERS - PART 3

Managing your C/C++ projects and building your game libraries

Hello there-! Have fun reading a little background on the purpose of this tutorial series (especially since this is the tutorial for compiling on different operating systems), skip to the first bold heading if you just want the tutorial but I really hope you read the background... please :3

Everyone sane has a computer with one of the big 3 operating systems. Even if you're partially insane, you still probably have Linux, Windows, or a Mac. As you probably know Windows is used in 90% of computers because of Microsoft's evil but clever marketing techniques. So... What does make an operating system good for games? Let's think about this for a moment and come up with a list...

An operating system good for games is:
- Stable, because it's annoying to play a game only to have it crash often making you restart >.<
- Yielding to resources, so that when the user is playing a video game, a good amount of resources are availiable to the game.
- Simple and organized, when the user does want to play a game, they don't want to have to enter many commands, the list of games should be readily availiable and start when clicked upon.
- Supportive of hardware, so when a user gets a game, they can be sure it will work on their computer because the operating system took care of the hardware drivers.

My audiance for this tutorial is Windows game programmers, so you probably are one yourself. You've been using Windows, and I'm going to talk about both XP and Vista. People keep saying Windows is unstable, but this only really applies to Vista- but! Vista has been getting patches to make it at least run for longer than 30 minutes. Windows actually does yield resources when told to, as long as you tell it to, but Vista is a resource hog. You can't deny Vista's basic hefty memory usage and CPU overhead because I can prove it to someone who did a clean unmodified install by opening task manager. However, both are organized and the game is either on your desktop, or where Vista takes it further by having a Games window. Also excelling as a gaming operating system, both have support for drivers mostly, and companies are getting on the ball with writing drivers for Vista. The reason I switched to Linux is for point 2- the fact Vista eats up many resources. However, making a game a Linux-exclusive worse than making a game that's Windows-exclusive since most people will have Windows. The goal of someone who wants to convince gamers to have Linux is to show them a good game that works on Linux and Windows, but demonstrate that the Linux version runs much better because it has lots of resources. It's going to be a long time before Microsoft even loses slightly it's grip on the computer video gaming market.

Next is the sexy macs. They're not kawaii they're sexy apparently, as I hear computer nerds tell me. Awws... I like kawaii better, but in any case they are the easiest to use of the three. This makes it the best operating system for point 3. Macs are generally stable, but people don't understand they are just as stable as Windows and no more (excluding Vista) since most of the fault is with all the crap that comes pre-installed in Windows machines by third parties. There's little hardware that comes with Macs, and it's all stuck- making it the worst in point 4 since the owner of the Mac is generally stuck with what they got unless they somehow get the manufacturer to replace the parts inside or they figure out on their own how to do that. I'm not sure how Macs are on resources, but it seems about the same as XP. I personally don't like Macs, but I hope that more games support all the operating systems so that people can choose an operating system based on what they like rather than what games/programs it supports.

Linux, is really kawaii~! Linux may break, but I've never had my computer crash to the point of where I couldn't do anything. If something breaks, you can still continue working usually and if you know how, use the terminal to fix it. Unfortunatly, fixing a problem in Linux is very very difficult (and NO the terminal is NOT AWESOME!). You'll most likely be surfing google for a while trying to find the answer to fixing your problem- so yes and no for being a stable system. Nothing yields resources like a Linux system, I open the process viewer to see every program and operating system process I'm not using asleep with very little CPU usage. Also, there's so much RAM for me to use, allowing games to really push themselves further. If you have the Ubuntu version or even just GNOME installed for your custom Linux systems, games can be put in the space in between stuff in the taskbar, in the programs list, or on the desktop. It's easy to look up a game with the Add/Remove programs manager in the Ubuntu version making it very organized- "on par" with a Mac. It's about as supportive of hardware as Vista is, except most of it is automatically installed on the Ubuntu version of Linux. As you know from the first part of this series, enabling a video card is easy. Linux is a good operating system for programmings, but thanks to versions like Ubuntu, it's becoming also a great operating system for everyone. Enough of this let's get going^^!

Cross Platform

Hehe, I'm going to spend the time to show you how to organize your libraries of code so that you don't have to write the same SDL loop or event handler over and over. It's a good thing to have a battle-tested library that you can just drop into your projects and work from, so let's go ahead and prepare everything for work.

This tutorial will cover an example of an "SDL Core". This "SDL Core" will be made with your personal flavor of programming in it... I want this folder to contain a bundle of code files that I wrote which contains the SDL loop and some basic handling such as tilemaps and sprites. There's much I will add here but for now I just want to write a generic version of the SDL loop you saw in the previous tutorial in this series.

Now, we will develop our game in Linux, but you might have a Mac or Windows machine to compile on. When writing your code, you need to think of how it should be compiled on each system. Look at this code:


/* Platform Includes */
#ifndef COMPILE_FOR_WINDOWS
#include </usr/include/SDL/SDL.h>
#endif

#ifdef COMPILE_FOR_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <SDL.h>
#endif


And this code:


/* Main */
#ifdef COMPILE_FOR_WINDOWS
int WINAPI WinMain(HINSTANCE hinstance,HINSTANCE hprevinstance,PSTR szcmdline,int icmdshow)
#endif
#ifndef COMPILE_FOR_WINDOWS
int main(int argn,char *argv[])
#endif
{
/* Exit */
return 0;
}


Remember that the #define / #ifndef / #endif / #ifdef things generate text, not code, so this compiles just fine on GCC and the Microsoft Compiler. Combining this with our code from the last tutorial, you get:


/*
wcsdlcore.c
WolfCoder's SDL Core
written by, of course, WolfCoder (2008)

Version 1
*/

/* Platform Includes */
#ifndef COMPILE_FOR_WINDOWS
#include </usr/include/SDL/SDL.h>
#endif

#ifdef COMPILE_FOR_WINDOWS
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <SDL.h>
#endif

/* My Includes */
#include "wcsdlcore.h"

/* Globals */
SDL_Surface *screen;
SDL_Event main_event;
unsigned long ticks;
unsigned long duration;
double time_step;
unsigned int frame_rate = 120;

/* Gets the time step value */
double wcsdlcore_time_step()
{
return time_step;
}

/* Handles SDL events and returns a 0 if the game should exit */
int wcsdlcore_handle_events()
{
/* Get all events */
while(SDL_PollEvent(&main_event))
{
/* End program */
if(main_event.type == SDL_QUIT)
return 0;
}
/* Return the exit code for quitting or continuing */
return 1;
}

/* Main */
#ifdef COMPILE_FOR_WINDOWS
int WINAPI WinMain(HINSTANCE hinstance,HINSTANCE hprevinstance,PSTR szcmdline,int icmdshow)
#endif
#ifndef COMPILE_FOR_WINDOWS
int main(int argn,char *argv[])
#endif
{
/* Initialize SDL */
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
screen = SDL_SetVideoMode(SCREEN_X,SCREEN_Y,0,SDL_HWSURFACE | SDL_DOUBLEBUF);
/* Initialize the game */
wcsdlcore_init();
/* Enter the main loop */
duration = 0;
ticks = 0;
time_step = 0;
while(wcsdlcore_handle_events())
{
/* Clear the screen */
SDL_FillRect(screen,NULL,SDL_MapRGB(screen->format,0,0,0));
/* Sample the time */
duration = 0;
ticks = SDL_GetTicks();
/* Handle one frame of the game */
wcsdlcore_main();
/* Flip buffers */
SDL_Flip(screen);
/* Record the time that has passed, checking for the 49 or so day wrap error */
if(SDL_GetTicks()-ticks > 0)
{
duration = SDL_GetTicks()-ticks;
/* Wait for the maximum FPS */
while(SDL_GetTicks()-ticks < (1000/frame_rate));
}
time_step = (double)(duration)/(1000/16);
}
/* Clean-up the game */
wcsdlcore_exit();
/* Quit SDL */
SDL_Quit();
/* Exit */
return 0;
}


And for the wcsdlcore.h file:


#ifndef WCSDLCORE_H
#define WCSDLCORE_H

/* Defines */
#ifndef SCREEN_X
#define SCREEN_X 320
#endif
#ifndef SCREEN_Y
#define SCREEN_Y 240
#endif

/* Functions to be written by developer */
extern void wcsdlcore_init(); /* Initializes the game, this gets called once before entering the main loop */
extern void wcsdlcore_main(); /* Handles one frame of the game and is called for every frame */
extern void wcsdlcore_exit(); /* Cleans up the game's resources, this is called once after exiting the main loop */

/* Functions provided by WolfCoder's SDL Core */
extern double wcsdlcore_time_step(); /* Returns the time_step value, multiply this by all the speed to achieve correct speeds */

#endif


Lastly, when writing a library, we need to write a test program. The wcsdlcore_init()/ect. functions are missing and need to be written. Here's a stub file called test.c:


/*
test.c
Tests WolfCoder's SDL Core
written by, of course, WolfCoder (2008)
*/

/* My Includes */
#include "wcsdlcore.h"

/* Game initialize */
void wcsdlcore_init()
{
}

/* Game main */
void wcsdlcore_main()
{
}

/* Game exit */
void wcsdlcore_exit()
{
}


Let's run this mess through GCC. I wrote the following Makefile and did the Tools->Build command from GEdit as usual:


# Test run WolfCoder's SDL Core
compile-run: test.c
make compile
gcc -c -Wall test.c
gcc -lSDL wcsdlcore.o wcsprite.o test.o -o wcsdlcore-test
./wcsdlcore-test
rm test.o

# Clean
clean:
rm *~

# Comple WolfCoder's SDL Core
compile: wcsdlcore.c wcsdlcore.h wcsprite.c wcsprite.h
gcc -c -Wall wcsdlcore.c wcsprite.c


It works just fine, we've got our window and everything, and our bundle of code does what it needs. If you plan on making a Windows version of your game, then read on~ Make a new Win32 project in Microsoft Visual Studio. Since this is Linux game programming for Windows users, chances are you've got the system you've been developing on all this time. You still need it if you want to ensure that you can compile ports. Create the new project, MOVE all your files into the part of the project where the Microsoft Compiler will compile and do the following shown in the picture (right click on the Source Files folder):


You need to download the Windows version of the SDL library. The folder you get looks like this on the inside:


Add your .c files, and then do the same for the .h files on the Header Files folder. Now, remember the COMPILE_FOR_WINDOWS macro we put in there? Here's what you do next, it should be Project->Properties as shown here:


Click on the section here and link the SDL folder (it's the SDL folder wherever it is, and then \include) you have to the project as shown:


Now right click the Resource Files and add existing file as shown in the picture. You'll find the sdl.lib inside the lib folder inside the SDL folder:


Alright, now copy and paste the SDL.dll and put it right next to your project. It varies where you put it but if you are tired of this just paste it into the C:\\Windows\System32\ folder so that any program will run. Rememeber to put SDL.dll right with your game when giving your game out to people though, and it has to be the sdl-runtime.dll (rename it to SDL.dll once it's with your .exe file).

And then click on the section shown here:


You add COMPILE_FOR_WINDOWS in there. This is the same as #define COMPILE_FOR_WINDOWS everywhere but you don't actually have to write it, ensuring that it works and that your code compiles for Windows on Windows, and for Linux on Linux. Go ahead and build, and then run the code and you should get this:


You should now have a folder containing a Microsoft Visual Studio project, and in the code folder is the Makefile along with the code files, and the Linux version of the program if you made it earlier in Linux. You can put this on a thumbdrive, or multi-boot your machine and compile it and it should work. Just remember your COMPILE_FOR_WINDOWS macro to be check and used on things that are platform-specific, but only in wcsdlcore.c should you really need it.

How was that? There's other things you could still use the macro for. For example, you could use the macro to put exclusive features in your game for each operating system for fun- or to have the in-game user interface mimic the user interface style of Linux or Windows. Just because you're using a platform-independant library doesn't mean you can't add some extra fun things to each platform.

Your Personal Library

It's all up and ready! Are you ready for the fun to begin? The idea behind this library is to pack the series of SDL commands inside so that you don't have call them outside of the core. This makes it easier to write a game without worrying about including SDL everywhere. Let's write a function that allows us to lock the main surface, write pixels, and unlock, but much simpler and without SDL functions in test.c.

Recall our set_sdl_pixel function in the previous tutorial:


/* 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;
}
}


Let's modify this so that all we need to provide is x,y and r,g,b to the function. This is a pretty minor function, it's not like a sprite-handling code so we can just put it in wcsdlcore.c. Look closely at the modified version:


/* Sets a pixel in a surface, paying attention to pixel format */
SDL_PixelFormat *set_pixel_format;
int set_pixel_width;
void *set_pixels;
void wcsdlcore_set_pixel(int x,int y,int r,int g,int b)
{
Uint8 *bit8;
Uint16 *bit16;
Uint32 *bit32;
/* Address memory according to the width */
switch(set_pixel_format->BitsPerPixel)
{
case 8:
/* 8-bit modes (indexed color */
bit8 = (Uint8*)set_pixels;
bit8[set_pixel_width*y+x] = SDL_MapRGB(set_pixel_format,r,g,b);
break;
case 16:
/* 16-bit high color */
bit16 = (Uint16*)set_pixels;
bit16[(set_pixel_width/2)*y+x] = SDL_MapRGB(set_pixel_format,r,g,b);
break;
case 32:
/* 32-bit true color with possible ALPHA */
bit32 = (Uint32*)set_pixels;
bit32[(set_pixel_width/4)*y+x] = SDL_MapRGB(set_pixel_format,r,g,b);
break;
}
}


We added some globals which belong to the function, but can be quickly set by functions that lock and unlock the screen... Er... Hehe, we make mistakes often, don't we? Let's make this function safer by causing it to do nothing if it is not safe to do anything. I also changed the name of the function so it's consistent with the library. Let's add another global variable to that list and modify the very top of the function:


int set_pixel_safety = 0;
void wcsdlcore_set_pixel(int x,int y,int r,int g,int b)
{
Uint8 *bit8;
Uint16 *bit16;
Uint32 *bit32;
/* Do nothing if it is not safe to set a pixel */
if(set_pixel_safety == 0)
return;
/* THE CODE CONTINUES HERE LIKE BEFORE */


Alright then, let's add functions that lock the surface and release it so we can call them in our test.c file:


/* Sets a pixel in a surface, paying attention to pixel format */
SDL_PixelFormat *set_pixel_format;
int set_pixel_width;
void *set_pixels;
int set_pixel_safety = 0;
void wcsdlcore_set_pixel(int x,int y,int r,int g,int b)
{
Uint8 *bit8;
Uint16 *bit16;
Uint32 *bit32;
/* Do nothing if it is not safe to set a pixel */
if(set_pixel_safety == 0)
return;
/* Address memory according to the width */
switch(set_pixel_format->BitsPerPixel)
{
case 8:
/* 8-bit modes (indexed color */
bit8 = (Uint8*)set_pixels;
bit8[set_pixel_width*y+x] = SDL_MapRGB(set_pixel_format,r,g,b);
break;
case 16:
/* 16-bit high color */
bit16 = (Uint16*)set_pixels;
bit16[(set_pixel_width)*y+x] = SDL_MapRGB(set_pixel_format,r,g,b);
break;
case 32:
/* 32-bit true color with possible ALPHA */
bit32 = (Uint32*)set_pixels;
bit32[(set_pixel_width)*y+x] = SDL_MapRGB(set_pixel_format,r,g,b);
break;
}
}

/* Locks the surface so that pixels can be drawn */
void wcsdlcore_begin_pixels()
{
/* Lock the surface */
if(SDL_LockSurface(screen) != 0)
return; /* Failure */
/* Get values for set pixel */
set_pixel_width = screen->pitch;
set_pixel_format = screen->format;
set_pixels = screen->pixels;
/* Change the set pixel width here */
switch(set_pixel_format->BitsPerPixel)
{
case 16:
set_pixel_width /= 2;
break;
case 32:
set_pixel_width /= 4;
break;
}
/* Make it safe to set pixels */
set_pixel_safety = 1;
}

/* Unlocks the surface to resume normal operation */
void wcsdlcore_end_pixels()
{
/* Unlock and make unsafe */
SDL_UnlockSurface(screen);
set_pixel_safety = 0;
}


Since we're going to draw lots of pixels, the width is calculated in the begin pixels function. Looks simple right? Let's reference these in wcsdlcore.h so that test.c can use them. Let's modify wcsdlcore.h:


#ifndef WCSDLCORE_H
#define WCSDLCORE_H

/* Defines */
#ifndef SCREEN_X
#define SCREEN_X 320
#endif
#ifndef SCREEN_Y
#define SCREEN_Y 240
#endif

/* Functions to be written by developer */
extern void wcsdlcore_init(); /* Initializes the game, this gets called once before entering the main loop */
extern void wcsdlcore_main(); /* Handles one frame of the game and is called for every frame */
extern void wcsdlcore_exit(); /* Cleans up the game's resources, this is called once after exiting the main loop */

/* Functions provided by WolfCoder's SDL Core */
extern double wcsdlcore_time_step(); /* Returns the time_step value, multiply this by all the speed to achieve correct speeds */
extern void wcsdlcore_set_pixel(int x,int y,int r,int g,int b); /* Sets a pixel ONLY WHEN wcsdlcore_begin_pixels was called */
extern void wcsdlcore_begin_pixels(); /* Enables the wcsdlcore_set_pixel function to work */
extern void wcsdlcore_end_pixels(); /* Call this when you're done using the wcsdlcore_set_pixel function for the current frame */

#endif


Now let's apply our new functions by drawing a red pixel at (10,20) like before. This is test.c and all we have to do now is add the following to our main frame function:


/* Game main */
void wcsdlcore_main()
{
/* Draw some pixels */
wcsdlcore_begin_pixels();
wcsdlcore_set_pixel(10,20,255,0,0);
wcsdlcore_end_pixels();
}


We've crammed all that code into three functions we can use, now whenver you want a quick set-pixel sort of application to test something, you can drop the code in the project and all you have to write is another file like test.c and include wcsdlcore.h. It'll even compile according to the platform like you set up earlier!

Homework? Hehe~!

You've learned how to set up a project folder that contains both a Microsoft Visual Studio project for Windows and a simple Makefile project for Linux. You've also learned how to organize bundles of code for use as libraries. So whenever you want to make an SDL project, just get out your SDL Core bundle of code and compile it with your project.

Here's ideas for practicing stuff you learned in this section~

- Try writing a library that makes writing a DEBUG log to a .txt file easy. Have a function that opens the file, one that closes it, and one that writes a string or number to it, and adds a line feed if told to. This way you can put this in your game and if something goes wrong, you have a text file telling you all the errors that occured and when they occured.

- Try writing the previous dots.c demo but ONLY in test.c and WITHOUT INCLUDING SDL in that test.c file, all the includes you need are stdlib.h for the rand and wcsdlcore.h for the SDL Core we wrote.

- If you wrote a version of the dots.c demo for test.c, now compile it for Linux and Windows.

- And for a real challenge, write a library that draws lines without crashing the engine if the lines go offscreen, and then off of that, add support for particle effects using lines and pixels. This is for you advanced programmers out there who already know how to do this, or those wanting a hefty challenge for this level.

Until the next time, have fun^^

Posts

Pages: 1
wow this was really helpful! wondering if any1 knows the rgss to load another script(from title menu i need for mini game menu)
Pages: 1