itch.io is community of indie game creators and players

Devlogs

Picocraft: Pico8 Sprite and maps Tricks

PicoCraft
A browser game made in HTML5

Devlog: Sprite Management, Map Depth, and Performance in a PICO-8 Strategy Game

In this post, I share how I approached graphics, memory handling, and performance optimization for my PICO-8 game—from sprite compression and map depth simulation to fog of war and the minimap. I hope you like it !

Starting with sprites

At the beginning of the project (around 2022), I built a prototype with a scrollable map and an empty UI. As soon as I started adding unit animations, buildings, and interface elements, I quickly realized that a single 128×128 spritesheet wouldn’t be sufficient.

Through the community, I discovered PX9 compression, an algorithm that works well with repetitive pixel data. With it, I managed to store up to four spritesheets in the space of one. By experimenting, I was able to compress graphics for the map, human units, and buildings into just two sheets, without much additional optimization.

Graphics Workflow with PX9

My initial goal was to fit everything into a single cartridge. So, I included both the PX9 decompression code and the compressed data directly in the main game cart. Later to free some Pico8 tokens, I switched to a multi-cartridge setup, moving the decompression code and data to a dedicated data cartridge.

Sprite Compression Workflow

  1. Edit one of the four GIMP files (pack1.xcf, ..., pack4.xcf)

  2. Export the file to PNG

  3. Import the PNG into the corresponding .p8 cartridge

  4. Use a compress_pack.p8 utility to compress and save the data inside a PICO-8 data cartridge

Note: GIMP is optional—you can use any image editor, including Pico8. 

Decompression and High Memory Usage

PICO-8 cartridges have 32KB of memory. Since version 0.2.4, an additional 32KB of high memory was introduced, which is the exact size of four spritesheets.

The idea is to decompress the packed sprites once into high memory, then draw from that memory during gameplay. My first implementation copied sprites from high memory into the main spritesheet, and then used spr() or sspr() to draw them.

This worked, but the memory copy added overhead.

Memory Remapping in Version 0.2.6

With version 0.2.6, PICO-8 introduced video memory remapping using poke(0x5f54, ...). This allowed me to switch the active spritesheet to a high memory bank, eliminating the need for copying.

And that's it ! To simplify usage, I created two wrapper functions:

-- spr() wrapper to use sprites from high memory
function vspr(nspr,x,y,flipx)
 --map spritesheet to highmem
 poke(0x5f54,nspr\256*32+128)
 --draw sprite
 spr(nspr%256,x,y,1,1,flipx)
 --restore scr spr mapping
 poke(0x5f54,0)
end
-- sspr() wrapper with memory remapping
function vsspr(bank,sx,sy,w,h,dx,dy,dw,dh,flipx)
  --map spritesheet to highmem
  poke(0x5f54,b*32+128)
  --draw sprite
  sspr(sx,sy,w,h,dx,dy,dw or w,dh or h,flipx or false)
  --restore scr spr mapping
  poke(0x5f54,0)
end

With these two functions, I could directly access and draw decompressed sprites from high memory—saving both CPU and tokens.

Managing Map Levels

My map system consists of:

  • A tilemap background

  • A set of horizontal banks for units and objects, one for each row of tiles (to simulate depth)

The background includes 27 tile types (grass, dirt, water) and is always stored in the first spritesheet, where the first three lines are reserved for common tiles and cursors.

To make editing easier, I created a custom map editor, as the built-in one lacked features like object placement and auto-tiling. This tool also compresses and stores map data into the launcher cartridge.

Memory Between Cartridges

PICO-8 allows certain memory areas to persist between cartridges. I use 0x4300 as temporary storage when switching from the launcher to the game.

  1. After choosing a level, the launcher decompresses the background map into 0x4300

  2. The game cart copies this data into the standard map memory at 0x2000

  3. Using poke(0x5f57, map_width), I define the map width for rendering

Since 0x4300holds 4864 bytes and a 64×64 tilemap only uses 4096 bytes, this works perfectly.

Simulating Map Depth Without Sorting

To simulate depth (units appearing in front of or behind buildings), I organize objects into banks based on their Y coordinate. Each tile row corresponds to a bank.

When a unit moves to a new row, it's removed from its old bank and inserted into the new one.

During rendering, I simply loop through the banks from top to bottom and draw objects—this automatically handles depth without needing a sorting algorithm.


Fog of War Optimization

Fog of war is one of the most CPU-intensive features. Fortunately, the playable area is smaller than the full screen due to the UI (about 106 pixels tall), so I can post-process it efficiently using sprite memory.

Fog of War Rendering Steps

  1. Draw tilemap, buildings, and units

  2. Copy the visible screen to the spritesheet (starting at line 24)

  3. Draw black circles over areas within line-of-sight

  4. Use sspr() to apply darkening and transparency

  5. Repeat the process for already visited areas

This system uses memory remapping and offscreen rendering to maintain performance.

Under the hood, here is how it looks:

Rendering the Minimap

This was the most complex feature to optimize !

Initial Approach

I first tried drawing each tile and unit pixel-by-pixel based on map data, with fog of war. This method consumed up to 20% of the CPU—far too much !

Final Solution

I reserved the last upper memory page (128×128 pixels) and split it into four regions:

  • Top-left: Unit/building markers + workspace (32×32 pixels)

  • Bottom-right: Pre-rendered full map background

  • Bottom-left: Visibility mask

  • Top-right: Visited areas

These layers are merged just like the fog of war system. Drawing uses vsspr() and circfill() instead of per-pixel logic.

This redesign reduced CPU usage by 14%, and the performance is now much more stable. Wouhou !!

Thoughts

Working within PICO-8’s constraints requires creative solutions. By using compression, memory remapping, and smart rendering pipelines, I was able to build a visually rich strategy game within tight limitations.

This article focused on the technical systems behind sprite storage, map depth, fog of war, and the minimap. If you're working on a similar project or curious about the tools I used, feel free to reach out or comment.

Thanks for reading!

Download PicoCraft
Leave a comment