Saturday, December 27, 2008

(0) Comments

2D Tutorial Series Introduction

welcome to infomix.blogspot.com

What this tutorial covers

* Setting up SDL
* Opening a window with SDL
* Setting up a timer
* Starting the game loop
* Drawing bitmaps and text with SDL
* State stacks

Introduction

This tutorial is meant to be a quick introduction to setting up an SDL game project. It covers setting up SDL and using it to draw bitmaps and text. All of my SDL tutorials are geared towards using it to create specific games. If you 'd like some more general tutorials on using SDL, check out this site.

This tutorial uses the STL implementation of a stack, as well as function pointers, to handle the game states (menu, game, exit screen). Although I'll be briefly explaining these concepts in this tutorial, you can also check out my General Programming Tutorials for more information.

One word before we go on, I'm no programming expert and my code is not meant to be optimized or even to be the best way to get the job done. My main goal has been to write clean and understandable code that people new to game programming can understand. These tutorials are completely directed at people who are sick of making console apps and want to see what their new found programming skills can do.
Getting started

Since I'm assuming that you haven't worked with an API before, I'll now show you how to set up Visual Studio for SDL. Those of you who don't use Visual Studio can go to http://www.libsdl.org/ for instructions on how to set up SDL for your compiler. You'll need to go there anyways to download the latest version of SDL. Just scroll down to where it says "Download" and grab the newest version (it'll be under "Development Libraries").

Once you have it, install it to a directory of your choice (remember the directory, we'll need it soon). Now that you have it installed, open up Visual Studio (note that I'm using Visual Studio .NET for this discussion).

Go to Tools->Options and click on the Projects folder. In the top left, there's a drop down menu titled Show directories for:, bring that down and select Include Files. Click on the New Line button (looks like a folder) and add the path to the include folder which should be in the directory you installed SDL to.

Now go back to Show directories for: and select Library Files. Do the same thing as before, only this time add the lib folder from your SDL directory.

We have now told Visual Studio where to look for the files that make up the SDL API. This is a one time process, so you shouldn't have to worry about it again unless you reinstall Visual Studio.

Now go to your SDL directory and open the lib folder. Copy "SDL.dll" and paste it into your project directory. When you distrubute your SDL program, you must include this file in the same directory as your .exe file.

Unfortunately, SDL does not come with text support. A lot of functionality you'd expect from SDL actually doesn't come from SDL itself. It comes from other libraries which people have written themselves. You can get these libraries from the SDL website.

For text output, we'll be using the SDL_ttf (true type font) library. Assuming that the SDL website hasn't been changed since I downloaded the library, you should be able to just click here to get it. If the link doesn't work, email me and let me know. In the meantime, you can always get it from the SDL website if this link doesn't work.

Once you get the zip, extract it to where you installed SDL. It doesn't really matter where you extract it to actually, but this way things will be more organized. We have to do the same thing with SDL_ttf that we did with SDL itself to set it up. In Visual Studio, go to Tools->Options->Projects->VC++ Directories. Now select Include Files in the Show directories for drop-down and click the New Line button (looks like a folder). Add the path to the include folder located in the directory you extracted SDL_tff. Now do the same thing for Library Files only this time add the path to the lib folder in the directory you extracted SDL_tff.

In the lib folder that's in the directory you extracted SDL_TTF to, you'll find a file called "SDL_ttf.dll". You need to include this file when you distribute your program. Be sure to copy and paste it into your project folder now so you can run the game. It should be in the same folder that "SDL.dll" is in.

There's one more file you'll need to include. Whenever you use a font, you have to include the font file in your game directory. Be careful with what fonts you use though, some people have the nerve to copyright fonts. Always check before using a font. Anyways, download the arial font from here and stick it in your project directory.

Now let's start a Win32 Project and call it "SDL Introduction". This project will be used as a base for other SDL projects. It will open a window, start a timer (explained in a moment), and enter our game loop (also explained in a moment). First select New Project, then select Win32 Project from the list of templates. When the next window pops up, select Application Settings and then select Empty Project.

Once your project is loaded, you need to change one more setting. Select Project->SDL Application Framework Properties. Now select the C/C++ folder on the left and go to Code Generation. In the box that says Runtime library bring down the drop-down menu and select Multi-threaded DLL (/MD). You must do this for all of your SDL projects.

When using SDL, you have to include the GNU LGPL license (which SDL is released under) for legal purposes. Go to http://www.libsdl.org/ for more details, but rest assured that as long as you include "SDL.dll" and the GNU LGPL license (I include it in all of my projects, so you can get it from this tutorial's source code zip) you'll be complying with the license.
The Code

Before we start coding, you need to know how screen coordinates work. If you were given the point (20, 60) and were asked to find its location on the screen, you would probably go 20 pixels to the right from the left of your screen, and 60 pixels up from the bottom.

For us, the X value is normal, but the Y value is the opposite. You actually start from the top of the screen and move down. This means that you would go 20 units to the right from the left of the screen, and 60 pixels down from the top of the screen. Keep in mind then that if you want to move something down, you increase its Y value.

We will be using two files for this project. One will be named "Main.cpp" and will contain all of the code required to run our game. The other will be named "Defines.h" and will contain all of the constant values that we will be using. Add two files to your project and name them "Main.cpp" and "Defines.h".
Defines.h

Let's begin with "Defines.h". The #define directive allows us to associate a constant value with a more readable name. For example, we might need to specify the width of our window many times throughout our program. Instead of having to remember what the width of our window is, we can specify its size with #define WINDOW_WIDTH 800 and just use WINDOW_WIDTH anytime we need the width of our window. When we compile our program, the compiler will change any instance of WINDOW_WIDTH to 800 for us. The main advantage of doing this is that we only specify the value once in our program. If we decide to change it later, the compiler will automatically change every instance of WINDOW_WIDTH to the new value.

We might want to change the size of our window later so we'll define the dimensions of our window here. We also might want to change the caption at the top of our window so we'll define that here too. Add the following at the top of "Defines.h":

// Window related defines
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define WINDOW_CAPTION "SDL Introduction"

We'll be using a timer to control the frame rate of our game. We'll get to our timer in a bit but for now you should know that it works in milliseconds and that it checks to see if a given amount of time has passed since we've processed something. Let's create two defines, one that represents our frames-per-second, and another that represents the time between each frame. Add the following to "Defines.h":

// Game related defines
#define FRAMES_PER_SECOND 30
#define FRAME_RATE 1000/FRAMES_PER_SECOND

Note that our timer works in milliseconds so we divide 1000 by FRAMES_PER_SECOND. We want 30 frames per second, which means a frame needs to be processed every 1000/30 milliseconds.
Main.cpp

The first thing we need to do in "Main.cpp" is tell our linker to link in the required SDL libraries. Add the following to "Main.cpp":

#pragma comment(lib, "SDL.lib")
#pragma comment(lib, "SDLmain.lib")
#pragma comment(lib, "SDL_TTF.lib")

Although we could have done this in our project settings, I prefer this method because we can now just copy "Main.cpp" into any of our projects and not mess around with any settings.
Includes

We will be using the STL implementation of a stack to handle our states. If you'd like a more thorough introduction to this concept, see my STL Tutorial and my Function Pointers Tutorial. The concept is fairly simple however. A stack is a data structure that stores data like a stack of plates stores plates. If you want to add a plate to the stack, you add it to the top. If you want to take a plate, you take it from the top.

Function pointers are exactly what they sound like: pointers that point to functions. There's nothing really complicated about them. We'll have function pointers that point to the functions that handle the states of our game (menu, game, exit screen) and push them onto our stack. If we push a pointer to our exit screen onto the stack, and then a pointer to our menu state, we'll have a stack that looks like this:

[exit screen]->[menu]

In each frame of our game, we'll call the function at the top of our stack, in this case our menu state. If the user selects to play the game, we push a pointer to the function that handles our game logic. Then our stack looks like this:

[exit screen]->[menu]->[game]

Now our game function will be called every frame. If the user presses the 'escape' key, we'll pop the game function off of our stack, and the stack will look like this:

[exit screen]->[menu]

If we press the 'escape' key again, the stack will look like this:

[exit screen]

Now the user will see our exit screen, which asks if the user really wants to quit. If the user decides to continue playing, we just push the menu state back onto our stack. If the user chooses to quit, we pop the exit screen state off the stack and our program will quit because the state stack will be empty.

Our includes will be pretty straight forward. We include the SDL header files and our "Defines.h" file. Add the following to "Main.cpp":

#include // We'll use the STL stack to store our function pointers
#include "SDL.h" // Main SDL header
#include "SDL_TTF.h" // True Type Font header
#include "Defines.h" // Our defines header

using namespace std; // save us from writing "std::" everywhere
Data

We initialize an STL stack with the following syntax:

stack stack_name;

The notation tells us that the stack is a templated class. This means that we can specify the type of data we want the stack to store. See my Templates Tutorial for more on templates.

The notation for a function pointer is this:

return_type (*pointer_name)(parameters);

One problem we have is that we can't do the following:

stack stack_name;

The STL stack does not accept function pointers as a data type. To get around this, we'll encapsulate a function pointer into a struct. Add the following to "Main.cpp":

// The STL stack can't take a function pointer as a type
// so we encapsulate a function pointer within a struct.
struct StateStruct
{
void (*StatePointer)();
};

// Global data
stack g_StateStack; // Our state stack

There's actually not much more data needed for this tutorial. One thing I need to explain though is the concept of double buffering. In graphics, a buffer is a location in memory that contains information about what we want to draw to the screen. The data that gets drawn to the screen is stored in the "front buffer".

Double buffering comes in when we want to change what is drawn to the screen. If we just write to the front buffer, we'll be drawing over what is currently on the screen while it is still being displayed. The player will actually see the screen being drawn, which leads to all kinds of problems. To get around this, we create a "back buffer" that does not get drawn. We first draw to the back buffer and then display the entire screen once it has been completely drawn on the back buffer.

We'll call our back buffer g_Window because it represents our game window. When we want to draw it, we'll call a function that replaces the contents of our front buffer with the back buffer.

The rest of the data is simple, we need a structure that stores the bitmap to be displayed during the game state, a structure to store events for when we handle input, and an integer that stores the time at which we last processed a game frame. Add the following to "Main.cpp":

SDL_Surface* g_Bitmap = NULL; // Our background image
SDL_Surface* g_Window = NULL; // Our back buffer
SDL_Event g_Event; // An SDL event structure for input
int g_Timer; // Our timer is just an integer

The SDL_Surface structure is just a buffer that stores image information. You can think of it as a painting canvas. You can draw whatever you want on it. The SDL_Event structure just stores event information like what keys the user has pressed.
Prototypes

Our game will handle three states, a menu, the actual game, and an exit screen. We'll have a function for each one of these states. Add the following to "Main.cpp":

// Functions to handle the three states of the game
void Menu();
void Game();
void Exit();

Every game project, in fact every project, usually needs some intialize and shutdown functionality. We'll write a couple of functions to handle this for us.

Our state functions will require some helper functions. Each state will need to clear the screen before it draws anything, so we'll write a function that clears the screen to black.

The menu and exit screen states will need a way to draw text. Writing text to the screen is actually a bit tricky in SDL so let's just write a function to handle this and never worry about it again.

The game state needs to draw our bitmap to the screen, so we'll have a function to handle that too.

Finally, each of our states will handle input. Because we'll probably want each state to handle input differently, we'll write three different input functions.

Add the following to "Main.cpp":

// Init and Shutdown functions
void Init();
void Shutdown();

// Helper functions for the game state functions
void DrawBackground();
void ClearScreen();
void DisplayText(string text, int x, int y, int size,
int fR, int fG, int fB, int bR, int bG, int bB);
void HandleMenuInput();
void HandleGameInput();
void HandleExitInput();

Note that DisplayText() takes the forgeound and background colors of the text. This will be explained in a moment.
main()

Our main()function will actually be quite simple. One thing to always remember though is that SDL requires you to include the following parameters:

int argc, char **argv

Aside from that, all we need to do with main is call Init(), call the top pointer on our state stack until the state stack is empty, and finish by calling Shutdown(). Add the following code to "Main.cpp":

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

// Our game loop is just a while loop that breaks when our state stack is empty.
while (!g_StateStack.empty())
{
g_StateStack.top().StatePointer();
}

Shutdown();

return 0;
}

Notice that in our while loop (this would also be considered a "Game Loop") all we have to do is check to see if our stack still has something in it and then call the function pointer at the top. Also notice that a function pointer is called just like a function would be called.
Init() and Shutdown()

Initializing SDL is very simple. We make a call to SDL_Init(), which will intialize the SDL video and timer components. A call to SDL_SetVideoMode() will allow us to specify the dimensions and format of our window. The function also returns a pointer to our window, which we assign to g_Window. SDL_WM_SetCaption() allows us to set the caption at the top of our window.

Whenever we process a frame in our game, we'll reset our timer to the time at which we processed the frame. To start, we'll set our timer to the time at which we intialize our game.

SDL_LoadBMP() allows us to load our bitmap from a file. TTF_Init() initializes the True Type Font library.

We start our game in the menu so we should make sure that a pointer to Menu() is at the top of our state stack after initializing our game. We should also make sure that a pointer to Exit() is always at the bottom of the stack since it should always be the last screen the player sees. Add the following to "Main.cpp":

// This function initializes our game.
void Init()
{
// Initiliaze SDL video and our timer.
SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER);
// Setup our window's dimensions, bits-per-pixel (0 tells SDL to choose for us),
// and video format (SDL_ANYFORMAT leaves the decision to SDL). This function
// returns a pointer to our window which we assign to g_Window.
g_Window = SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, 0, SDL_ANYFORMAT);
// Set the title of our window.
SDL_WM_SetCaption(WINDOW_CAPTION, 0);
// Get the number of ticks since SDL was initialized.
g_Timer = SDL_GetTicks();

// Fill our bitmap structure with information.
g_Bitmap = SDL_LoadBMP("data/background.bmp");

// We start by adding a pointer to our exit state, this way
// it will be the last thing the player sees of the game.
StateStruct state;
state.StatePointer = Exit;
g_StateStack.push(state);

// Then we add a pointer to our menu state, this will
// be the first thing the player sees of our game.
state.StatePointer = Menu;
g_StateStack.push(state);

// Initialize the true type font library.
TTF_Init();
}

To shut our program down, we just tell SDL and TTF to shutdown. We also have to free our surfaces. Add the following to "Main.cpp":

// This function shuts down our game.
void Shutdown()
{
// Shutdown the true type font library.
TTF_Quit();

// Free our surfaces.
SDL_FreeSurface(g_Bitmap);
SDL_FreeSurface(g_Window);

// Tell SDL to shutdown and free any resources it was using.
SDL_Quit();
}
State Functions

At the beginning of each of our state functions we'll make a call to the appropriate input handling function. We'll also need to check to see if it's actually time to do any processing. We do this with our timer.

When it's time to process a new frame, we first clear our screen. We then do whatever rendering our state needs and call SDL_UpdateRect() to display our backbuffer.

To finish, we update our timer.

The specific rendering of our states is handled by other functions so we just make some simple function calls here. Add the following to "Main.cpp":

// This function handles the game's main menu. From here
// the player can select to enter the game, or quit.
void Menu()
{
// Here we compare the difference between the current time and the last time we
// handled a frame. If FRAME_RATE amount of time has, it's time for a new frame.
if ( (SDL_GetTicks() - g_Timer) >= FRAME_RATE )
{
HandleMenuInput();

// Make sure nothing from the last frame is still drawn.
ClearScreen();

DisplayText("Start (G)ame", 350, 250, 12, 255, 255, 255, 0, 0, 0);
DisplayText("(Q)uit Game", 350, 270, 12, 255, 255, 255, 0, 0, 0);

// Tell SDL to display our backbuffer. The four 0's will make SDL display the whole screen.
SDL_UpdateRect(g_Window, 0, 0, 0, 0);

// We've processed a frame so we now need to record the time at which we did it.
// This way we can compare this time with the next time our function gets called and
// see if enough time has passed between calls.
g_Timer = SDL_GetTicks();
}
}

// This function handles the main game. We'll control the
// drawing of the game as well as any necessary game logic.
void Game()
{
// Here we compare the difference between the current time and the last time we
// handled a frame. If FRAME_RATE amount of time has, it's time for a new frame.
if ( (SDL_GetTicks() - g_Timer) >= FRAME_RATE )
{
HandleGameInput();

// Make sure nothing from the last frame is still drawn.
ClearScreen();

// Draw the background of our 'game'.
DrawBackground();

// Tell SDL to display our backbuffer. The four 0's will make SDL display the whole screen.
SDL_UpdateRect(g_Window, 0, 0, 0, 0);

// We've processed a frame so we now need to record the time at which we did it.
// This way we can compare this time with the next time our function gets called and
// see if enough time has passed between calls.
g_Timer = SDL_GetTicks();
}
}

// This function handles the game's exit screen. It will display
// a message asking if the player really wants to quit.
void Exit()
{
// Here we compare the difference between the current time and the last time we
// handled a frame. If FRAME_RATE amount of time has, it's time for a new frame.
if ( (SDL_GetTicks() - g_Timer) >= FRAME_RATE )
{
HandleExitInput();
// Make sure nothing from the last frame is still drawn.
ClearScreen();

DisplayText("Quit Game (Y or N)?", 350, 250, 12, 255, 255, 255, 0, 0, 0);

// Tell SDL to display our backbuffer. The four 0's will make SDL display the whole screen.
SDL_UpdateRect(g_Window, 0, 0, 0, 0);

// We've processed a frame so we now need to record the time at which we did it.
// This way we can compare this time with the next time our function gets called and
// see if enough time has passed between calls.
g_Timer = SDL_GetTicks();
}
}
Rendering Functions

Let's start with DrawBackground(). SDL is great for stuff like this. All we need to do is define two rectangles and call SDL_BlitSurface(). The first rectangle stores the location in our bitmap file of the image we want to draw. The other rectangle stores the location within our window that we want our image displayed. The term "blit" means "block-image transfer". It just means that we transfer data from one area in memory to another (back buffer to front buffer). Add the following to "Main.cpp":

// This function draws the background
void DrawBackground()
{
// These structures tell SDL_BlitSurface() the location of what
// we want to blit and the destination we want it blitted to.
// Presently, we blit the entire surface to the entire screen.
SDL_Rect source = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };
SDL_Rect destination = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };

// This just 'block-image transfers' our bitmap to our window.
SDL_BlitSurface(g_Bitmap, &source, g_Window, &destination);
}

ClearScreen() simply clears the screen to black by calling SDL_FillRect(), which draws a rectangle of whatever color we specify. Add the following to "Main.cpp":

// This function simply clears the back buffer to black.
void ClearScreen()
{
// This function just fills a surface with a given color. The
// first 0 tells SDL to fill the whole surface. The second 0
// is for black.
SDL_FillRect(g_Window, 0, 0);
}

Displaying text using SDL takes a bit of work. We first have to create a font structure to specify the style and size of our text. We also need two color structures to hold the color of text and the color of what we want behind the text.

To actually display the text, we first need to render it to a temporary surface. We then blit that surface just like we did with our bitmap. To finish, we just close the font structure. Add the following to "Main.cpp":

// This function displays text to the screen. It takes the text
// to be displayed, the location to display it, the size of the
// text, and the color of the text and background.
void DisplayText(string text, int x, int y, int size, int fR,
int fG, int fB, int bR, int bG, int bB)
{
// Open our font and set its size to the given parameter.
TTF_Font* font = TTF_OpenFont("arial.ttf", size);

SDL_Color foreground = { fR, fG, fB}; // Text color.
SDL_Color background = { bR, bG, bB }; // Color of what's behind the text.

// This renders our text to a temporary surface. There
// are other text functions, but this one looks nice.
SDL_Surface* temp = TTF_RenderText_Shaded(font, text.c_str(), foreground, background);

// A structure storing the destination of our text.
SDL_Rect destination = { x, y, 0, 0 };

// Blit the text surface to our window surface, the NULL specifies the whole surface.
SDL_BlitSurface(temp, NULL, g_Window, &destination);

// Always free memory!
SDL_FreeSurface(temp);

// Close the font.
TTF_CloseFont(font);
}
Input Functions

To handle input, we first need to fill our event structure. A call to SDL_PollEvent() will take care of this. We then need to figure out the type of event. The two we will deal with are SDL_QUIT and SDL_KEYDOWN. SDL_QUIT occurs when the player clicks the 'X' in the caption bar. SDL_KEYDOWN occurs when a key is pressed.

When the user manually closes the window, we pop all of the states off of our stack so our game loop will quit and Shutdown() will be called. When the 'escape' key is pressed, we pop the top most state off of the stack.

The rest of our event handling is specific to each function but should be self-explanatory.

Note that SDL stores the key that was pressed in SDL_Event.key.keysym.sym. This is kind of ugly so don't feel bad if you just copy-paste it instead of memorizing it.

Add the following to "Main.cpp":

// This function receives player input and
// handles it for the game's menu screen.
void HandleMenuInput()
{
// Fill our event structure with event information.
if ( SDL_PollEvent(&g_Event) )
{
// Handle user manually closing game window
if (g_Event.type == SDL_QUIT)
{
// While state stack isn't empty, pop
while (!g_StateStack.empty())
{
g_StateStack.pop();
}

return; // game is over, exit the function
}

// Handle keyboard input here
if (g_Event.type == SDL_KEYDOWN)
{
if (g_Event.key.keysym.sym == SDLK_ESCAPE)
{
g_StateStack.pop();
return; // this state is done, exit the function
}
// Quit
if (g_Event.key.keysym.sym == SDLK_q)
{
g_StateStack.pop();
return; // game is over, exit the function
}
// Start Game
if (g_Event.key.keysym.sym == SDLK_g)
{
StateStruct temp;
temp.StatePointer = Game;
g_StateStack.push(temp);
return; // this state is done, exit the function
}
}
}
}

// This function receives player input and
// handles it for the main game state.
void HandleGameInput()
{
// Fill our event structure with event information.
if ( SDL_PollEvent(&g_Event) )
{
// Handle user manually closing game window
if (g_Event.type == SDL_QUIT)
{
// While state stack isn't empty, pop
while (!g_StateStack.empty())
{
g_StateStack.pop();
}

return; // game is over, exit the function
}

// Handle keyboard input here
if (g_Event.type == SDL_KEYDOWN)
{
if (g_Event.key.keysym.sym == SDLK_ESCAPE)
{
g_StateStack.pop();

return; // this state is done, exit the function
}
}
}
}

// This function receives player input and
// handles it for the game's exit screen.
void HandleExitInput()
{
// Fill our event structure with event information.
if ( SDL_PollEvent(&g_Event) )
{
// Handle user manually closing game window
if (g_Event.type == SDL_QUIT)
{
// While state stack isn't empty, pop
while (!g_StateStack.empty())
{
g_StateStack.pop();
}

return; // game is over, exit the function
}

// Handle keyboard input here
if (g_Event.type == SDL_KEYDOWN)
{
if (g_Event.key.keysym.sym == SDLK_ESCAPE)
{
g_StateStack.pop();

return; // this state is done, exit the function
}
// Yes
if (g_Event.key.keysym.sym == SDLK_y)
{
g_StateStack.pop();
return; // game is over, exit the function
}
// No
if (g_Event.key.keysym.sym == SDLK_n)
{
StateStruct temp;
temp.StatePointer = Menu;
g_StateStack.push(temp);
return; // this state is done, exit the function
}
}
}
}

Conclusion

That's all you need to know to start an SDL game project. All you need to do now is copy this framework into any of your game projects and you'll be able to get straight to the game specific code.

0 Responses to "2D Tutorial Series Introduction"

Post a Comment