Get Home is a retro metroidvania about a fox that must escape the derelict space station it’s stuck on. The game stores the massive map using a unique compression algorithm.
Get Home
The Gameplay
In Get Home, you are a fox that was left behind in the zoo of a now abandoned space station. Just out the cupula, you can see planet Earth: your home. Get there.
As a “metroidvania”, Get Home puts the player in a large, non-linear space station map. While abandoning the space station, the humans had left behind parts of the strange device they were developing there; these eight parts each grant the fox protagonist new powers to help it through the station. However, these humans also left behind many hazards for this fox; even the other animals from the zoo have grown hostile toward anything that moves, even a tiny creature who just wants to get home.
Get Home is made for the PICO-8 virtual console. It is limited to 128×128 pixels, 16 simultaneous colors (limited to a library of 32 colors total), and only 64 KiB of total memory.
The Character
In Get Home, you play as a black and white fox. As you unlock new powers, you gain new tails, each one a different color. Your pelt also changes color when you select an ability.
Rather than using 2D sprites (individual images for the character), the game procedurally draws the fox using a series of circles and rectangles. The fox can move more smoothly this way; for example, it doesn’t need to stop its running animation to start a jumping animation. In this “ball and chain” system, circles are given a target position, but it is constrained to its parent; it must remain within range of a distance and angle from its parent.
Level Compression
One of the most impressive parts of Get Home is the way it stores and loads stage data. The PICO-8 console can only save a measly 12 KiB of combined map and sprite data to use for screens (about a 192 pixel by 128 pixel area), but the entire space station setting of the game needs over 100 times that!
So, of course, the map must be compressed!
First, the map is divided into “screens”: individual areas that the player can explore. This works similar to classic “metroidvania” games; the player reaches the edge, and the screen scrolls to the adjacent screen. Each screen is typically at least 128px wide and tall (the size of the PICO-8 screen) but may be larger or smaller.
But each screen is then stored as a hexadecimal string. The first digit in the string dictates the width of the screen, as 2n; if the first digit is 0x7
, then the screen will be 27 = 128 pixels wide. The game now has a single 128×128 tile it can fill with the next digit. This digit essentially divides the square into four and fills it bitwise, left-to-right then top-to-bottom. For example, if the next digit is 0xE
, which is 0b1110
, then the first, second, and third quarter will be opaque, and the fourth will be transparent.
The magic happens with 0x6
(0b0110
) and 0x9
(0b1001
). Normally, those two numbers would each create a checkered pattern. However, the algorithm interprets those differently. With 0x6
, it still divides the current tile in four, but then moves to the next digit. For example, let’s say again that the first digit is 0x7
, meaning that the first tile is a 128-pixel square split into four 64×64 tiles. If the next digit is 0x6
, then that first 64×64 tile is further split into four 32×32 tiles! The next digit can then fill a 32-pixel square instead of a 64-pixel square. And another 0x6
would further divide that into 16×16 tiles. But that’s what 0x9
is for: it simply does the same operation as 0x6
, but twice.
With this system, the map can have complexity down to the pixel without needing to store too much repetitive data for more open areas.
The algorithm continues down the string until it reaches the end of the string, filling the next tile. Whenever a tile subdivides, the algorithm fills the four new tiles (in the same left-to-right order) before continuing past that tile. If it fills all tiles but still has more digits, it creates another initial tile (i.e. a 128×128 pixel tile) below the rest, building downward. This allows for rectangular screens. Conversely, if the string ends but the screen still has remaining tiles, those tiles are simply left empty, or discarded if the tiles would add height to the screen.
As mentioned, PICO-8 is a 16-color system. Indeed, each color is represented by a hexadecimal value. So, this hexadecimal screen can be perfectly stored in the map and sprite data. Suddenly, the 12 KiB of map ROM feels like a lot! That’s 24,576 hexadecimal values. The most complex screens may have upwards to 50 hexadecimal values, so even if every single screen were that complex, this would be enough memory to store over 500 screens! The game itself has shy of 200 screens, so this is more enough.
The generated black-and-white image is used as a collision mask; it tells the game what the player can walk on, for example. During the next stage of this process, the game then decorates the screen based on this mask. It may add edges, tiles, signs of wear, vines, and so on, depending on what area of the space station this screen is in. And then, finally, the game transitions from the old screen to this one.
The algorithm assembles this new screen in PICO-8’s memory when the player starts transitioning into the new screen. Though the code takes less than a second to assemble this stage, the transition lasts a few seconds for effect, panning from one region to another. After all, if the screen just cut to the next, and an enemy or hazard is waiting at the other side, the player would have no time to react!
As an aside: further level data (elsewhere in the code) stores dynamic objects such as water, destroyable debris, movable boxes, and enemies. The states of these are saved in memory.
Current State
As of posting, the design and function of this game are in place. The algorithm works flawlessly; I was actually surprised that I did not have to fix a single bug with it. Last I worked on was screen decoration and physics (i.e. making sure jumping and moving work well). I have yet to fully implement the character rig.
Unfortunately, my attention was pulled from this project toward work as well as other personal endeavors. Still, I have no intention of dropping this. I absolutely enjoy the work I have done for it so far, and I would love to see it through. Just the idea of making something so large despite the PICO-8’s 64 KiB of total memory…it’s exciting! PICO-8’s limitations were what attracted me to the platform in the first place; I want to see just how far I can push them.