In this tutorial we will be turning our simple one-screen space shooter into a scrolling shoot-’em-up! You’ll learn how to use PICO‑8’s handy map editor to quickly and easily draw out levels, and how to use the sprite editor to create terrain tiles. We’ll talk about using sprite flags to distinguish between background and foreground and how to spawn enemies. Speaking of which, we’ll also talk about level design basics and introduce a new turret enemy type to add extra spice and challenge to your game. There’s lots to get through, so let’s get started!
You'll need
Raspberry Pi
Keyboard and mouse
To have completed the earlier parts of this retro game design tutorial
Download the code for this tutorial here
A blank canvas
Much like every other aspect of game development, PICO-8 has a quick and easy solution for designing levels. Switch to the map editor by selecting it from the editor menu at the top right. At first glance, it looks a lot like the sprite editor, with the same sprite sheets and drawing tools at the bottom of the screen. The difference is that, instead of plotting coloured pixels, the map editor paints with our finished sprites. Try this out by selecting a sprite and drawing on the canvas above.
Chunks of dirt
We can’t build a level out of enemy and player sprites – that would be sheer insanity! PICO-8’s map editor is grid based, so we’ll need to create some new terrain sprites that we put together as tiles. Figure 1 shows a 3×3 square of sprites that can be tiled easily, with a couple of variations along the side. Switch to the sprite editor and create something similar. We’ve chosen suitably weird-looking purple asteroids for our terrain, and we’ve also created simple background sprites out of a chequer-board ‘dither’ pattern that we can use to imply depth.
Tiles for miles
Now we have our raw level-making material, let’s start working with it. Switch back to the map editor. You can zoom the canvas with the mouse wheel and pan with the pan tool. Hit the SPACE bar to view gridlines, and you’ll see that your canvas is 128×64 tiles, with grid reference (0, 0) being the top-left tile. As PICO-8’s screen resolution is 128×128 pixels, and each tile is 8×8 pixels, a single screen in PICO-8 is 16×16 tiles. Use your terrain sprites to draw some asteroids in the top-left 16×16 tiles of the canvas.
Mapping it all out
Let’s see what this looks like in game. First of all, comment out the enemy wave code, so that we can explore our level without being rudely interrupted by space blobs. You can use --[[..]] for block comments. Next, add map(0,0,0,0,128,64) to _draw() just after where we draw the background stars. This function tells PICO-8 to draw a 128×64 block of tiles starting from tile reference (0,0) on the map to coordinates (0,0) on the screen. Run your game and you should now see your asteroids. Great work, but it’s all rather static – let’s get this level scrolling!
Look into the camera
To turn our game into a scrolling shoot-’em-up, we will need to use a scrolling camera. Declare new variables camx,camy = 0,0 in _init() for the camera’s coordinates. Next, add camx+=1 to the start of _draw(), followed by camera(camx,camy) which sets the top left of PICO-8’s built-in camera to these coordinates. We’ve modified our player, laser, and draw background, score, and game-over message code to be locked to the new camera coordinates. As the map is only eight screens long, we’ve also written a cheeky bit of code to move the camera and player to the next row on the map when it reaches the end.
A red flag
Modifying the code is mainly a matter of changing boundaries to be set to camx and camy instead of arbitrary values; we’ll also add player.x+=1 to _update() so that the player scrolls with the camera. See the code listing for more details. You’ll have probably noticed that we can fly straight through the terrain unimpeded, so let’s add terrain collision detection. We’ll use sprite flags to do this. Set the sprite flags (those radial buttons above the sprite sheet tabs in the sprite editor) of each of your terrain tiles so that flag 0 is on. It should light up red.
Deep impacts
Sprite flags are a way of ‘marking’ sprites. In this case, we will treat any sprite with flag 0 as solid terrain that our player can crash into. To actually detect the collision, we’ll create a new function player_terrain_collision() which will check four points of a square around the player’s coordinates, retrieve whatever sprite is there, and return true if that sprite has flag 0 activated. Then we’ll add few lines in our update loop that’ll call that new function and kill the player if it returns true. We nearly have everything in place!
Enemy placement
Next, we want to slightly modify our enemy code so that instead of spawning in endless waves, we can place them in our level and they will attack when they appear on camera. See the code listing for the changes. To place enemies in the level, we will use one of our existing enemy sprites in the map editor. Then we will add a few lines to _init() that will check every map tile for enemy sprites and spawn enemies when it finds them – simple! Now that we can place terrain and enemies, we can begin the level design proper.
Flow state
Level design is as much an art as it is a science. For every rule of good level design, there are a hundred examples to prove it wrong. That being said, for your first few levels there are certainly some guiding principles you can follow. It’s a good idea to start simple and gradually increase the challenge as your players become better at the game. This is to keep players in a satisfying state of ‘flow’ where a level is not too easy as to be boring, or too hard to be frustrating.
Difficulty curve
In our space shooter, difficulty is determined by the number and location of enemies and the placement of terrain. Modifying these factors allows us to control the challenge and ideally create a smooth ‘difficulty curve’. In our level, enemies are introduced singularly at first, then in increasing numbers. Terrain is then introduced, then enemies and terrain, and lastly challenging combinations of both. You can see how new elements are introduced one at a time and in situations that allow the player to learn their behaviour before the difficulty is increased.
Reinforcements
Variety is the spice of life and although our green blobs from space have a certain appeal, it is the introduction of new elements, or new combinations, that keeps a level entertaining. That’s why we’ve created a new enemy type, the turret. You can see the code, but essentially it is a malignant mutant that fires a mucus projectile at the player every few seconds. How delightful! This gives us more possibilities for interesting combinations with the other elements in our game; for example, turrets in an asteroid field or amongst waves of enemies.
A happy ever after?
So, your player has defeated every wave of enemy, dodged every asteroid, and made it to the end of your level. What now? Well, the polite thing to do would be to reward them in some way, or give them one final gigantic boss battle. Either way, we will need a congratulations message to tell the player that they are the saviours of mankind. As a final touch, we’ve added a message that will show when the player makes it all the way to the end. Well done space fighters, the galactic federation thanks you!
Top tip
Sprite flags
Sprite flags are extraordinarily useful for lots of things, such as distinguishing between drawing layers or marking objects that collide.
About the writer
Dan Lambton-Howard is an independent game designer based in Newcastle upon Tyne, where he is lucky enough to make games for his PhD.
For earlier parts of this retro game design tutorial click here.
Code for this tutorial can be downloaded here.