recurse center, pt. 1: game programming for PICO-8
- i made that ☝️! (you can play it online here)
- making up patterns as you go is actually a pretty decent way to learn a new paradigm, and can be super fun
i recently started a three-month batch at the recurse center, and one of my goals for my time at RC is to dive headfirst into software paradigms with which i’m not familiar. in our first few days of RC, a bunch of people were talking about making lo-fi games for the PICO-8 fantasy console, and as a complete novice to game programming, it seemed like as good a place as any to start.
a fantasy console is just a virtual platform, as opposed to a console that has real physical hardware, like the game boy color. users download the PICO-8 software and play cartridges saved as .png
images or .p8
files; programs are written in lua (a language i hadn’t used before, but which wasn’t too bad to figure out given some knowledge of other C-like languages). the weirdest thing about lua is that “tables are the only ‘container’ type” (and when they are used like arrays, they are 1-indexed).
for a beginner to game design like me, it helped a lot that PICO-8 platform has some pretty rigorous constraints: you only get 16 colors, a 128x128 pixel screen, four audio channels, and 32k per cartridge.
so, after downloading the software, how do you get started? fortunately, at RC, someone extremely experienced is always close at hand and willing to help, and in this case i got a great primer from fellow RCer Ayla Myers, who’s developed a ton of really awesome games. there are three main functions in any PICO-8 program:
_init
: initializes any state_update
: updates state; called at 30 frames per second_draw
: draws to the screen; also (roughly) 30fps
here’s a very minimal program which just moves a white circle across the middle of a black screen:
local x
function _init()
x = 0
end
function _update()
x += 1
end
function _draw()
cls(0) -- black background
circfill(x, 64, 3, 7) -- white circle, radius 3, at (x, 64)
end
which looks like this (the looping comes from the GIF; our code doesn’t do that yet):
even this very simple program is a significant departure from functional(ish) programming on the web! two immediate differences i noticed were:
- time plays a much more central role in the program. rather than initialize and wait for user input, like most web applications do, all state updates in a PICO-8 game originate in the
_update
function, which is fired off once per unit time (usually 30 times per second, though PICO-8 also has a 60fps mode, for which you’d want to use_update60
). i’ll get to user input in a bit, but–spoiler alert!–it will also need to be handled in the_update
loop. -
data flow is very different, and forget immutability. as opposed to the “unidirectional data flow” typical of a React app, we continuously mutate some sort of state that is accessible by
_draw
(in this case, incrementing the x coordinate of the circle that gets drawn once per frame). importantly, there’s no explicit relation between_update
and_draw
. from the manual:_draw() is normally called at 30fps, but if it can not complete in time, PICO-8 will attempt to run at 15ps and call _update() twice per visible frame to compensate.
this actually leads to a pretty nice separation of concerns between game state and how it is represented on the screen, albeit in a very different way than i’m used to!
adding interactivity
alright, so now we can see something change on the screen. how about making it respond to user input? again, PICO-8’s constraints help us out here: we get just the four directional buttons, plus two more. they’re indexed 0
through 5
and are accessed with the btn
function, which returns a boolean indicating whether or not that button is currently held down. (there’s also btnp
, for “button pressed”, which will essentially throttle the input a little). this cheat sheet, courtesy of Ayla, is super helpful if you can’t remember which index corresponds to which button–plus it also has a ton of other great info about the PICO-8 API.
let’s modify our previous _update
function to allow the user to move the circle back and forth on the screen (_draw
and _init
remain the same):
function _update()
if btn(1) then
x += 1 -- right arrow, move right
elseif btn(0) then
x -= 1 -- left arrow, move left
end
end
no callbacks here! each _update
tick will either kick off some action or not, based on boolean values returned by btn
. this was probably the biggest hurdle to my understanding of how to add features to games; i eventually came to realize that all additions are going to need to originate in the _update
loop. adding a btn
call in _update
could be thought of as analogous to “registering a callback”; one could theoretically even set up an event registry that _update
calls out to, but i’ll leave that as an exercise for the reader 🤔
graphics and sound
drawing graphics with the builtin draw functions alone (just circles, rectangles and lines) could get tedious very quickly; fortunately, PICO-8 comes equipped with a sprite editor and the spr
function, which draws your little pixellated masterpieces to the screen. here’s how i drew the main sprite for the character in my game (you can also see its other states in the larger spritesheet underneath it, as well as the mountains for the background):
then, to animate it, i basically just toggle back and forth between sprites in the _draw
function based on some piece of state (which can just be a counter, or whether or not the player is in the air, etc.). here’s a simplified example (assume our two sprites are located at indices 0 and 1):
local counter
function _init()
counter = 0
end
function _update()
counter += 1 -- increment counter every tick
end
function _draw()
local sprite = 0
-- every 25 steps, toggle the sprite
if flr(counter / 25) % 2 == 0 then
sprite = 1
end
spr(sprite, x, y) -- assume x and y are defined somewhere
end
after i wrote some animations this way, Nicole Leffel, who’s also in my batch at RC, posted about the making of her first PICO-8 game, which contains a much more legible technique for two-step animations, as well as a bunch of other good advice. go read it (and play her game, “sonar”)!
PICO-8 also comes with a built-in 8-bit sequencer, which i was particularly excited about (i spent a lot of time as a teenager making chiptune music, all of which is way too embarrassing to post here). in addition to adding them to your game, you can export your tunes as a .wav
file–here’s one i made:
(recognize it? 👈 but don’t listen if you don’t want it stuck in your head for the rest of your life…)
learnings, reflections, etc.
it was fun to see how you can start to invent patterns and best practices, even when you have no idea what you’re doing. for instance, when i first added collision detection, the player’s Y-coordinate would sometimes dip a tiny amount under the Y-coordinate of the ground, which would appear to the user as a totally random crash. to account for it, i added a little bit of tolerance on all of the collision logic, which really helped the game feel more fluid and more playable.
PICO-8 also doesn’t have any built-in physics engine, so you get to implement it from scratch if your game requires it. here’s a good reddit post i only found after i spent a few hours figuring out how to implement gravity and grounding, but on second thought, maybe try to implement it first on your own, because it feels pretty great to finally get it working after hours of stuff like this:
going forward, i’d love to improve the game’s terrain generation, and make it get progressively harder over time (right now it’s about as hard as it’s going to get from the get-go, and sometimes spits out some pretty gnarly mountain to ski over).
i also think the code could be cleaned up a bit now that i’m a little more comfortable with lua and PICO-8, so i’ll probably seek out a code review from some more experienced game developers around RC.
you can find the code for this game on my github, and play the web-exported version of the game here (warning: the sound in the web version can be a little laggy). if you have PICO-8 installed, you can grab the cart here.
hit me up on twitter @jared_mcdonald if you want to chat about PICO-8 or 8-bit music (or anything, really); otherwise, happy game developing!