Spacecrash day 1 of 7: game skeleton
Time to get started. Today I am going to set up the main project skeleton, project directory, get it to compile on both Windows and OS X, set up loading textures, and get a spaceship to fly over some kind of scrolling background. Plus possibly have a few things flying around.
Setting things up will be a bit painful, since there are a lot of small things to configure correctly. Projects, modules, etc… if you are not all that fluent in C/C++, and something isn’t easy to understand, don’t despair: follow the instructions to set it up, ask your doubts on the comments, and remember that we will only be doing this type of stuff today, since once things are set up we will be concentrating in the game code. I am going quickly here, so you will be able to put in the time and effort after this week, and be able to understand all the details of today’s work when you look at it more slowly.
Good way to organize a project: create directory for the project wherever you like to work and call it with the project’s name (“spacecrash” here). Create three subdirectories: “src” for the source code, “data” for the resources that will be loaded by the game, and “prod” for intermediate production files.
Here’s how we’ll organize the code for our game: there will be a game.cpp file with the main logic of the game (remember, we’ll use C, but compiling as C++, thus the cpp extension). We will have a module called sys with two versions: sys_osx.cpp or sys_win.cpp for the OS-dependent part. If you are only going to do one OS, you can have just one. I will write both version here, to compile on both Windows and OS X, and the project on each platform will only include the relevant version. The trick is that both sys_osx.cpp and sys_win.cpp implement exactly the same functions, then there is a sys.h file with the declarations of these, and the rest of the program can call the functions in this module, thus becoming platform-independent. No game.h is necessary, since the other modules won’t be calling anything from game.cpp (game.cpp is a user of the other modules, not the other way around).
We’ll use prefixes for module functions: SYS_ for those in the platform layer (sys_win.cpp or sys_osx.cpp). It’s good practice and common in C, where there is no support for modules, classes or similar mechanisms.
The sys module will contain the OS-dependent logic, but the main loop will be in game.cpp, inside a function that I have decided to call “Main” (with uppercase “M”). This function has to perform the main game loop, calling the SYS_ functions, and return when the game has finished. The system-dependent sys_osx.cpp or sys_win.cpp is responsible of calling Main() to get everything started.
In case this is your first game dev experience, every game is centered around a “game loop”: this is a long running loop that does the following:
- It draws the game on the display for the player to see it
- It reads player input and processes it
- It runs the game logic (characters and enemies advancing, etc…)
- …and then just starts all over again!
This way, the player sees a continuously animated world, which is just the result of seeing many individual images per second. They can press keys that are checked every time around the loop. The game doesn’t wait for input: if no input is ready, it keeps looping and advancing the game, as is necessary for the game to progress independently of the player.
Files common to all platforms
You need several files which will be common for all platforms. First of all, it’s quite common in C/C++ projects to have a base.h file that is included everywhere, declaring some useful basic data types. Here it is:
Short explanation of the elements: the #ifndef/#define trick avoids errors if you include the file multiple times (common C/C++ idiom). The typedefs above define low-level types such as “byte”, “word”, etc… which do not exist in C/C++ by default. The SIZE_ARRAY macro is just for utility. We define an ivec2 structure type to contain integer x,y coordinate pairs (we will use it for mouse coordinates). And the last piece is our vector library: we define a vec2 structure type to hold a floating-point 2D vector, and functions to operate with them: addition, subtraction, length, do products, etc…
Apart from this, we need a sys.h file that contains the declarations of the system-dependent functions, which will be then defined differently on the different platforms:
As you can see, sys.h declares some functions that do system chores. The main game loop will call the various SYS functions to make sure the system goes running. The actual functions will be written differently in Windows and OS X, but they will do the same work, so that the game itself can be written just once.
You can see some global constants (width/height of the game window, width and height of the game coordinates we will use to refer to it, and you can see how some #ifdef trickery is used to define the constants for keys differently on Windows or OS X).
Here is the first version of the game code to test this set-up:
This contains the main game loop, already structured in calls to do the separate tasks (Render, ProcessInput, etc…). You can see how it calls the SYS abstraction layer to do some basic tasks: show the results after drawing, etc… and it uses OpenGL calls directly for others. It calls SYS_GottaQuit() to check whether to exit. And you can see some poor-man’s attempt at controlling the speed of the game: it sleeps for 16 milliseconds each turn, assuming the rest of the code hardly takes any time at all, this will get it to run at 60 frames per second.
We are also going to need to configure a module for “precompiled headers”. Since including many files with #include in C or C++ can make compilation slow, it’s common to configure all of them in a single module so that it is processed just once. We are going to call it “stdafx.h” and “stdafx.cpp”, as it’s usually called under Windows (we could call it anything for OS X too). Here is the required content:
Setting it up on OS X
Finally, you need sys_osx.cpp implementing the OS X specific code, here it is:
As you can see, the SYS_ functions are implemented just by calling glfw or Unix-specific code (usleep). It also features the function main(), the real entry point in a Unix/Linux/OS X application, which starts GLFW and calls our custom Main() game function which resides in game.cpp.
Now, create a new OS X project (“Command Line Tool”). Call it “scosx”, C++ type, and select the “spacecrash” root project directory as the location (not the “src” subdirectory). Once created, delete the “main.cpp” and “scosx.1” files Xcode creates by default, and add our source files to the project: game.cpp, sys_osx.cpp and sys.h, stdafx.h and stdafx.cpp. You will need to do the same adjustments we did for the test program: add the frameworks OpenGL, OpenAL, Cocoa and IOKit, the linker flag “-lglfw”, and the search paths “/usr/local/include” and “/usr/local/lib” for headers and libraries. After this is done, you should be able to run the sample app with Cmd-R. It should just show a portrait window with a dark-blue background.
Setting it up on Windows
We are going to share most files with Windows: base.h, sys.h, stdafx.h, stdafx.cpp, and mainly, game.cpp, which is the main module, containing the actual game. We will need another file: sys_win.cpp, implementing the SYS functions for Windows. Here you have it:
It is more complex than the one for OS X, as we are doing everything “by hand” instead of using GLFW. What you see above is actually the minimum set of things an app has to do under Windows to create an OpenGL window and keep it running. Also here, the WinMain() function which is the actual entry point in a Windows application.
When you move to another platform, this is the kind of code you have to write to get started, and then you can get to programming the game in the same way we are going to do. This is the same for OS X, Linux, Android, iOS, and many other platforms.
Here are the steps you need to take to set up the project under Windows: open VS, select “New Project”, “Visual C++”, “Win32 project”. Select “spacecrash” (the root directory of the project), and name the new project “scwin”. Once created, right-click and remove from the project all the files Visual Studio created, and add our source files. You will also need to right-click and change the properties of stdafx.cpp, select “Precompiled Headers”, and set to “Create” instead of “Use”. Also, the same changes we had to do in the test: “Use Multibyte Character Set”, add “opengl32.lib;openal32.lib;” to the extra libraries, and add the OpenAL directories to the VC++ Directories for Include and Library files.
By the way: VS, the same as other IDEs, allows multiple work configurations for a project. It has two by default: “Debug” and “Release”. When you change settings, by default it only applies to the currently active one (“Debug” by default). To apply the changes to all configurations, after right-clicking “Properties”, select “Configuration: All Configurations” in the combo-box at the top-left. Try to do this before changing the settings.
Once this is done, running with F5 should show you a nice dark-blue background portrait window.
Setting up on Linux
I can’t go into details, but I believe you can use exactly the same source code as the OS X version, and compile using gcc from the command-line. Please post in the comments if there is any issue with this.
Now we have an application skeleton, both for Windows and OS X, which sets up a main game window, and enters a game loop, drawing and calculating each frame. It is very static now since we haven’t added anything yet. But we should now start drawing game elements… let’s get on to that.
We need to load some graphics into memory. Since I didn’t want to use any external library, I have chosen a graphics file format which is very easy to read: 32 bits-per-pixel BMP with alpha. This format is not compressed, thus, it can be read as a sequence of bytes from the file, and then sent over easily to OpenGL. OpenGL wants the pixel data in raw uncompressed format – if you load PNG or some other file, you need to decode it, which is non-trivial. When using PNG or other formats, the common thing is not to write your own loader, but to use some nice library, of which there are many. But you should learn to read a file format by hand for instructional purposes, before using libraries to do anything! So here we go.
One other important thing is that OpenGL only supports textures in sizes that are a power of two (32, 64, 128, 256, 512, etc…). To load images of arbitrary size, we will round up to the nearest power of two and only use a part of the texture.
Finally, to hold this code, and other types of similar code, we will create a new module: the core module. This will contain code that is common between platforms, but which is not really game-specific. Texture support, graphics utilities, sounds support… this is all part of the “engine”: we will declare things in core.h and define them in core.cpp. We will use the prefix CORE_ for the functionality in this module. Here is the first version with the texture-loading code (add these modules to your “src” directory and to your VS or Xcode project):
As you see, this declares a function to which you can pass a filename (as an array of chars, the C concept of a string), and returns an identifier to use it later on. You can call GetBmpSize() to get the pixel size of the bitmap. There is also a function to unload the texture and release the resources, which you only need to call once it is not going to be used any more. Finally, we have added a function to draw a rectangle with a given texture, passing in the coordinates, size, and what texture to use (a returned by CORE_LoadBmp()).
Here is the implementation:
I won’t go into much detail about this code, although you would do very, very well in understanding it if you want to become a proficient C/C++ games developer. The CORE_LoadBmp() function reads the file header to get the main information, and then reads the pixel data. BMP files may store the info bottom-to-top or top-to-bottom, and it has to adapt it to the common BMP file format (which we take as being bottom-to-top). It computes the nearest higher power-of-two (hp2()), and creates a texture of that size, passing in the pixel data to that texture. It sets the texture parameters, and then saves both the texture identifier and the information about what part of the texture is actually used, in a g_textures array which is the array where we will keep the information of all loaded textures.
To test the image loading and drawing, I am going to prepare the graphics for the ship you will be controlling. Here are the ones I have selected from the Tyrian graphics:
We can’t use these graphics directly. We need to extract each animation frame into a separate sprite, making sure it is properly aligned, and prepare it with alpha transparency for the area in green. They way I do it: I use Paint.NET (you can use your favorite tool), copy and paste a single image, use the magic wand to select the green area, and remove it. Save the result as a 32-bpp BMP with alpha, which is what we are able to load. I actually create a single file with multiple layers, and put each animation frame in one layer, that way I can check alignment. Then, export each layer separately a single BMP file (hide all layers but the one you want, “Save as”, and choose BMPX, which will flatten the image – use “Undo” afterwards to restore all layers). I put the intermediate files in my “prod” directory (that’s what it is for), and the actual sprites in the “data” directory (I’m calling them “ShipLL.bmp”, “ShipL.bmp”, “ShipC.bmp”, “ShipR.bmp” and “ShipRR.bmp”).
I am also going to export a simple background graphic just for some early testing, here it is:
I have uploaded these graphics for you, you can get them here:
Now that we have these, they should be exracted to the “data” directory, and let’s make the main game load them and draw a simple scene with them:
You can see how we use global variables to keep the graphics, and how our Render function now actually draws a scene. I have also made it check the state of the LEFT/RIGHT keys and draw something a bit different.
See how we are loading the resources: we are loading files with the name “data/*.bmp”. These are called “relative paths”, and it will only work if the current working directory for the program is the root project directory. To ensure this, on OS X we have to select “Edit Scheme” (click where it says “scosx > My Mac 64-bit” control on the top left of the Xcode Window)m check “Use custom working directory”, and select the “spacecrash” project root directory. In VS, you need to right-click on the project, “Properties”, select “Debugging”, “Working Directory”, and browse to the project root file.
Once you run it, it should look like this (press left/right to see how the ship graphic changes!):
Starting the actual game
It’s been a bit of a mouthful to get all the above working, but we now have all the graphical & input tools necessary to start building the game! Let’s get on to doing some game dynamics.
[Important note for those who started with an early version of this article: I have added extra functionality to the Core module, it now remembers the pixel width & height of a texture, and you can request it with CORE_GetBmpSize(). I’ve added the code in the modules above. Be sure you copy/paste it again if you had done so before this functionality was there.]
First thing, I’ve added some rock graphics. Download the new package from here:
Now, in order to have some game dynamics, I did two things:
1. I decided on a coordinate system: entities and all things in the game world will be stored in world-coordinates (not in screen coordinates). The ship will start flying upwards from the bottom of the level (y = 0), and will advance boundlessly (y will grow) until the end of the level is reached. We will have a “g_camera_offset” variable controlling what part of the level is seen, which will be set on each frame according to the coordinates of the ship.
2. I have created an “Entity” struct type for game elements, stored in a global array. There is an “EntityType” enum for the different game element types. There is an E_NULL entity type, used for empty slots in the entities array.
Without further ado, here is the code that does this (only game.cpp needs to change):
There are a few of things worthy of mention: rand() is the C way of getting a random number, between 0 and RAND_MAX. If you divide rand() by RAND_MAX (using floating point), you will get a random value between 0.0 and 1.0. Also, checking (rand() < RAND_MAX/10) returns true only one tenth of the time. I'm using rand() to generate new rocks only once in a while, also to calculate the position where to create them, and also to choose what rock type to calculate (since there are 5 rock types, I need a random number between 0 and 4 - I do (rand() % 5U), where 'U' means 'unsigned, to get the positive reminder of dividing by 5). Also, to render the background, imagine that there is an infinite number of instances of the background image, but we don't want to render them all, only the two that are visible at the current camera position. I use floor() to find which one to render, and I render this one and the one on top. As you can see now, the game running and drawing is now centered around entities, which is a very common pattern. And here is a video showing how the above results:
Not bad for one day. Hope you like it, and see you tomorrow.
And if you want to be notified when new articles to help you become a games developer are ready, just fill in the form below: