jared mcdonald

recurse center, pt. 1: game programming for PICO-8

nov 22, 2017

pico-8 game


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:

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):

circle moving across screen

even this very simple program is a significant departure from functional(ish) programming on the web! two immediate differences i noticed were:

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):

pico-8 sprite editor

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:

blarrrgh

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!