Nice balance between exploring and coming home.
retrogradeorbit
Creator of
Recent community posts
Day 2 (graphics from free tile set on opengameart by Buch https://opengameart.org/content/a-blocky-dungeon )
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.
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!
If you decide not to continue with this work it would be great to see the code and run it as is. Continuing and finishing it would be great, bu also consider uploading the source to github if you do abandon it. I enjoyed reading your thought processes and learnings around your approach in your posts and would love to see what came of it.
A quick update:
Added lots of stuff. The enemies now shoot at you and also fire homing missiles. You can shoot the missiles out of the sky but not the bullets. There are also now parachutists who appear and float across the sky for you to rescue. If you shoot them they die. This creates a nice tension.
The game generally is way too hard now. It begins immediately on expert level. This is no concern as I can tune the parameters and constants later. What I need first though is a level progression. After shooting a certain number of planes down, the boss plane should appear and that should take a bunch of shots to kill. Once you've killed that the level should empty out and you should progress to the next level where things are slightly harder. Other levels could have some different graphics, too. Different planes, etc.
I plan on making the levels otherwise pretty much the same. Maybe a different sky colour just to confirm that things have changed. So three days left. One day for level progression and end boss. One day for sound effects and music. And one day for graphics work. Should squeeze in just in time. :D
Play the latest version here: https://retrogradeorbit.github.io/cloud-fighter/
I implemented collision detection. It's the first time I've used my spatial hash code in a game-jam. I think it went well, and it certainly works well, but I discovered a few clunky parts of the API when using it. Firstly, it expects all the positions to be vectors of [x y]. Yet in the game you inevitably don't have that, you have a vec2 (part of the google closure library), which is a JavaScript object. So every time I work with the position I'm going (vec2/as-vector pos). This has to be introducing unnecessary slow down too, as every tracked object is updated every single frame. As always with game-jams, that'll do for now, as after the jam I can refactor the code to be cleaner.
I had to implement a quick explosion which I just outlined. It's a technique I discovered in a past game-jam. That is, don't flesh out any of the graphics until the last hours of the game-jam. Just do quick outlines to use as place-holder art and get back to the game. This is for a few reasons. Firstly, I'd done too many game-jams where you spend all this time making assets you just don't end up using. And that time ends up wasted. And in jams time is the most precious thing of all. So by just doing outlines, and then immediately placing the asset in the game, you keep moving. Then at the end you know exactly how much work needs to be done.
The other reason comes from an audio mixing book called Mixing With Your Mind, by Michael Paul Stavrou. One of the tips in the book is to avoid left brain/right brain context switches. There's a whole bunch of stuff you do when recording music that is technical, like laying cables and setting up microphones and connecting and troubleshooting equipment and so on. These are all logical tasks that exercise the left-brain. Then there are a whole bunch of tasks that are creative and right-brain, like balancing a mix, choosing effects and plugins and tweaking settings etc. If you keep context switching between the two, you do a poorer job of both sets of tasks.
However if you group all the left brain tasks together, and group all the right brain tasks together, it allows you to get more into a flow in each endeavour and you get better outcomes. So I extended this idea to game-jams. When you are doing logic and code you want to try and stay in the code and not be drawn out to more creative artistic pursuits. And when you are being artistic you don't want to be drawn back into the code. By using place-holder art (and place-holder sounds) you can quickly return to the code before the 'context switch' sets in. And then at the end of the game-jam you can spend a bunch of time getting into flow around the art and sound.
I haven't got the death and losing a life, then restarting the game loop done yet. That will be next. For now, when you die, you have to reload in your browser to start again. After that I will setup the level progressions. I'll aim to get all of this done before adding more enemies and variety to the game.
So have some fun and play the game as it presently stands. It plays best with a gamepad controller, so after the page is loaded just plugin your USB game controller and go from there!
Today I fixed the player's oscillation bug. The function now returns a vector. I still use the dot product method to determine if the player is turning left or right, but then I apply the turn to the heading vector (left or right depending on the dot product) and then I calculate the dot product again for this new heading. If it was left and now it's right, or it was right and now it's left, I know the turn has overshot the destination vector and I just return the destination vector. This cured the oscillation bug and the players turning feels more solid now.
I then removed the autofire. I'm going to start with the old school notion of one press, one bullet. It's a little painful to shoot a lot, but that's good. If I get time I can add an autofire pickup later in the game to make life easier.
Onto the enemies. I started with a basic wandering boid, with a little bit of seeking the player. I set up the 'e' key to spawn a new enemy somewhere near the player on screen. And I drew a rough outline of a plane into the sprite sheet. The difficulty came in making the enemy adjust for the players 'position'.
There were two ways to go. I could actually move the player around, like in a conventional game, and make the camera track the player to keep the player centered. This is a method I've used in past game jams. But the game is much simpler than other games I've built. The player is always in the center, and the world only exists for one screen around them. Really it's all an illusion. The parallax clouds just wrap around and are randomly placed. So there is no 'level' to speak of in a conventional sense. So I decided to keep the world's co-ordinate system static, with the player permanently anchored at the center [0 0], and to move everything relative to the players direction around them. This is more like the technique that would have been used back in the day on old 8 bit computers.
To do this I moved all the players state into a state atom in a separate name space. And then just adjusted the boids position in its go-loop each frame by the last velocity. This worked well and it does feel like you are flying around a world, even though the world is flying around you.
Next task is to implement some collision detection. Bullets shoot planes and collisions go boom!
Play the game here: https://retrogradeorbit.github.io/cloud-fighter/ and don't forget to press the 'e' key to spawn enemies.
Test it: https://retrogradeorbit.github.io/cloud-fighter Source: http://github.com/infinitelives/infinitelives.pixi
Didn't have much time today. Only a few hours after work and before bed. Put in the directional controls. And added Shooting.
For the directions I needed to work out if the player was turning left or right. So I took their present heading and rotated that vector 90 degrees to the right. Then I did a dot product with the vector of the analogue stick. If a dot product between two vectors is positive, then the angle between them (with their tails coincident) is an acute angle (less than 90 degrees). If it's negative its a larger, obtuse angle. So if its positive we turn to the right, if its negative we turn to the left.
The code for this was very succinct, but it actually didn't work as well as I'd hoped. When you hold the ship in one direction, the ship rotates to that position and then oscillates around the position, turning left, right, left, right, as it overshoots the final position with each frame. You cant see this on the ship itself because the oscillation is so small. But as soon as I added the bullets you can see it with two streams of bullets flying out alternating!
What I need to do is write a function that takes the present heading, the wished for heading, and returns a new heading that is slightly closer to the wished for heading, or if it's close enough, the actual wished for heading. Instead of returning :left or :right, it needs to return a vector.
I won't have much time again until the weekend, but I'm gonna try and get little bits done so the weekend can be as productive as possible.
Im making my game in ClojureScript using the InfiniteLives.pixi gamejam library: http://github.com/infinitelives/infinitelives.pixi
Finished the titlescreen. I love the way the title words came out. Bouncing and moving like that.
Check it out here: https://retrogradeorbit.github.io/cloud-fighter/
You can see the source on github: https://github.com/retrogradeorbit/cloud-fighter/
I used an online title font generation utility: http://cooltext.com/
Also managed to nail an exitable go-block macro. I'm using ClojureScript, and in clojure's core.async, once a go block is 'running' there is no way to exit it. But I need to exit go blocks on certain events (like on the titlescreen when the player presses the start button).
The macro is go-while... (go-while test-form body...) After every synchronisation point <! or >!, it threads the test-form in. And if it evaluates to false, an exception is raised and the go-block terminates!
The macro recursively walks through the code, and leaves everything as it is, except anywhere where it finds a (<! ...) it changes it to (<!* test ...), and everywhere it finds a (>! ...) it changes it to (>!* test ...). Then the <!* and >!* are macros that behave like the normal macros, but after the channel pull/push, they evaluate the test, and if its false, throw an Exception.
Its working really well. Pressing a button on the gamepad or keyboard when you are on the titlescreen immediately exits the go-blocks. Success! After work this afternoon I will begin on the in game flying.