Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Cloud Fighter Postmortem

A topic by retrogradeorbit created Aug 13, 2016 Views: 508 Replies: 3
Viewing posts 1 to 4
Submitted (8 edits)
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!

Submitted (1 edit)
Part 2:

What did you learn?

Mainly the lessons I outlined in What went wrong. After each game-jam I take the lessons and I apply them to InfiniteLives. And I refactor out any common code from the games into InfiniteLives. I guess I learnt to love Clojure more than I do already!

What did Lisp enable you to do well in this entry?

I find Lisp enables me to manipulate and refactor code quickly and easily. Because the editor can understand the syntactic structure of the code, it can help you in ways that it can't when you are writing in other languages. Emacs helps a lot here with paredit and expand-region and Emacs macros. Also to state the obvious, Lisp macros are a godsend in helping you remove boilerplate that higher order functions can't remove. Like the aforementioned go-while macro.

I find Clojure helps even more than your usual Lisps by being immutable. Immutability just helps everything come together at the end so seamlessly. Nothing inadvertently impacts anything else because nothing changes! So even when you are rushed and write really shitty code (my game-jam code is often shitty), it all comes together in the end seamlessly and without any crashes or bugs.

Also by forcing all your mutation to occur through the registration of pure functions that the STM applies to the atoms on your behalf, it makes all the state mutations happen through one point. So an example of how this helped in this game was right at the end when I had to add bonus lives. You get a bonus life at 10,000 points, 60,000 points and then every 50,000 from then on. Because all the state lives in this one giant state atom and all the change happens to it via a pure function operating on the old immutable value, and returning a new immutable value to replace it, adding this functionality right at the end was trivial. Rather than the score changes being sprinkled all around the code, they were all in one spot. And in that spot I had the old value of the atom, and I had calculated the new value. So if the old value was less than 10,000 and the new value was more than or equal to 10,000, you had gained another life. And the life was in the same atom. It's all there in that atom. So I just return the new lives as part of the returned immutable value to replace it in the atom. And it works perfectly.

What challenges did Lisp present in making your entry happen?

The only real challenge that ClojureScript throws up in game-jams that I've learnt to switch off is advanced compilation. The advanced compilation in ClojureScript is incredible giving you extremely small compiled JavaScript artefacts that run extremely fast and includes things like dead code removal and symbol minification, but it can be time consuming to get your code to run correctly as you have to specify all your external namespaces. This time consuming work can really destroy your morale in a game-jam so I've long ago learnt to switch it to simple compilation for jams. The code then is fast enough and all those tricky issues go away. If you are really eager later, you can switch it to advanced compilation and then deal with any badly munged names and write your externals files then.

If you are comfortable answering, please you mention how long you have used lisp and describe previous gamedev experience, if any.

I learnt Common Lisp many years ago but never really got deeply into it. Then about four or five years ago a developer I was working with at the time switched me onto Clojure and I have been using it heavily since then.

Back in the old days I did some game development for the Gameboy Advance in C and C++, then subsequently made some games with Python. I've been using Clojure and ClojureScript now for about 5 years and have done 2 global game jams and about 5 ludum dares with it since then. If you want to check out some of my other game-jam work you can see it all on my github.

Host

Awesome post!!!

Host

I used to love Time Pilot on the Colecovision :)