How to create your first PICO-8 game


PICO-8 is a great tool to use if you’re looking to learn game development. You can create 8-bit retro-style games and distribute them online for anyone to play.

PICO-8 offers its own sprite editor and music composer to create complete standalone games. To familiarize yourself with how PICO-8 works, it’s best to start simple.

Running the PICO-8 Console

PICO-8 is a virtual console and game development tool. You can use it to create and distribute simple retro-style games.

Start by getting the PICO-8 application and running it:

  1. Purchase and download the software from its home page. You can download PICO-8 for Windows, macOS or Linux.
  2. Launch the app and you should see a splash screen like this:

    This is the PICO-8 command line that you can use to load and run cartridges, as well as several other actions, documented on the PICO-8 Wiki.

Basic use of the code editor

You can get a feel for how PICO-8 programming works by using the code editor to create a simple program and then running it.

  1. At the start screen, tap ESC to enter the code editor. You should see a mostly blank screen:
  2. The code editor is basic but easy to use. For now, start with the simplest program:
  3. When you have entered the code above, press ESC to return to the start screen.
  4. You can now type CLASSES to run your program. It should display the text “HELLO, WORLD”.

Write a simple game

Once you’re comfortable writing PICO-8 code and running it, it’s time to try a simple game. Tic-tac-toe is a great choice for a starter game because:

  • It’s turn-based, so you don’t have to worry about timing anything.
  • It involves very little logic, essentially consisting of only two rules.
  • It has very simple graphical requirements.

The full code for this simple game is available in a GitHub repository. You can download this file, save it to your local cartridge folder and open it with PICO-8.

To open your local cartridge folder in your computer’s file browser, type CASE on the PICO-8 command line.

Game Loop Overview

No matter what language you use, game development usually revolves around a “game loop”. It consists of the following process:

  1. Get user input
  2. Update game state
  3. Draw the game

PICO-8 has excellent built-in support for a game loop through its functions _init(), _update()and _to draw(). These functions are special because PICO-8 calls them automatically, if necessary.

You can see how it works by creating the skeleton of a game. This first revision will show a cursor on the screen that moves when you move your mouse.

Start by configuring the required actions in _init(). This function is not strictly part of the game loop since it only runs once, at startup. For a game that requires mouse input, you’ll need to make a special call to enable it:

function _init()
poke(0x5f2d, 1)

The _update The function handles user input and updates to your game state. To support mouse input, this is where to keep track of the pointer position.

function _update()
mousex = stat(32)
mousey = stat(33)

Note that variables in PICO-8 are global by default, so any function in your program will have access to mousex and mouse. PICO-8 includes stat as a built-in function. It gives various details about the current environment, including mouse, keyboard, and date/time.

Finally, set a _to draw function to display a pointer on the screen. For a crosshair pointer, you can draw a vertical line and a horizontal line at the current mouse position:

function _draw()
line(mousex, mousey - 4, mousex, mousey + 4)
line(mousex - 4, mousey, mousex + 4, mousey)

The line() The function is another built into the PICO-8. It takes four arguments to draw a line between two points: the x and y coordinates of point a and the x and y coordinates of point b.

The cls() function clears the screen. Try deleting this line to see what happens when you don’t clear the screen.

Draw the remaining game elements

Tic-tac-toe takes place on a grid of nine squares. You can recreate this using four lines: two horizontal and two vertical. As a PICO-8 screen is 128×128 pixels, each square can be 42 pixels, leaving 1 pixel for the grid lines:

function draw_grid()
line(42, 0, 42, 127)
line(85, 0, 85, 127)

line(0, 42, 127, 42)
line(0, 85, 127, 85)

The next task is to draw a symbol – zero or cross – in a given square. You can use it to display both the current “table” and a “preview” symbol when the user hovers their mouse over an empty box.

Drawing a “zero” is just like calling the PICO-8 circle() function:

function draw_nought(x, y)
circ(x, y, 10)

Drawing a “cross” is a bit more complicated. You will need to draw two diagonal lines that intersect:

function draw_cross(x, y)
line(x - 10, y - 10, x + 10, y + 10)
line(x + 10, y - 10, x - 10, y + 10)

The remaining drawing function is draw_square. It draws the symbol (cross or zero) in a given grid square, if there is one. To complete this feature, however, you will need to access the game state.

Keep track of game status

To play a meaningful game, you will need a way to track which symbol, if any, is in which square. Although PICO-8 supports multi-dimensional arrays, it’s just as easy to use a one-dimensional array for simple use cases like this. You just need to follow nine different locations in the grid:

p = {"", "", "", "", "", "", "", "", ""}

You can access the elements of this array using syntax familiar from other languages. For instance:

p[1] = "0"

if (p[9] == "x") then

PICO-8 arrays start at index 1, not index 0 as is common in many languages.

You can translate from a column and row reference to an index in this table, and vice versa. Using the fact that the screen is 128×128 and the grid is 3×3, you can convert the x and y coordinates to grid references like this:

mouserow=flr((mousey/42.666) % 3)

User input management

You can track mouse button presses in the same way as mouse position. In _updateuse a global variable to track the state and call statistics(34) to check if the left mouse button is pressed:

if (down and stat(34) == 0) then
down = false

if ((not down) and stat(34) == 1) then
down = true

Keep track of down allows you to identify when the button is first clicked, since stat() just tells you if the button is pressed when it runs.

Victory condition detection

A game of tic-tac-toe ends when:

  • Three instances of the same symbol exist in the same row, column, or diagonal.
  • All boxes are full.

The check_for_win The function determines whether the current game state meets either of the conditions. There are several ways to handle this, but for a simple game like tic-tac-toe, a brute force approach is often the easiest:

for a = 1,3 do
if p[a] != "" and p[a] == p[a + 3] and p[a] == p[a + 6] then
finished = true
winner = p[a]

This code snippet demonstrates column checking. It searches for non-empty matching values ​​in the appropriate positions in the main grid array. If it discovers a winning line, this code sets two global variables which then drive the display of the winning message in _display().

Packaging your game

There is no point in creating a game unless others can play it. Fortunately, PICO-8 has you covered. You can create an executable by running the following on the PICO-8 command line:

  1. Type CLASSES to start your program.
  2. While running, press F2 to take a screenshot. PICO-8 will use it as a thumbnail for your game.
  3. Type EXPORT TICTACTOE.HTML. HTML is the fastest format to generate and the most portable.

PICO-8 also supports native binaries for Windows, macOS and Linux. Use EXPORT TICTACTOE.BIN to create them. Whichever format you choose, PICO-8 will export your files to the cartridge folder.

PICO-8 could be just the start

This simple game has a lot of potential for follow-up work. You can make improvements to graphics or add logic to CPU moves. You can also create a two-player version and allow the player to choose their symbol.

Most PICO-8 game developers make their creations available for free. In the world of professional game development, tools like Unreal Engine, GameMaker, and Unity are more common.


About Author

Comments are closed.