PICO-8 Devlog 2: Levels
It was always possible that my first devlog for my latest PICO-8 game attempt would be the last, but here I am with log #2 having reached my next milestone! 🎉
Objective
I'd referred to my core concept to identify the next most important thing to focus on, and concluded it was "an exit block that allows a level to be beaten". I need the central game mechanics to be functional before getting into aesthetics.
So I want a tile that represents the exit, and when the player's sprite lands there the next level is loaded and the game proceeds.
Process
I've never got far enough with a PICO-8 game to have to deal with updating the map, so my knowledge of the technical requirements was vague at best. I quickly realised that in order to load the next level, you have to have the next level … so what does that mean?!
I thought maybe I could just draw a new level as another literal screen in the next 16 by 16 tile space:
I knew this wasn't a viable approach in the long run because it would limit me to a handful of levels; my instinct was to use it as a scaffold to establish the bones of the thing. But it wouldn't work, because my whole movement system is based on x/y tile coordinates; I'd need to adjust the x/y calculations for each new section of the map area. Fuck that.
Levels as data
So I had this technical hump to overcome: how to take an existing map screen that's been designed in the editor, compress it, store it, and retrieve it in sequence. Oof. I won't lie, I was intimidated. Reading the lexaloffle forums I figured out that I probably wanted to represent my levels as strings that I could read out to map memory via processes called "serialization" and "deserialization" respectively.
I wasn't able to find any tutorials on this particular subject, and although folk in forums have links to their own solutions I struggled to parse out what was happening in the abstract. So I went at it myself from first principles, hoping to get a better understanding of the problem, if nothing else.
Serialization
Assuming I have a string of numbers (lev) referencing sprites:
function build_level(lev)
local start_i=1 -- substring start index (1-based)
local end_i=start_i+15 -- always 16 map tiles wide
-- outer loop gets 16 digit chunks from level data
local row_y=0 -- track which row is being returned
for c=1,16 do -- ...in the outer loop:
local row_data=sub(lev,start_i,end_i) --chunk
for c=0,15 do -- take each 16-character chunk
local i=c+1 --increment the index - lua 🙄
local sp=sub(row_data,i,i) -- sprite tile
mset(c,row_y,sp) -- insert tile at coords
end -- until the 16th (last) character of row
start_i+=16 -- start index for the next chunk
end_i+=16 -- the end index for the next chunk
row_y=c -- row done! increment the row number
end
end
At the end of the update_p_turn() function that animates the player sprite to the next tile, we check, and if the player's current map tile has the "exit" flag, increment lev counter and call build_level(lev) to get the next level data list by index.
And it works! Now I can have a list of levels stored as strings and when the player reaches the exit the next level instantly appears.
It looks like an Atari 2600 game! (.gif)

Shaping the tutorial levels after their numbers seemed like a cute idea, but it starts to fall apart after 3, I think!
The important thing is that when the player reaches the "exit" the next level loads.
Deserialization
I'll include this for the sake of completeness, but reading the map into a string is kind of just the same thing in reverse, and it's simpler because we're just pushing data into the string rather than extracting sections:
function stringify_row(y)
local row_data=""
for c=0,15 do
local sp=mget(c,y)
row_data..=sp
end
return row_data
end
function store_level()
local level_data=""
for c=0,15 do
level_data..=stringify_row(c)
end
return level_data
end
Problems
I don't even want to call these problems, really — they were just parts of the work. Points of friction, perhaps. But let's not quibble.
PICO-8 / lua quirks
The map origin is zero-index while maps are one-indexed and it took me a while to spot that gaffe, but the biggest issue I had was trying to use multi-line strings that I didn't realise counted whitespace. It would be nice if the levels were human-readable in the code, but not necessary when they're serialized from the map editor.
Workflow for map editing
I had to fiddle around creating a mode variable that lets levels increment if it's game and lets the current map be played when editing. Writing the map out to an external terminal in a CTRL+C friendly format was also a bit of a faff, but I got there.
I've realised that this approach only works if I limit myself to sprites 1-9, though, as it doesn't account for two-digit numbers! I'll deal with that in due course.
Crashes
I was running into a problem where sometimes the game would just hang instead of starting, but I wasn't getting any error messages, and I couldn't see any likely source in the code. I finally figured out that the function that counts empty spaces was unbounded, so unless the player location is bounded by blocking tiles it'll just count forever. I've added a maximum move space of 16 as a temporary fix.
Next steps
Staying focused on mechanics last time was sensible, and rationally I ought to do the same again:
- fix sprite serialization
- level transition updates
- create more level maps
- introduce threat tiles.
I may well work on some of those. But I'm also conscious that this is a creative exercise, not a job, and I need to strike a balance between functionality and fun, or there's a risk I'll just lose interest in the project.
Since I made a major breakthrough with the implementation last time, I'm giving myself permission to explore the presentational side of things next. These are not even "non-functional requirements" because the function of the game is to be engaging.
I'd like to look at
- the overall graphical style
- color schemes
- particle effects
- animation enhancements
END