My very good friend Andreas Brinck and I have been working for some time now on a hobby project on our spare time. It all started out as a very large project, we had a game design and all for a rather ambitious platform game. We quickly realized that we would not be able to pull it off with just two programmers, so we kind of changed gears and decided to try a simple puzzle game with minimal art resources. This would be our first collaboration in years and this time around we were doing remote developing, Brinck is located in Sweden and I'm here in California, which added to the difficulties. The big one was probably the time difference, while I'm sleeping or at work, he's coding and vice versa. This article will go through some random thoughts that I've had during development and how we tried different things that did and didn't work. I guess it's a post mortem of a sorts.
In the beginning
It all started as I've mentioned with a design document. I generally hate them, but in this case I actually took a stab at writing one that would communicate our intent to the reader. It's really hard to write a good design document, it's much easier to just code a prototype instead. As it turned out this document is still in our repository, but not used since the game it outlines is far beyond our capacity as a two man spare time team.
One of the things we wanted to was to create a mac game for their OS X. Since we both were very comfortable with OpenGL and SDL it seemed a natural choice to continue using them for maximum platform compatibility. We still develop our debug versions on windows, but had an eye out for cross platform stuff. That is not to say that we took any particular detours for ensuring that things would run on mac, a simple #ifdef and an #error was the norm. Plan for the future but code for today I'd like to call it. In the end we actually have very little mac code, if any. Somewhere in the middle we decided to continue with the windows version and release that one as well, which in the end proved to be much less hassle. The main cause for this is the fact that we only have one mac box, my old G3 laptop, which isn't very good and very slow due to limited memory and OSX being a real memory hog. So eventually the Mac version died (hey if someone at Apple wants to donate a OSX box to me and Brinck...).
Generally I don't really like engines for the sake of the engine. In our case, the engine parts mostly grew out of the demands of the game which is great since they now have two clients, the tests and the actual game. One of the realizations we made early on was that the scope for the game was very limited. The main goal of the game was to get something finished and the byproduct would be proven libraries that could be used in practical applications.
In the beginning we had a small set of functionality that I had ported over from an old project of mine, the main thing this time was to write unit tests for the libraries. So as a foundation I had the old libraries but started out to rewrite them with the help of TDD (Test Driven Development). I also developed an internal unit test framework, AuroraTest, that we use for all our unit tests. This is of course heavily influenced by CppUnitLite2 that I've used while I was at High Moon Studios, as well as many little things from AuroraTest made it into CppUnitLite2 (and it's successor UnitTest++) as well. From the modest beginnings of having three small libraries, AuroraTest, Core and Math it has expanded into almost 20 small libraries. The emphasis is on small, some of them are really tiny and bareley make it into a library, e.g. DebugFont is simply one class and Timer is just 4 cpp/h pairs.
At one point we need a GUI library for handling out menus and 2D elements of the game. This library grew out of need, but it took the wrong turn from the beginning. If you haven't seen Casey's talk about IM gui v/s retain mode gui, do so now. It's really good. Ok. Are you back? Once again in this project my experiences with GUI's was horrible. This time we had ourselves to blame, in the past I've been locked into the GUI library provided by someone else. Many of our problems were related to synchronizing the states in the actual game and the GUI, which is insane. At this point we're both very much fed up with the whole retain mode gui idea that we're for our next project are going to make it a priority to create a lean mean library for IM gui. It's not as if it can go much worse than the one we already have...
In addition to a slew of small libraries that we needed, we also prototyped small tools for converting our data from the raw format into something we could load into the game itself. It's been a philosophy of ours to start with data and always have separation between engine data format and the intermediate format. In past games I've even gone so far as to have three formats, exported, intermediate and engine format. One of the hard parts of writing games is to have a good tools pipeline up and running. We actually cheated a little bit with the authoring tools, we used already existing tools. One example is the tool we used for creating the levels themselves. After a lot of searching we found the little java editor tiled (showing to the right).
After we've authored the map itself in the editor, a simple pipeline takes over. Nant is controlling everything and invoking command line tools that takes one file at a time and transforms them into suitable formats for the engine to load. For example, the tiled map editor outputs simple xml text files with all the information and we have a mapcompiler that takes that xml and after a couple of transformations (e.g. fill in coloured tiles and making sure that the level is solvable) spits out a binary blob that the game knows how to load. This of course have the not so strict coupling from the binary format the compiler writes down to the binary format the game expects to read. They better be the same or strange things will happen. We've traded this for the separation between preprocessing and the actual game. This also means that we can't in the real game load native xml files, which is a good thing actually, so any hotloading we need to do has to happen in three steps, detect, invoke compiler and load into game.
I just happened to use NAnt at work so I took that experience with me and started to use it at home as well. After a couple of modifications and experiments I arrived to the conclusion that if you write your own nodes, which is surprisingly simple, you can have it do what you want. I could not understand how the intended use was making any sense, I must have misunderstood. But with this custom node in nant you can have it work very reasonable. I view build systems for data much as I view mail clients -- they all suck, you just have to pick one that annoys you the least and stick to it.
The prototype itself Brinck wrote at the start. The fact that is was written in C++ already and working was a compelling argument, it's so much more powerful when you have a working demo that you can play in your hands instead of a dream design document that doesn't have any realization to it yet (that is not to say that they are bad, just that the playable version is so much more powerful in communicating the intent and mechanics).
At one point when we were developing the prototype into a more full fledged game we were commenting on the fact that the prototype was insanely small, all it did was using straight up SDL and OpenGL immediate mode for rendering and the whole game logic was contained within two files. Even though the code might not have been the best at that point, the fact that it was so small made it work and when we began to add more and more code to it to do things "right", e.g. rendering everything with shaders and meshes instead of immediate mode, the code complexity increased and the nice simple and small prototype vanished and in it's place this more clunky and not that more capable thing had emerged. At that point we had to do some refactoring passes to enforce some structure that made more sense now that the project was bigger. This is a small trap for people that develop prototypes and then take them to the next level, but with the same structure. What worked in the prototype may not work in the final game. The game is usually not as forgiving as a prototype and the scope is larger. Whereas you can hack stuff in the prototype, in the game this usually comes back and bite you. The code becomes more cryptic and since the actual lines of code now have grown, this presents a problem since it's not possible any more to get a quick overview just like that.
Closing in to final
So when the game is starting to close in on a final version, we have a couple of more features than we had in the beginning. Most of our later feature and efforts were focused on the GUI and presentation to the player. Very little effort were put into the gameplay at this point. Font polish, nice transitions, experimenting with user input, these things were the kind of issues that we dealt with at the end.
Ran out of steam
In the end it our little project proved to be a failure after all. We set out to actually finish something but now it feels far from finished and we're validating the old saying :Projects are not finished, they're abandoned. But all was not lost, we did validate our first impressions that the initial project was far too big in scope and this we could finish. We also did, after a fashion, but there is a lot of functionality missing as well that we would have like to put in there. Monsters, more sophisticated powerups and crazy score schemes. But in the end we also needed to remind ourselves that the purpose of the project was to finish something and let it go. The game did not develop in the direction that we expected it from the start either, after the initial prototype.
Wrapping up this project have taken a long time as well, I remembered that I wrote most of this postmortem when I was in Paris in the end of May this year, it's been unfinished since then. So I think it's time to abandon ship, we've got lot's of new ideas that we want to implement and try out so this will probably join the prehistoric repository of little demos we've made.
It really helps to have decent art in the end, Brinck was actually an artist before he converted and found the true path in programming, so we have lightyears away from ordinary programmer art (stick figures and crooked lines). If nothing else, it really helps with morale within the production team to see nice looking things moving into the game. Even though functionality might not change or any code is necessary, just having decent art is a huge thing. Half of the game is presentation and the other half is gameplay.
You can download the game itself below. The installation process doesn't do anything else than copy files to the destination directory, no cleanup of registry. The only thing it does is to modify the highscore file that lives in the same directory as the data files.