Lisp Game Jam Post-mortem
I recently participated in the 2016 Lisp Game Jam, an event where participants had 7 days (later extended to 10 days) to create a computer game using a dialect of Lisp, a family of programming languages. This is a "post-mortem" of that experience, in which I discuss what went well, what could have gone better, what I learned, and so on.
About My Game
For the jam, I attempted to create a local-multiplayer action platformer game, entitled "Treasure Jumpers", where you run and jump around trying to collect more coins and gems than the other players. (It's not a very inspired title, I'll admit. But it's better than "Platformer", the working title it had for most of the jam.)
I programmed Treasure Jumpers in CHICKEN Scheme (an implementation of Scheme, a member of the Lisp family of programming languages). I used Simple DirectMedia Layer (SDL) 2 for graphics, user input, etc., via chicken-sdl2, a set of library bindings I have created to make it easy to use SDL2 from CHICKEN Scheme.
I decided to create an action platformer game because it would allow me to test the performance limits of chicken-sdl2 more than, say, a puzzle game would. I initially considered making a space shooter game instead, but I decided it would be better to make that after I release chicken-sdl2 0.2.0, which will have support for SDL2's accelerated 2D rendering API. An action platformer doesn't need anything that wasn't already in chicken-sdl2 0.1.0 (except music and sound effects, which will have to wait until I make chicken-sdl2-mixer).
You can read about the development of Treasure Jumpers in my blog posts from during the jam. You can view my game jam submission and download the source code if you like. The latest code is in the chicken-sdl2-examples repository. I recorded and uploaded two videos so you can get a sense of what the game is like:
What Went Well
Coding in CHICKEN Scheme was a lot of fun, and very productive. Adding each new feature to the game was quite painless, as was refactoring and cleaning up the code. Scheme's functional style and macro system make it very easy to compose small, simple parts into a larger whole. CHICKEN also has many useful extra features that helped. Scheme is sometimes derided as being overly simplistic and lacking in features, but this is certainly not true of the major implementations.
It was great to put the chicken-sdl2 library bindings to the test with a complete game. It gave me a number of good ideas of things that I should add or improve in chicken-sdl2, and things I should create as a separate library. I was expecting to uncover at least one major bug in chicken-sdl2 during the jam, but the only bugs I found were relatively minor and easy to work around. I did run into a performance issue (described below), but I don't think it is caused by chicken-sdl2, just my quick and dirty game code. So, I'd say chicken-sdl2's maiden game jam was a success!
I really enjoyed creating my game. It is very satisfying to watch your own creation coming together, one step at a time. At first I had nothing but a few sprites laid out in a static scene. Then I was able to load a level from a file and draw it on the screen. Then I added characters which followed a simple parabolic gravity curve, but didn't collide with any of the tiles. Then I added collision detection, so the characters would collide with the tiles. Then I added user input, so you could control the characters by pressing keyboard keys. Then I added treasure that you could run around and collect. It was wonderful to see each new piece of the game come to life on my screen. Game development gives me a visceral joy that I don't experience with most other kinds of programming, where the results are usually less interactive and your successes feel less tangible.
Also, thanks to the long deadline (many game jams are only 2 days long!), I was able to take my time and clean up and document my code. This was important to me, because I was creating the game primarily as an example for people to learn how to use chicken-sdl2.
What Could Have Gone Better
Overall, things went pretty smoothly. There were a few things that could have gone better, though.
The main thing is that the game has a performance issue, where CPU usage gradually increases and framerate gradually degrades over time. After several minutes of playing, the framerate can become so choppy that it is difficult to keep playing. Since the issue takes several minutes to manifest, I didn't notice it until late in the jam. For most of the jam, I was testing the game for only a few seconds at a time to verify that the level was rendering correctly, or the players were moving correctly, etc. It wasn't until my game was complete enough that I could actually play the game for several minutes, that I could finally notice the issue.
I suspect that the issue is caused by my quick-and-dirty code allocating more objects than it needs to, which puts a lot of pressure on the garbage collector, so that garbage collection takes longer and longer over time. The main culprits in my code for allocating objects were the physics and collision detection systems, since they generated new temporary bounding boxes and hitboxes every frame, at ~60 frames per second. I spent a few hours trying to optimize that code by reusing existing bounding boxes and hitboxes, and I managed to alleviate the performance issue somewhat, but I wasn't able to fully solve it.
I think if I spent more time optimizing the code, or if I had been more performance-conscious from the start, I could solve the issue. But in a game jam setting, you're always trying to implement things quickly, and the quickest way to implement things often involves a lot of temporary allocation, for example mapping over a list to create a new modified list. This practice isn't specific to Scheme, but functional-leaning languages like Scheme make this especially tempting, because many of the built-in operations make this style of programming more concise and convenient than other styles. But for a game, especially an action game running at 60 frames per second, that way of programming can easily lead to performance issues, even when you are using a mature, high-performance implementation like CHICKEN Scheme.
This experience has given me ideas for features I can add to chicken-sdl2 to help with performance, such as pools of reusable rects and points, so that the programmer can borrow a rect or point for a while, use it for some temporary calculation, then return it to the pool. That would help alleviate some of the allocation and garbage collection of SDL objects, at least.
Since Treasure Jumpers is an example game for chicken-sdl2, I will probably revisit it from time to time to improve it and showcase new features. I will also probably try to improve performance when I do. But for now, I'm putting this game on the shelf.
Spending Too Much Time
There were a few aspects of the game that I spent too much time on, to the detriment of other parts of the game. The biggest time sink was collision detection and resolution. Even for very simple collision detection like this game has, it takes a lot of time to implement, and then a lot more time to tune it so it feels fun. Platformer games are notorious for this, and many experienced game-jammers advise not to attempt them. Certainly if this had been a shorter jam, I wouldn't have tried.
There are CHICKEN bindings for the Chipmunk physics engine currently in development, but they are not released yet, so I didn't want to depend on them for my game. But if those bindings (or bindings to another similar physics engine) had been available, I certainly would have used them instead of rolling my own physics engine. Rolling my own simple engine was certainly fun, but it was not an efficient use of my time.
Besides collision detection, there were many smaller parts of the game that I spent more time on than I should, such as the event handling system. Again, they were fun to write, but I could have started with something simpler, and used the extra time for other features.
There were quite a few features I wanted to put in the game, but had to cut or postpone in order to get the game done in time for the jam, or because I ran out of energy towards the end. The features that I thought I would probably finish were:
- A HUD to show the players' scores
- A countdown timer so the game ends after a while
- A win screen to announce who won
- A simple GUI screen to choose which level to play
- Joystick controls
I could probably have finished those features if I had spent less time on collision detection, or if I had postponed more of the cleanup, documentation, and blog posts until after the jam. On the other hand, if I had put those things off until after the jam, I might have never gotten around to actually doing them. So maybe I made the right choice.
There were also some "maybe someday" features that I wish I could have added, but I realized early on that I would need to cut:
- Sloped (diagonal) tiles
- Water and swimming
- Player collision: bouncing, jumping on each other's heads, etc.
- Trap floors and switches
- Throwable or shootable items
- Player animations
- Music and sound effects
The final thing that didn't go well, is that the jam was quite disruptive to my daily life and sleep habits. This is common with game jams, but with a 2 day game jam you can usually compress and limit the disruption to a single weekend, then get a good night's rest Sunday night and go back to your normal life. For a jam lasting 7 or 10 days like this one, you have a whole week of disruption, and it is much harder to maintain a balance between the jam and your daily obligations and habits.
I work from home and set my own schedule, so I was never "late to work" because I stayed up too late working on my game. But, it was very hard to pry myself away from the jam and do my work, interact with my family, walk the dog, etc. I also found myself staying up much too late and not being able to sleep, because my mind was still thinking about the game and what I would program next. More than a week of late nights and trouble sleeping takes a significant toll on you! The last few days of the jam, I just ran out of steam and had to give up on some features I was planning to implement.
I think my ideal game jam schedule would be opening Friday morning or noon, and ending Sunday night. That way you have some time to think about what you will make during the day on Friday, then you can work Friday evening, Saturday, and Sunday, then you're done.
In any case, I think I will limit my involvement in future jams to no more than 4 days.
What I Learned
I'm pretty experienced with CHICKEN, and SDL, and I wrote chicken-sdl2 myself, so I didn't learn a ton of new technical things during the jam. For me, the jam was more an opportunity to help other people learn, by making an example game. It was also an opportunity to sharpen my skills, and test out chicken-sdl2.
I did learn one new technical skill, though: how to use the CHICKEN Scheme performance profile to locate performance bottlenecks. This is a skill which I will be happy to use in the future to optimize my libraries and applications.
I also gained more experience with simple physics simulations and collision detection. I was already familiar with the basic concepts, but I did pick up a few tricks, such as having multiple hitboxes or points located on different sides of a player, so that you can more easily determine how to respond to the collision.
I got everything I wanted out of this jam:
- Created a new example game for chicken-sdl2
- Tested chicken-sdl2 in action
- Gained experience writing games in CHICKEN
- Had fun
I'm looking forward to the next one — although if it is another 7 day jam, I will have to limit my participation, to minimize the disruption to my daily life.