PICO-8 GameDev #2: Stars & Space [Tutorial]

pico-8

#1

“Space. The Final Frontier…” :vulcan_salute: :face_with_raised_eyebrow:

Welcome to the 2nd Part of my PICO-8 GameDev tutorial series.
(If you missed the first part, Getting Started, you can find that here…)

In this part - to coincide with the 1st GameShell Game Jam that’s taking part, we are going to be creating a Scrolling Starfield!

2-stars-finished

Scrolling star fields have been used in games (particularly space shoot-em-up’s) for many years. The parallax effect of the stars moving at different speeds (and colours) can be very effective in giving the player a sense of movement through space.
It’s also very easy to do - as I’ll we’ll see now…

Where do I start?

Continuing on from the my last tutorial - you should now be familiar with the basics of opening PICO-8 and getting some code to run.

If you just ran PICO-8 from scratch and you see a menu screen, instead of the flashing cursor - it means you are in SPLORE mode, which is PICO-8’s cart menu system for browsing games. To exit, either press ESC on the keyboard (if you have one connected) or MENU on GameShell then “EXIT TO CONSOLE” to get to the console

If you already had PICO-8 open: we want to start off with a clean cart - so let’s reboot it to wipe everything (don’t forget to SAVE your work!) by pressing ESC to get to the console, then calling the REBOOT command.

From the console, simply press ESC to switch to the Code editor.

PICO-8_4

Foundation code

We need the create the three main functions as before - but this time, we’ll use a special variant of the Update function, called _UPDATE60() to enable 60fps for smoother gameplay.

So let’s write out (or copy+paste) our foundation code:

-- PICO-8 GAMEDEV #2
-- SPACE...

FUNCTION _INIT()

END

FUNCTION _UPDATE60()

END

FUNCTION _DRAW()

END

Creating stars

In order to create some stars, we need somewhere to store them… lots of them!
The good news is that PICO-8 offers a table data type (as does Lua) for such things.

Tables are similar in concept to container types in other programming languages, where they allow you to store many values (things), each with a given key.
If a key is not specified, then the table is treated as a sequence, and simply auto-increments an assigned key, starting at 1 (as PICO-8 uses 1-based indexing).

Let’s start creating some variables we’ll need within the _INIT() function:

-- INIT VARIABLES
STARS={}
STAR_COLS={1,2,5,6,7,12}
WARP_FACTOR=3

TIP: The first line is simply a comment to help document the code - good practice!

The next line initialises the STARS variable as an empty table {}
We’re also creating another table variable, STAR_COLS, to store the colours of our stars (wouldn’t be as effective if they’re all the same!).

PICO-8 has 16 colours available - from 0 (black) to 15 (light pink-ish). I’ve picked a few colours that work well as stars, but you could use any colours you like!

colors
(Handy PICO-8 color chart reference, created by Neko250)

Finally, a WARP_FACTOR variable we’ll use to control how fast everything moves.

Now we need to fill our table up with stars… lots of stars. So to do that, an efficient way is to create a “loop”, to add as many stars to our table as we want. The FOR loop is ideal for this situation, because it allows you to specify how many times to loop a section of code.

But we also want to have many levels of depth to our stars - so to do this we’ll create have two FOR loops, to create a bunch of stars for each depth level.

Let’s add the following to our _INIT() function:

-- CREATE STARFIELD
FOR I=1,#STAR_COLS DO
 FOR J=1,10 DO
  LOCAL S={
   X=RND(128),
   Y=RND(128),
   Z=I,
   C=STAR_COLS[I]
  }
  ADD(STARS,S)
 END 
END

So, first we have a FOR loop, which loops from 1 to #STAR_COLS (this just means, the length of the STAR_COLS table - of which we have 6 colours), and it stores the number in a variable called I - this will be useful later.

Then within that, we have another FOR loop, which loops from 1 to 10 (a good number of stars, per depth level). Then inside that, we are creating a new table, setting some mapped values and assigning it all to a local variable S. PICO-8 doesn’t have the an “object” type, but instead we can just use tables to represent objects, by using mapped values.

The LOCAL keyword denotes scope. It means that, unlike our STARS variable - which has global scope (accessible anywhere in the program) - the S variable is only accessible locally, in this case, from within this FOR loop.

TIP: Controlling your scope for variables is good practice, not only for performance, but also to allow you to safely reuse variable names without any unwanted side-effects.

We’re also specifying four named values, for each new “star” table:

  • X and Y - each set to random values from 0 to 128, our star positions.
    (Note: RND() returns fractional numbers, not rounded to nearest whole number).
  • Z - set to I, the current field “depth”.
  • C - set to the colour value at index position I, the current “depth”

Finally, we’re using the ADD() function to insert the value S (which is a table), into our STARS table.

Drawing stars

“That’s all well and good, but I can’t SEE anything!”

Good point! We need to actually do something with all those stars we’re creating - let’s draw them. Type in the following code within the _DRAW() function:

CLS()
-- DRAW STARS
FOR S IN ALL(STARS) DO
PSET(S.X,S.Y,S.C)
END

As you will probably notice from our previous tutorial, the first line simply clears the screen. If we didn’t do this, it’s look pretty trippy!

We also have another FOR loop, but this one uses ALL() to loop through all the values in the STARS table - automatically placing each star in the S variable. Inside the loop, we have a single command, PSET(). This command lets us draw a single pixel to the screen at a certain position (X,Y) and colour. We access each of the star values using the dot (.) notation.

Now when you run the cart (CTRL+R / +R), you should see something cool…

Static%20stars

Moving stars

“The stars don’t move - OMG, I’ve broken it!”

Calm down, you’ve done nothing wrong - we’ve yet to write the code to MOVE the stars, just to draw them to the screen. Type in the following code within the _UPDATE60() function:

-- MOVE STARS
FOR S IN ALL(STARS) DO
 -- MOVE STAR, BASED ON Z-ORDER DEPTH
 S.Y+=S.Z*WARP_FACTOR/10
 -- WRAP STAR AROUND THE SCREEN
 IF S.Y>128 THEN
  S.Y=0
  S.X=RND(128)
 END
END

So firstly we’re doing another FOR… ALL loop, to loop through all the stars. Then, for every star (S), we’re doing couple of things.

The first part may look a bit scary (and is probably not my finest code), but it’s not - we’re simply moving each star by a proportionate amount, depending on how far away it is (depth). Let’s break it down:

S.Y+=S.Z*WARP_FACTOR/10

This line is saying, take the current star’s Y position (S.Y) and then we use the += shorthand to simple ADD to it the following… the star’s Z position, multiplied by our current “speed”, then divide it by 10 (otherwise it moves WAY too fast!)

Finally, we check each stars position against the bottom-edge of the screen (128 pixels) and if it is over this: we move it back to the top (0 pos) and re-randomise the star’s X position.

We never actually create any new stars, we simply keep wrapping all the stars round to the top again - like a conveyor belt - in a new position, to create the illusion of movement.

Putting it all together

If everything has gone to plan - you should be able to run (CTRL+R / +R) and see some a rather cool scrolling starfield! :sunglasses:

2-stars-finished

Failing that, see the full listing below… (in case something got missed/entered incorrectly)

-- PICO-8 GAMEDEV #2
-- SPACE...

FUNCTION _INIT()
 -- INIT VARIABLES
 STARS={}
 STAR_COLS={1,2,5,6,7,12}
 WARP_FACTOR=3

 -- CREATE STARFIELD
 FOR I=1,#STAR_COLS DO
  FOR J=1,10 DO
   LOCAL S={
    X=RND(128),
    Y=RND(128),
    Z=I,
    C=STAR_COLS[I]
   }
   ADD(STARS,S)
  END 
 END
END

FUNCTION _UPDATE60()
 -- MOVE STARS
 FOR S IN ALL(STARS) DO
  -- MOVE STAR, BASED ON Z-ORDER DEPTH
  S.Y+=S.Z*WARP_FACTOR/10
  -- WRAP STAR AROUND THE SCREEN
  IF S.Y>128 THEN
   S.Y=0
   S.X=RND(128)
  END
 END
END

FUNCTION _DRAW()
 CLS()
 -- DRAW STARS
 FOR S IN ALL(STARS) DO
  PSET(S.X,S.Y,S.C)
 END
END

Save Your Work!

Don’t forget to save your work!

You can do this by:

  1. Pressing ESC, to stop your program and exit back to the Console.
  2. Then using the SAVE() command, followed by the filename.
    For example:

SAVE STARFIELD

You can also download the finished cart, by saving this PICO-8 cart .png and loading it:

p8

Summary

This tutorial showed you how to create a scrolling starfield in PICO-8.

In this lesson, you:

  1. Learned how to create & use TABLES to store multiple values (inc. other tables!)
  2. Learned how to use FOR loops
  3. Learned how to draw pixels to the screen

Further Exercises

If you want experiment more with PICO-8, here’s some extra things you try:

  1. Try increasing/decreasing the value of the WARP_FACTOR variable
  2. Try commenting out the line “CLS()” for a trippy effect!
  3. Try changing and adding colours to STAR_COLS, to see more depth and colours!

In the next part of this series, we’ll actually start using SPRITES, and get them controlled by the player! :rocket: :video_game:

I hope you enjoyed this tutorial, and if you did - please let me know. :smile:
For more information about me & the games I’ve made, please visit my website or Twitter.


#2

:clap: Congratulations on the second tutorial! I will try it later. Keep up the great work! :+1:


#3

Hey Paul! I’m really glad that you’re making this tutorial series of PICO-8 game development. I’m a great fan of your work. Thank you.


#4

This is awesome! Especially for someone who is fairly new to programming. I cant wait to dig into PICO-8. Thanks man. :+1:


pinned #5

#6

Hi Paul,

I’ve just completed both tuts. Thanks a lot, it’s good fun playing with them. Found a quick update to the code within your tutorial.

In the ‘Moving stars’ section you have this line.

IF S.Y>128 THEN

The ‘&gt’ part is a ‘>’ within your full code section at the end. It currently gives an error when you try to run it. Just a heads up for anyone going through this great tutorial :slight_smile:

Looking forward to the next instalment.


#7

Grr… copy+paste fail! (FIXED now)
Thank you for bringing that to my attention! :wink:

And thank you for the kind words - glad you liked the tutorials so far :smile: