Game Coding‎ > ‎

1.3 SDL Window

Creating a game window.
Change the project options

Currently, the project is set up to be a simple command-line program that doesn't actually do very much. We are going to change that.

Firstly, go back to the project properties window:

Project > Properties...

Go to the "Build targets" tab and change the type from "Console application" to "GUI application" instead.

Now either click on the "Build Options" button or double-click on the "Windows Release" (or whatever) text on the left. When the new dialog appears, go to the "Linker Settings" tab. Under "Link libraries", add "SDLmain" and "SDL" in that order. It should look like this:


(Click for a larger version)

When you're done, click "OK" to close this window and then click "OK" again to close the first window.

What we have done is informed the project that SDL exists. As a result, you are now able to use SDL to do things in your program, such as use joysticks and create windows.

If you are using Windows, make sure that you set up the Compiler and Debugger options correctly in Part 2 or GCC won't know where to look for the SDL developer files you have downloaded. In this case, your program won't even compile, never mind run.

Copy SDL.DLL

If you're using Windows, you'll need to copy SDL.DLL into the "bin" folder or the program that we're about to build will complain and crash when you try to run it.

You can also put it in the system32 folder of your Windows directory, but that's untidy.

Creating a window

Erase everything in main.c and replace it with this version instead:

/* Include header files. */

#include <SDL/SDL.h>  /* SDL functions.   */
#include <stdlib.h>   /* Standard things. */

/* This function handles system events (such as key presses). */

int sysPollEvents ()
{

  /* Create a new "event" object. */

  SDL_Event event;

  /* Loop until there are no more events to handle. */

  while (SDL_PollEvent (&event))
  {

    /* If this was a quit event, return a non-zero value. */

    if (event.type == SDL_QUIT) { return 2011; }

  }

  /* No SDL_QUIT events were found - keep going. */

  return 0;

}

/* The program will always start with the main() function. */

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

  /* Attempt to start SDL. If it won't start, end the program. */

  if (SDL_Init (SDL_INIT_EVERYTHING) != 0) { return -1; }

  /* Try to create a window. If we can't, shut down SDL and exit. */

  if (SDL_SetVideoMode (640, 480, 32, SDL_HWSURFACE) == NULL)
  {

    SDL_Quit ();

    return -2;

  }

  /* Main game loop. */

  while (sysPollEvents () == 0)
  {

    /* This is where your game code would go... */

  }

  /* Shut down SDL. */

  SDL_Quit ();

  /* End the program. Success! */

  return 0;

}

Save it and press F9. An empty black window will appear. It won't do very much, so after you've dragged it around a bit just close it and the program will end.

Comments

You'll notice that in several places there are bits of text that tell you what a piece of code actually does. These are called comments, and in C they begin with a forward-slash followed by an asterisk and end with an asterisk followed by a forward slash:

/*

The following function restarts the game.

Don't call this unless you have started the game first!

*/

restartGame ();

Anything between these markers is ignored, and you can write whatever you want into your code. This is VERY useful for reminding yourself how your game engine works when you come back to it after months of doing something else. It's also an important way for programmers to show each other how the code that they have written works.

Another useful way to use comments is to dummy out bits of code that you don't need right now but might want back in the future:

/*
I've removed the damage code.
This enemy is now invincible.
*/


enemyThink ();

/* enemyTakeDamage (); */

enemyMove ();

In Java and C++, there are single-line comments. These begin with a double-forward-slash and end when a line does.

// Load the next map.

nextmap ();

You can use these in C as well, although they may or may not be "acceptable" depending on which C compiler you are using and how you have set it up. I usually avoid using them, but feel free to use them if you want.

Indentation

The other thing to notice about the code is that certain parts of it are more "indented" than others, with indented lines starting further to the right.

This is used to visually break a piece of code down into sections that make sense to the programmer. You don't have to indent code for it to work, but it's considered very bad practice not to do so - ESPECIALLY if you are working with other programmers.

You'll generally indent stuff based on how many curly braces come before or after it, so that it all lines up:

#include <stdio.h>

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

    if (2 > 3)
    {

        printf ("This code will never happen.");

    }
    else
    {

        printf ("Well, yeah... 2 is never more than 3...");

    }

    return 0;

}

Indentation can either use tabs or spaces. There is a small war in the programming community, with one side insisting that spaces are the best choice and the other swearing allegiance to tabs. Whichever you decide to use is up to you.

If you decide to use tabs, you will generally use one tab per level of indentation. If you decide to go with spaces, you'll probably use between 2 to 4 of them. I personally use 2 spaces per level of indentation.

You can configure Code::Blocks to automatically convert tabs into spaces or change the number of spaces used per level of indentation by selecting:

Settings > Editor...

The options should be the first thing that you see, on the "General settings" tab.

How the code works

In this section we will go though the above code step-by-step and examine how each part of the program works.

Header Files

The first thing that the program does is include header files:

/* Include header files. */

#include <SDL/SDL.h>  /* SDL functions.   */
#include <stdlib.h>   /* Standard things. */

Header files use a .h extension and can be paired with a C file of the same name:

gameEngine.h
gameEngine.c
main.c

In C++, headers sometimes use a .hpp extension. This is because C++ files generally use .cpp instead of .c in order to show which language they are written in.

Put simply, header files define functions, structures and data types that can be used by a program. The functions that they define will exist in another .c file, somewhere else.

By including gameEngine.h in our main.c file, we can use the functions that we have written in gameEngine.c from the main.c file.

At this stage, we won't actually be writing any headers ourselves, and we will be sticking to just one C file (main.c) in order to keep things simple.

For now, just be aware that we can only use, for example, SDL_SetVideoMode() if we have included SDL.h at the top of our main.c file. Without it, main.c doesn't know what SDL_SetVideoMode() is and won't compile if it is told to do something with it.

The sysPollEvents() Function

The next piece of code is a new function, which I have called sysPollEvents(). Feel free to call this BobMarleysJamFarm() in your own code. It doesn't really matter what you call it, so long as there is no other function with the same name and the name you choose isn't one of the special "reserved words" used by C, such as int, float or struct.

int sysPollEvents ()
{

  /* Create a new "event" object. */

  SDL_Event event;

  /* Loop until there are no more events to handle. */

  while (SDL_PollEvent (&event))
  {

    /* If this was a quit event, return a non-zero value. */

    if (event.type == SDL_QUIT) { return 2011; }

  }

  /* No SDL_QUIT events were found - keep going. */

  return 0;

}

The first line of the function defines the function name, which is fairly obvious. What isn't obvious if that it should begin with the word int.

int sysPollEvents ()

Functions in C "return" a value when they have finished running. In this case, we are returning a whole number AKA an integer AKA an int. Exactly why we would want to do this will be explained later on.

I won't go into too much detail on data types now, but here's a quick reference list:
  • int/long/short - Whole numbers. They can be either signed or unsigned.
  • float/double - Decimal numbers, such as 2.75.
  • char - A single byte. Can be either signed or unsigned. Called a char because you can store one ASCII-format char-acter in them. They can be used to store text.
  • void - Not really a "type", but if a function doesn't need to return anything, it can be told to return "void" instead, in which case nothing is passed back.
Other data types include arrays, pointers, structures, enums and (in C++) classes and bools. However, more on those later - for now, you just need to know that an int is a number.

The next line of code creates an "SDL_Event" called "event" - though, as with the function name, feel free to call it "Derek" instead.

  /* Create a new "event" object. */

  SDL_Event Derek;

Derek will only exist until the function ends, at which point he will be forgotten about. As you may have guessed, SDL_Event is a special data type used by SDL, which is why I didn't mention it above.

Now that we have Derek, we can use him to store "program events". These can be anything from an "end program" signal or a window resize event to a button press or a mouse movement. It doesn't matter what they are, they all need to be dealt with. We can go through them using a while loop, as seen on the next line:

  /* Loop until there are no more events to handle. */

  while (SDL_PollEvent (&Derek))
  {

    /* If this was a quit event, return a non-zero value. */

    if (Derek.type == SDL_QUIT) { return 2011; }

  }

Ignore the & for now, we'll cover that later, when we look at pointers.

A "while" loop continues running until a specific condition is met (or rather, not met). In this case, the loop will keep repeating the same code over and over until there are no more events to be handled.

Each time the loop repeats, the SDL_PollEvent() function stores another event inside Derek. This means that Derek is describing a different event every time the loop repeats and, at some point, one of these events might be a program quit message. If we find one of these, we "return" an int value of 2011 and the function ends.

As we shall see later on, it doesn't matter exactly which number we decide to put here, just as long as it is NOT zero. I chose 2011 because it's New years Eve 2010 tomorrow.

Finally, if no quit events have occurred, the loop will eventually end and the last line of code will be executed:

  /* No SDL_QUIT events were found - keep going. */

  return 0;

This should be fairly self explanatory - if a quit event is found in the loop above, it returns 2011. Otherwise, the program comes here and returns zero instead.

The main() Function

Now that the sysPollEvents() function has ended, we can get started with another function. In this case, it's the main() function, which will always be the first function that the program runs when it is executed. It doesn't matter that main() is not the first function in the file - what matters is that it is called main.

The main() function generally looks like this:

int main (int argc, char *argv[])

As with the sysPollEvents() function, main() is described as [Return Type] followed by [Function Name]. However, the brackets aren't empty as with sysPollEvents() - they contain "arguments" or "parameters" instead. We aren't going to cover those now, so just note that they are there before we move on.

The first line of code in the main function attempts to start SDL:

  if (SDL_Init (SDL_INIT_EVERYTHING) != 0) { return -1; }

As mentioned above with sysPollEvents(), functions can "return" a value. In the case of SDL_Init(), any value other than zero means that something has gone wrong. If we get a non-zero result, main() just ends itself there and then, returning -1 and ending the program.

Assuming that SDL has safely been initialised, we then move on to creating a new window using the following code:

  if (SDL_SetVideoMode (640, 480, 32, SDL_HWSURFACE) == NULL)
  {

    SDL_Quit ();

    return -2;

  }

SDL_SetVideoMode() takes 4 parameters. As you can probably guess, the first three set the display mode to 640x480 with 32-bit colour. The last parameter is a "flags" field and can be used to customise how the display works - for example, you can enable 3D rendering with OpenGL, make the window fullscreen, etc.

For now, we don't really care too much about what this is set to, so we just use SDL_HWSURFACE, which tells SDL to store the contents of the window in video memory (HW = HardWare). You can also use SDL_SWSURFACE to store it in general RAM (SW = SoftWare).

If SDL_SetVideoMode is successful, it will return a "pointer" variable to the drawing area of the window. For the moment, we're just want to know if the window was created, so we need to check that this pointer actually points somewhere. In stdlib.h (which we #included at the top of the code) a value called NULL is defined. If a pointer is not valid, it will be equal to NULL. Therefore, this code attempts to set the video mode to 640x480 and, if it fails to do so, shuts down SDL and ends the program.

Now that we have a window, we enter the main game loop. In the current version of the project, it's rather empty, and looks like this:

  /* Main game loop. */

  while (sysPollEvents () == 0)
  {

    /* This is where your game code would go... */

  }

We have a "while" loop that will continue repeating the same section of code until the sysPollEvents() function returns a value that is is not equal to zero.

Let's re-visit the sysPollEvents() function we looked at earlier:

int sysPollEvents ()
{

  /* Create a new "event" object. */

  SDL_Event event;

  /* Loop until there are no more events to handle. */

  while (SDL_PollEvent (&event))
  {

    /* If this was a quit event, return a non-zero value. */

    if (event.type == SDL_QUIT) { return 2011; }

  }

  /* No SDL_QUIT events were found - keep going. */

  return 0;

}

As we mentioned above, sysPollEvents will only return non-zero when a program-termination event is received.

Because of this, the main loop will only end when a program-termination event occurs:

  while (sysPollEvents () == 0)

Nothing actually happens in the game loop yet, so let's move on.

When the main game loop finally ends, the last few lines of code are executed:

  /* Shut down SDL. */

  SDL_Quit ();

  /* End the program. Success! */

  return 0;

}

As mentioned above, SDL_Quit() will shut down SDL and free up any memory it has used. The last lines then exits the main function, returning a value of zero.

Now that the main() function has ended, there's not much else to do, so the program will terminate.

Conclusion


You now have a basic project set up and have created a display window for your game. We now need to make this window a bit more interesting.

Before continuing, make sure that you understand everything explained above. There is a lot to take in here, but it's all important stuff.
Comments