Cytosine postmortem

I’ll talk about what went well, what went badly, and how I could have done better. That’s not to say I’m not satisfied; on the contrary, I’m really happy how things turned out, but there’s always room for improvement eh? I’ll also throw in some things that did go well, so that other LD’ers might learn something.

This post will make more sense if you play Cytosine first and/or read how it works.

Evolution of the idea

No pun intended, really! Waking up on Saturday morning and seeing the theme, I realized that “evolution” did not fit any of the three or four ideas I’d been saving for a rainy day. So I started out with some brainstorming using a mind map. Clearly, some kind of evolution had to take place in the game, but how would this interact with the player? At the bottom of the sheet, I wrote four possibilities:

  • Player controls breeding?
  • Player controls conditions?
  • Player battles evolving species?
  • Player plays in evolving levels?

Clearly, the third of these piqued my interest. Why I chose to do Space Invaders instead of another type I don’t know; it was the first one that popped into my mind and I stuck with it.

That might have been a good thing, but in retrospect, I should have made it a continuously scrolling shoot-’em-up rather than a static one with levels. That way, evolution could happen much faster: when aliens breed, their children could immediately appear, and the feedback loop would be tighter and clearer to the player. Overcrowding would trivially be prevented by scrolling the surplus population off the bottom of the screen.

Unfit fitness

Another problem with the current approach are the fitness criteria: what makes a particular set of genes “good”? In the final version of Cytosine, the aliens must do two things to reproduce:

  • survive
  • stay close to at least one other alien

Note that the player does not enter this picture only indirectly, through the “survive” criterion; but surviving can be most easily done by staying far away from the player. This promotes cowardly behaviour, and does not breed for aggressive traits that could have made the game more interesting.

I had hoped that accurately dropping bombs on the player would contribute to survival, but as it turns out, moving towards a bullet-spraying tank generally leads to instant death. In hindsight, I should have changed the rules to add a third fitness criterion:

  • hit the player

An alien would only be allowed to reproduce after it has dealt damage to the player; the number of children it gets could even depend on the amount of damage. Instead of “die on first hit” the player would have a health bar, or a huge number of lives.

This might not be realistic, but games never are; they’re supposed to be fun and challenging!

Choice of platform

I’m really happy to see significantly more HTML5 entries in this Ludum Dare than in my previous one (#22). The web has the lowest barrier to entry of all platforms to date, both for the player and for the programmer.

And it’s finally beginning to take shape: all major browsers except Internet Explorer support enough of HTML5 to make a game, and if you use the right feature subset, porting is trivial. I developed using Firefox on Linux, but all I changed to make it work in Safari and Chrome on OS X and Windows was some CSS because the font rendered with a different baseline.

However, it’s not for everyone. Even though I’ve finally got the hang of JavaScript and its dozens of peculiarities and pitfalls, the language still caused problems at times. Ever tried writing something like this?

return
     overlap(a.x - a.width, a.x + a.width, b.x - b.width, b.x + b.width) &&
     overlap(a.y - a.height, a.y + a.height, b.y - b.height, b.y + b.height);

That works fine in all other languages with C-like syntax, but makes your function return ‘undefined’ in JavaScript, because of semicolon insertion. Go figure. I had wanted to learn CoffeeScript before the compo, but I didn’t have time.

Graphics

Often, when people use OpenGL for any kind of program, they find themselves staring at a black screen for hours while trying to find out which of the 30 different function calls they got wrong. Fortunately, I had prepared by writing a WebGL wrapper library (called Gladder) so the staring-at-a-black-screen-time was reduced to less than half an hour; not bad!

It wasn’t all perfect though. I tried to use framebuffer objects to do some fancy glow and motion blur effects, but found that rendering to a framebuffer disables antialiasing (which you get or don’t get at the discretion of the browser). Things looked so much uglier, and I didn’t want to implement my own expensive supersampling, so I abandoned the idea.

I had prepared for doing sprites by forgetting to install Paint.NET, but in the end I used a pure geometry approach without any prefab pixels at all. That saved me some time by not having to muck around with texture loading code. Besides, vector is relatively under-used, and looks different and cool.

Sound

This was a mixed bag for me. Creating sounds with bfxr was a breeze, also because I’d used the program before. The music was more of a risk, because I had never done that before, so I saved for last “in case I had time left”. I used LMMS, which I’d barely used before, and never for a complete song, but I think it turned out pretty catchy. The song is really simple; I’m still not sure if I have any musical talent, but if I do, it disappears as soon as I touch a computer.

The technical side was less of a downhill road. In my previous LD entry, I was forced to rip out all sound at the last minute, because some browsers (looking at you, Chrome) decided to re-download the sound every time it was played.

Having learned from that, I intended to use a JavaScript library that uses Flash behind the scenes. I’d meant to try some of these before the weekend, but forgot. So when it was time to add sounds, I tried SoundManager 2 first, but found that it resulted in a 200-500 millisecond lag; far too much to be useful. I didn’t get round to trying jPlayer, but its demos seemed to have similar issues. This appears to be common for Flash audio; why Flash games don’t suffer from it, I don’t know.

I decided to give plain old <audio> another try, but with a ‘data’ URL. This lets you embed the actual sound file in base64 encoding directly in the src=”…” attribute. I wrote a few lines of Ruby, so that I could put INCLUDE(sound.wav) into my HTML and it would get substituted.

This worked, but the problem is that each <audio> tag can only play a sound once. Rewinding back to the beginning is one of those things that don’t always work in all browsers. For polyphony, you have to create new Audio objects in the code. And then it dawned on me: if I’m passing this src attribute from the HTML into this Audio() constructor, why not load the base64-encoded wav file via AJAX instead? So that’s what I did, and it worked perfectly. I don’t even wait for it to load, betting on all sounds having been loaded by the time the game starts. The code is right near the top of game.js if you want to have a look.

Of course using base64 encoding gives a 38% increase in file size. It might be possible to download the binary WAV via AJAX, then base64 encode it in JavaScript, but I haven’t tried. I could also optimize by using MP3 and Ogg Vorbis files, but to have it work on all browsers, you need to supply both. As these sounds are short and there aren’t many, the overhead of WAV is acceptable. For the music, I did provide an MP3 and an OGG file, of course.

Looping the music turned out to be another interesting challenge; the ‘loop’ attribute was ignored by Chrome (if I remember correctly). I ended up attaching a rewind-and-play handler to the ‘onended’ event, as suggested here. It’s not at all gapless, but that’s at least in part because I trimmed the WAV file, then accidentally generated the MP3 and OGG files from the untrimmed one, and blamed the browser for the gap until the compo was over…

Conclusions

These are my recommendations for current and aspiring Ludum Darers:

  • Have a concrete idea. If it’s too vague, make it concrete before you begin. If there is one thing you take away from this post, let it be this. “Something with evolving enemies” is just not good enough to start coding, and you need to start coding early. Ideas can be changed instantaneously; code cannot. Steal (parts of) other game concepts if necessary; being 100% original is nearly impossible anyway.
  • Every once in a while take a step back to see where you are. Reality checks are essential. Maybe set a timer to take a break every few hours. It’s worth it. Keep asking yourself the question: “If there were only one more thing I could add or change, what would be it?” That thing is the most important, and that thing is exactly what you should be working on.
  • Know your language, platform, libraries and tools. Choose something you’re already comfortable with. In a 48-hour compo, you need to use your time effectively; learning a new language or library is not something you want to be doing. I solved this problem by writing my own library, ensuring that I knew every line of it, but of course that’s not the only way.
  • Abandon things that don’t work. You could waste hours trying to get that one nifty framebuffer effect working, or you could use those hours to fix glaring issues with the controls, or to make sure you have more than half a level. Version control helps here, by letting you revert to a previous revision.
  • Save risky things for last. Otherwise, you’ll risk wasting time that you could have spent better elsewhere. If you’re not sure something will pan out, only try it after you have completed the essential parts of your game.

I hope this post was enlightening and at least somewhat useful. Now, if you’ll excuse me, I have a thousand games to play.

Tags: postmortem

Comments

27. Aug 2012 · 18:10 UTC
Mind maps rock, am I right? Don’t know where I’d be without them.
27. Aug 2012 · 20:19 UTC
“That might have been a good thing, but in retrospect, I should have made it a continuously scrolling shoot-’em-up rather than a static one with levels.”
27. Aug 2012 · 20:31 UTC
Kurama_Youko: That’s really interesting! Why did you want to go the other direction?
27. Aug 2012 · 21:10 UTC
I think it’s because I wanted to add an evolvable weapon system and powerups, which made more sense in a shoot-’em-up context (they didn’t make it into the game due to time). Also, I simply didn’t have any better idea during the competition. Now yours seems to make much more sense to apply standard genetic algorithms, and since I didn’t implement the weapon system… Well, maybe it’s just that “the grass is always greener in the neighbor’s yard” 😛