Cloud Fighter - Postmortem
Part 1:
What dialect/tools/libraries did you use?
The lisp dialect I used was ClojureScript. I have done a bunch of game-jams in ClojureScript before and I have been working on a library focused on game-jams called InfiniteLives. I used InfiniteLives here as I have in the past. InfiniteLives is tailored towards making games rapidly in game-jams. Although you could probably use it for a larger scale game, this is not it's design goal. It's design goal is to help you make simple HTML5 web games quickly. It does this by having powerful constructs and intelligent defaults. Theres a 2D part of InfiniteLives I used called InfiniteLives.pixi that uses pixi.js for the graphics. Pixi.js provides a canvas and WebGL back-end and provides you with higher level constructs like sprites and layers and display containers.
I started with the figwheel template, and used figwheel as the main development server during development. Figwheel, if you've never used it, it's amazing. You must try it out. It watches your source code and when a file changes it compiles it and hot-loads it into the browser and into your running front end code. Although it's more tailored for Functional Reactive front end work (using a React based library like Om[next] or Reagent), it's also extremely useful for web game development and allows you (once you know how to write reloadable code) to tweak code and parameters of the game and watch the effect live in the game without having to reload the web page and get the game back to the state it was in. This fast feedback loop is critical to effective game-jam development because you can tune and tweak you game while playing it to immediately see the effect your changes have.
In the way of tools, for graphics I used aseprite which is a fantastic graphics editor for doing index coloured pixel art. I use a Wacom Bamboo stylus tablet for drawing. For sound I normally use sfxr or one of its clones like jsfxr, but this jam I tried out ChipTone and was very impressed with the results. It has the same kind of tools and feel as sfxr, but also a lot more goodies thrown in for good measure. Definitely one to check out. It requires flash though.
The code editing was done in my trusty old emacs editor. I know emacs is clunky and wierd, but I know it well so I'm over its crazy learning curve, and its lisp support is really fantastic and complete. I use the cider plugin connected to the figwheel nrepl server for live code evaluation in the editor.
What sort of game did you choose to make, and why?
I chose to make a clone of the old 1982 arcade game Time Pilot. For a couple of reasons. Ever since I build myself a MAME arcade cabinet I've found Time Pilot to be one of my favourite games. I really have enjoyed playing the game a lot and have wanted to write a modern version of the game for at least the last five years. Also, the game is very simple. Hey, it was 1982. Things were simpler back then. There is no real level, just a bunch of parallax clouds. And the enemies and levels are very simply laid out. Simple is perfect for a game-jam, where there is no time for the complicated. Also, the game is already out there, so it makes it much easier to know what to do and where to take the game. When you design a completely original game you have to spend a lot more time tuning and tweaking as you are still exploring the game space and fleshing out the ideas. Of course I didn't make an exact clone and I took some opportunities to make some changes and alter the game where I saw fit. If you think that I 'stole' the game idea then I cite Everything Is A Remix as my defence! Haha. Don't be afraid to look back into the past for good ideas and copy them, borrow from them or mash them up into something entirely new.
What went right, what were some successes?
A lot went right this game jam. Having 10 days certainly helps. I am used to doing 48 hour game jams where time is extremely tight, so 10 days felt very luxurious. Of particular success was I got to use my new spatial hash for collision detection. In the past I've sloppily knocked together a "compare every entity with every other entity" collision detection which increases the cost of collision detection. I'd end up doing things like (when (zero? (mod framenum 3)) (collision-detection!)) so that the expensive process only ran every 3 frames! But by using the spatial hash this time collision detection became quick and easy. I could issue a spatial query on the structure to find out all the entities in a section of the screen quickly and painlessly.
Also what went right was my super-cool early exit go block macro. Normally go blocks in Clojure cannot be 'killed'. In true functional style you must manually exit them from within when such conditions occur. So I made a macro (go-while test body) that would process the body code and anywhere where a synchronisation point occurred (<! or >!) it would thread the test form around it, so that after the go-block resumed from the channel push or pull, it would run the test and if the result was true, it would raise an exception that would bubble up to the top of the go block killing it. It worked really great.
What went wrong?
Funnily enough, the same things that went right! The spatial data structure stored all the entity positions as Clojure vectors of [x y], yet all the in game positions are vec2s. This meant I had to constantly convert back and forth between the two when using the code. This is a good lesson and I will alter the spatial hash code in InfiniteLives to take vec2s.
The go-while macro, although awesome, had some issues. Firstly, it descended too far down into the block code. If I nested a go block inside another go-while block, the outer go-while's test conditions would thread through the push and pull calls in the inner go block. This I can solve in future by stopping the block processor from descending into inner go blocks.
Also, I came to realise that I had built two things into this go-while macro and that it really needed to be split apart into its separate components. I bundled the early exiting functionality with the go macro itself. Sometimes you don't want the go block to exit completely. You want a section of it to exit. Because after the condition is met you want to do something else to transition out of the go block. So for instance, when you press fire to begin the game, the title graphics just vanish suddenly into thin air as the go blocks explode! What should happen here is they should go whoosh and fly off screen and then the game begins.
I can fix this in future by getting rid of go-while and making the macro continue-while, and not include the go call. So instead of writing (go-while test body) you would use (go (continue-while test body)). This way you as the coder can decide where the exit occurs and where the go block can continue. This way you could write (go (continue-while (fire-not-pressed?) title-code) (title-exit-code)) and have the exit transition from the go block.
It's funny how so much of improving code is taking things and splitting them apart into smaller, simpler, un-complected sub-things!