Pico 8 does NOT have a lot of space to store your levels. Pico 8 games that seem to have very large levels will generate levels using some sort of algorithm, and this is one of those games. But this one is very simple; I draw pieces of levels, one screen big each, refer to each of those pieces by a number (eg. peice number 1, 2, 3 and so on) and each level is like a "playlist" of peice numbers, eg. 3,5,6,34,4,5,7,3,23, etc.
Below is a screenshot of the map data being used to store one "screen" of level at a time. Each grid square is one screen. One screen is 8x8 tiles big, which is what will fit on a 64x64 pixel sized screen.
So I make the level one "screen" at a time, and think about how those peices can be mixed and matched to make a longer level. By mixing up the order, you can get a convincing amount of variety with the different ways they match up, with only a little bit of noticeable repetition to the player. The parts within each screen may be recognised, but the parts where they join all look different - because the level is made of tiles, there's no "seam" between each piece of level, just different arrangements of tiles as the different peices appear next to each other.
Below I've arranged 3 screens in different ways to demonstrate how new tile arrangements form naturally just by having the peices in different orders. You can't see where one "screen" starts and one ends.
In fact, if you look at the levels from the original Alex Kidd game, you can see some repetition. Sometimes the game designers on the old 8 bit consoles used a similar technique to get more length out of their levels for the storage space on the cart.
So, a more technical explanation of how the screens are put together to make a level.
There is a function called LOAD_LEVEL, which, depending on the parameter, will set a bunch of variables depending on what level is being loaded, including setting the SCREENS array, which is a 1 dimensional array of integers, each number referring to a screen in the map.
Basically I split the Pico8 map up into "screens" that are 8x8 tiles big, and are indexed as if they are a linear array (you know, the thing where you can use a single number to reference a grid, if you know the width of the grid, and you divide by the number of rows to get the Y, and get the remainder to get the X). The table below shows how the map is split into screens and how they are indexed.
The coloured squares represent every screen of level data that appears in the entire game. I colour coded them to help me keep track of which screens belonged to which level, as I had to optimise the space usage, removing some and replacing them with others.
This is how the level is rendered. Every frame, the screen is wiped blank, so you need to re-draw the level, and then the sprites on top. I probably could have got away with just drawing the correct "screen" of level data around you depending on where you are in the level, but I decided to be clever...
This was possibly an unnecessary optimisation. Try not to do these sorts of things till you actually have a performance issue to solve. I replace the first 2 screens of the actual Pico 8 map data with whatever 2 screens are currently around you, and draw that every frame. Once you walk ahead to the right more than one screen's worth of space, a "shuffle" occours, where everything is moved back by one screen, and a new screen of level tiles are placed in the 2nd screen of map data in the Pico 8 map data.
Better to show it in action. Below is a recording of the game window zoomed out to 128x128 which fits the 2 screens (index 0 and 1) on the screen at once, at the top. The lower half of the screen is just whatever map data happens to be there. In fact if you look at the lower half of the screen, you can see it doesn't change, demonstrating that the top 2 screens are being changed.
A more detailed explanation: In rightwards scrolling levels, they move rightwards into screen 1, the camera following them, and when they go beyond screen 1, a "shuffle" happens, where the map data in screen 1 position is "copied and pasted" to screen 0 position, losing screen 0 in the process, and a new screen is "pasted" into screen 1 position. That's what the FUNCTION SHUFFLE_SCREEN does in the code. Also, when this happens, all the objects, and the camera, are moved back one screen's worth - simply subtract 64 pixels from their X axis. Whenever this "shuffle" happens, it happens in the blink of an eye and you don't notice it.
This "shuffling" process is actually used in old 2D games, but at a finer level - single columns of tiles are scrolled onto the screen at a time, rather than a whole screens worth of tiles. This is because there wasn't a lot of video memory to work with, it keeps the cost of the hardware down. Even modern 3D games do this to some extent - yes. Only the open world games though. The thing is, in an open world game, when you move too far away from the world position of 0,0,0, you get "floating point errors" were the vertex positions of the meshes are now too big, and floating point accuracy is lost, and things start wobbling around and cracks form between meshes that should be perfectly lined up. So every so often, the whole world, all the static terrain, all the moving objects, are re-centered around 0,0,0 every so often. There is no sudden jolt when this happens, it's perfectly seamless. Moving meshes is very easy for the computer to do. I wrote a blog article about this very issue in my old blog, with screenshots of the issue.
Nearly every level in Alex Kidd in Miracle World scrolls from left to right, with a few exceptions.
The castle levels do not scroll, and allow you to enter and exit each screen from any of the 4 sides, allowing you to explore the castle at your leisure. This is much simpler; the LOAD_SCREEN is used to just copy the map data from the relevant screen index, into screen index position 0, and the player never leaves this screen - when they exit from one of the sides, the new screen data is pasted in, and the player's position is inverted to be coming in from the opposite side of the screen.
There is only one level that scrolled left in the original game, which is a helicopter level near the end (the really hard one with the stone and swamp tiles and red balls), and there wasn't a need for it to scroll that way, so I just let it scroll rightwards like all the others, to save on Pico 8's code space.
In the final post, I'll show how I overcame the code and music limitations in detail.