in which another game is jammed

All the games I've created previously have used the LÖVE framework, which I heartily recommend and have really enjoyed using. It's extremely flexible but provides just the right level of abstraction to let you do any kind of 2D game. I have even created a text editor in it. But for the 2019 Lisp Game Jam I teamed up again with Emma Bukacek (we first worked together on Goo Runner for the previous jam) and wanted to try something new: TIC-80.

tic-80 screenshot

TIC-80 is what's referred to as a "fantasy console"1; that is, a piece of software which embodies an imaginary computer which never actually existed. Hearkening back to the days of the Commodore 64, it has a 16-color palette, a 64kb limit on the amount of code you can load into it, and 80kb of space for data (sprites, maps, sound, and music). While these limitations may sound severe, the idea is that they can be liberating because there is no pressure to create something polished; the medium demands a rough, raw style.

The really impressive thing about TIC-80 you notice right away is how it makes game development so accessible. It's one file to download (or not even download; it runs perfectly fine in a browser) and you're off to the races; the code editor, sprite editor, mapper, sound editor, and music tracker are all built-in. But the best part is that you can explore other people's games (with the SURF command), and once you've played them, hit ESC to open the editor and see how they did it. You can make changes to the code, sprites, etc and immediately see them reflected. This kind of explore-and-tinker approach encourages you to experiment and see for yourself what happens.

In fact, try it now! Go to This is my Mech and hit ESC, then go down to "close game" and press Z to close it. You're in the console now, so hit ESC again to go to the editor, and press the sprite editor button at the top left. Change some of the character sprites, then hit ESC to go back to the console and type RUN to see what it does! The impact of the accessibility and immediacy of the tool simply can't be overstated; it calls out to be hacked and fiddled and tweaked.

Having decided on the platform, Emma and I threw around a few game ideas but landed on making an adventure/comedy game based on the music video I'll form the Head by MC Frontalot, which is in turn a parody of the 1980s cartoon Voltron, a mecha series about five different pilots who work together to form a giant robot that fights off the monster of the week. Instead of making the game about combat, I wanted a theme of cooperation, which led to a gameplay focused around dialog and conversation.

I'll form the head music video

I focused more on the coding and the art, and Emma did most of the writing and all of the music. One big difference when coding on TIC-80 games vs LÖVE is that you can't pull in any 3rd-party libraries; you have the Lua/Fennel standard library, the TIC-80 API, and whatever you write yourself. In fact, TIC-80's code editor supports only a single file. I'm mostly OK with TIC-80's limitations, but that seemed like a bit much, especially when collaborating, so I split out several different files and edited them in Emacs, using a Makefile to concatenate them together and TIC-80's "watch" functionality to load it in upon changes. In retrospect, while having functionality organized into different files was nice, it wasn't worth the downside of having the line numbers be incorrect, so I wouldn't do that part again.

The file watch feature was pretty convenient, but it's worth noting that the changes were only applied when you started a new game. (Not necessarily restarting the whole TIC-80 program, just the RUN command.) There's no way to load in new code from a file without restarting the game. You can evaluate new code with the EVAL command in the console and then RESUME to see the effect it has on a running game, but that only applies to a single line of code typed into the console, which is pretty limiting compared to LÖVE's full support for hot-loading any module from disk at any time that I wrote about previously. This was the biggest disadvantage of developing in TIC-80 by a significant margin. Luckily our game didn't have much state, so constantly restarting it wasn't a big deal, but for other games it would be.2

Update: I was able to add the above feature with a small amount of code, and it was merged promptly. It will be included in TIC-80 version 0.80.0. You must launch the game with the -code-watch flag and run the RESUME command to activate it. In order to take advantage of this you need to store game state in a global and ensure not to overwrite that global if it already has a value.

Another minor downside of collaborating on a TIC-80 game is that the cartridge is a single binary file. You can set it up so it loads the source from an external file, but the rest of the game (sprites, map, sound, and music) are all stored in one place. If you use git to track it, you will find that one person changing a sprite and another changing a music track will result in a conflict you can't resolve using git. Because of this, we would claim a "cartridge lock" in chat so that only one of us was working on non-code assets at a time, but it would be much nicer if changes to sprites could happen independently of changes to music without conflict.

screenshot of the game

Since the game consisted of mostly dialog, the conversation system was the central place to start. We used coroutines to allow a single conversation to be written in a linear, top-to-bottom way and react to player input but still run without blocking the main event loop. For instance, the function below moves the Adam character, says a line, and then asks the player a question which has two possible responses, and reacts differently depending on which response is chosen. In the second case, it sets convos.Adam so that the next time you talk to that character, a different conversation will begin:

(fn all.Adam2 []
  (move-to :Adam 48 25)
  (say "Hey, sorry about that.")
  (let [answer (ask "What's up?" ["What are you doing?"
                                  "Where's the restroom?"])]
    (if (= answer "Where's the restroom?")
        (say "You can pee in your pilot suit; isn't"
             "technology amazing? Built-in"
             "waste recyclers.")
        (= answer "What are you doing?")
        (do (say "Well... I got a bit flustered and"
                 "forgot my password, and now I'm"
                 "locked out of the system!")
            (set convos.Adam all.Adam25)
            (all.Adam25)))))

There was some syntactic redundancy with the questions which could have been tidied up with a macro. In older versions of Fennel, the macro system is tied to the module system, which is normally fine, but TIC-80's single-file restriction makes it so that style of macros were unavailable. Newer versions of Fennel don't have this restriction, but unfortunately the latest stable version of TIC-80 hasn't been updated yet. Hopefully this lands soon! The new version of Fennel also includes pattern matching, which probably would have made a custom question macro unnecessary.

The vast majority of the code is dialog/conversation code; the rest is for walking around with collision detection, and flying around in the end-game sequence. This is pretty standard animation fare but was a lot of fun to write!

rhinos animation

I mentioned TIC-80's size limit already; with such a dialog-heavy game we did run into that on the last day. We were close enough to the deadline with more we wanted to add that it caused a bit of a panic, but all we had to do was remove a bunch of commented code and we were able to squeeze what we needed in. Next time around I would use single-space indents just to save those few extra bytes.

All in all I think the downsides of TIC-80 were well worth it for a pixel-art style, short game. Being able to publish the game to an HTML file and easily publish it to itch.io (the site hosting the jam) was very convenient. It's especially helpful in a jam situation because you want to make it easy for as many people as possible to play your game so they can rate it; if it's difficult to install a lot of people won't do it. I've never done my own art for a game before, but having all the tools built-in convinced me to give it a try, and it turned out pretty good despite me not having any background in pixel art, or art of any kind.

Anyway, I'd encourage you to give the game a try. The game won first place in the game jam, and you can finish it in around ten minutes in your browser. And if it looks like fun, why not make your own in TIC-80?


[1] The term "fantasy console" was coined by PICO-8, a commercial product with limitations even more severe than TIC-80. I've done a few short demos with PICO-8 but I much prefer TIC-80, not just because it's free software, but because it supports Fennel, has a more comfortable code editor, and has a much more readable font. PICO-8 only supports a fixed-precision decimal fork of Lua. The only two advantages of PICO-8 are the larger community and the ability to set flags on sprites.

[2] I'm considering looking into adding support in TIC-80 for reloading the code without wiping the existing state. The author has been very friendly and receptive to contributions in the past, but this change might be a bit too much for my meager C skills.

« older | 2019-05-07T19:52:41