My goal is two posts a week - typically, one on Wednesday and one on the weekend. This is usually a pretty easy goal to obtain - it doesn’t take too much of my free time to churn out two posts between 750 and 1500 words. And yet, there almost wasn’t a post this weekend. Was I playing Minecraft? Nope.
I was writing code.
Programming is something I’ve always wanted to do professionally, but somehow I managed to end up doing enterprise technical support instead. So, it has become a hobby. And when a project really starts flowing, it is easy to lose myself in it. So I spent three days this week spending every available moment adding code to my new project: a game I’m tentatively calling TreeWars. I’ve managed to tear myself away from the project for a while, but rather than put it out of my mind, I’ve decided to blog about interesting things that happen during the development process.
First, meet TreeWars:
Not much to look at, I know. But the project is still very much a work in progress.
Some interesting things about TreeWars:
- It is my second attempt at games programming, and my first was just a software implementation of a classic abstract board game (hnefatafl). And it was written using the graphics tools in GTK (which are serviceable, but not ideal). As a result, I’m learning all sorts of interesting things about the fundamentals of programming games.
- It is my first (real) attempt at graphics programming as well. I’m using SDL, and may eventually rewrite the rendering engine (and parts of the game engine) to make the game 3d and use OpenGL. Either way, I hope to make it cross-platform, although I’m currently only focusing on Linux, since it’s my native platform.
- It is based on trees from graph theory, not those other kind of trees.
- Currently, all of those beautiful graphics are procedurally generated. It is actually harder than you would think to draw a circle, at least in SDL.
- It (and the idea to blog about the process) was inspired by Project Frontier, a fascinating project by an experienced graphics programmer who is making far more interesting things than I am with procedural content generation! But hey, give me time…
So, the first step on the path to TreeWars was to learn enough SDL to generate a simple accelerated 2D image (not that it really needs to be accelerated right now, but I hope that will change in time). It turns out, this is both somewhat harder and somewhat easier than I expected.
I found a reasonably nice SDL tutorial here. In fact, TreeWars was just ‘sdltutorial’ (with plans to start writing a game once I’d worked through some tutorial programs) until I got to lesson where you implement Tic Tac Toe, and my mind insisted ‘Tic Tac Toe is boring, let’s make a real game!’ I justified that I often learn better when I’m puzzling out the best way to do something myself, and leapt into it.
The problem is, I often learn better that way, but I also often learn the hard way. I stumbled through a lot of ‘wrong’ ways of doing things in SDL, wasting a good deal of time hunting down the correct function (out of numerous functions available in the API) for getting a particular result. In the middle of all of that, it occurred to me that there is a knowledge level for which tutorials are never written. Tutorials are often written in a way that is so basic that I have to skip past large portions and carefully pluck out that bits that are relevant to me. On the other hand, APIs are often dense, and each function definition assumes you know the platform generally, so getting deep knowledge out of it presents a bootstrapping problem. Libraries almost never have a ‘using our library for the first time’ guide, or a best practices document, or a list of caveats that newcomers to that particular library may need to know about. So you get your choice between a too-low-level API for a huge, unfamiliar library, or a guide that tells you over and over that you need to call SDL_FreeSurface() when you’re done with a surface.
Instead, I’d like a guide that goes something like this:
Before you start drawing anything with SDL, you need to include the headers and call SDL_Init. In Linux, the headers are typically at /usr/include/SDL/SDL.h, but for portability it is recommended that you include the SDL header directory directly with ‘-I /usr/include/SDL’ and use “#include <sdl.h>”. A common init call would look like this:
[sourcecode language=“cpp” gutter=“false” wraplines=“false”]
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) {
// error handling code goes here (return false
// if calling from an object or function,
// perhaps, or possibly exit())
}
[/sourcecode]
Typically, drawing graphics in SDL starts by creating a double-buffered, hardware-accelerated SDL_Surface object with SDL_SetVideoMode, like so:
[sourcecode language=“cpp” gutter=“false” wraplines=“false”]
SDL_Surface* window;
window = SetVideoMode(1024, 768, 32,
SDL_HWSURFACE | SDL_DOUBLEBUF);
[/sourcecode]
You can think of this as your main window (or root window, if you are comfortable with that terminology). You can draw other SDL Surfaces onto it (a process known as ‘blitting’ for some reason), and you can render the current contents of the (double-buffered) window with:
[sourcecode language=“cpp” gutter=“false” wraplines=“false”]
SDL_Flip(window);
[/sourcecode]
‘Flip’ may not make much sense until you understand the metaphor in use for a double-buffered surface. Think of a flat, thin surface with 2 sides - one side is visible (facing the user). When you draw onto the surface, all of your images are drawn on the invisible side - the side facing ‘away’ from the user. When you ‘flip’ this surface, the part that they were looking at is hidden, and the part you were drawing on is exposed. This lets you do your graphics updates ‘all at once’ instead of having the user see each object as you draw it.
So, to refresh the screen once per second, you might do something like this:
[sourcecode language=“cpp” gutter=“false” wraplines=“false”]
// assuming you have already initialized
// a surface called ‘window’
while (true)
{
// draw some stuff on window here
SDL_Flip(window);
sleep(1);
}
[/sourcecode]
There are several ways to draw things on surfaces - you can blit another surface on with SDL_BlitSurface(), or use SDL_FillRect() to draw a rectangle (this is SDL’s primary primitive shape, so procedurally generating curves becomes an interesting problem).
Of course, in practice you would probably want to do other things than just continuously render images - you need to respond to user input, iterate any data that needs to periodically change, etc. You might do those things in separate threads, but it is often simpler to check for them in a loop like this one. In fact, most graphics-based programs that respond to user input do things like this.
Oh, one other thing. When you’re done with a surface, you always want to clean it up with:
[sourcecode language=“cpp” gutter=“false” wraplines=“false”]
SDL_FreeSurface(window);
[/sourcecode]
Next time, we’ll look at some particular problems I encountered, and how I solved them. Then we’ll look at how I solved them correctly, to demonstrate that there are usually many ways to solve a problem. This will also let us highlight the difference between good and bad code. And we might talk some more about the joys of using the SDL API.