Helper Functions
There are some techniques, like _debug
mentioned in part 2, that I use in almost all my games. In this post we’ll implement some of them right at the beginning, because it’ll save us time later!
In Pico-8’s code editor you’ll notice a number at the bottom-right which increases as you write code. This is the token count, and it’s very important to keep an eye on this because Pico-8 programs can only have 8192 tokens! This might seem restrictive but it’s actually useful when just starting out to have constraints like this, because it encourages you to keep your game short which means you’ll have more chance of finishing it. It also encourages good coding practises which will be useful even when writing non-game programs later on.
Let’s go back to the example of drawing a sprite. In our program we defined player_x
and player_y
variables and used them in the _update
and spr()
functions. But what if we had 200 NPCs on screen? Following the same approach, we’d need 200 variables, 200 extra lines in _update
for movement and 200 calls to spr()
to draw them. Clearly, this approach is going to use up a lot of tokens very quickly - in programming lingo we’d say it doesn’t scale well. In order to avoid this kind of problem, we need to be mindful to notice when we’re falling into coding patterns that don’t scale well and will eat up tokens, and instead try to reuse existing code as much as possible. The way we do this is by writing functions that can be reused, and I call these helper functions.
start_game
Let’s start at the beginning. I already mentioned that _init
is typically used to initialize variables for later use. However, it can be a mistake to initialize everything directly in the _init
function. This is because games often ‘restart’, either when the player chooses to restart or encounters a game over. In this case, we want to set our game state back to the beginning without reloading the entire game. So we can’t use _init
for this purpose, because it’s only called when the program starts up.
A simple technique to solve this is to abstract the task of initializing variables away from the _init
function and make it a general purpose function that can be called at any time. To do this, I’ve created a separate start_game
function. This will be called from within _init
, but it can also be called later on whenever we want to restart the game. This has saved us tokens, because otherwise we would have had to initialize the game state within _init
and whenever we wanted to restart the game.
|
|
init_globals
|
|
Here we’ve just moved our code that was in _init
to init_globals
. This name ‘globals’ is short for global variables, which just refers to variables that can be used anywhere in the program. We’ll look at different kinds of variables later.
I’ve also added t=0
here. The variable t
will be used to hold the number of times that _update
has been called since the program started. This is useful for all kinds of various effects and timers, so let’s put it in right away.
make
|
|
The make
function returns a table, which is the thing with the curly brackets. An empty table is simply {}
. The programming language that Pico-8 uses is actually called Lua, and Lua uses tables for many different purposes. In Pico-8, using tables helps to write reusable code. In the example above, you’ll see x=64
and y=64
which looks like we’re assigning values to variables. However, these variables are inside the table, so we call them properties of the table.
In this make
function we are returning a new table with two properties. When a function uses return
, it means that this value will be passed to any function call to make()
. We can use it like this:
|
|
player
would now be a new table with the x
and y
properties. We can use this instead of having to write player_x
and player_y
- let’s do that later.
drawspr
|
|
Remember how we used player_x
in the call to spr()
to display the player sprite at the given coordinates? Well, instead of having to know the exact name of the variables that are holding our coordinates for the thing we want to draw, we can write a new function called drawspr
. This function takes 3 parameters. The first, s
, can be any table, such as the player in our game, or an enemy, or a fireball, etc. The only requirement is that the table must have x
and y
properties so that we can draw it. The drawspr
accesses these properties using the .
notation as in s.x
and s.y
.
There’s a few more things going on in this function. Don’t worry if you don’t understand everything at this stage - we’ll see more examples of this later on.
|
|
This says our function takes 3 parameters: s
, x
and y
. This means that when we call the function like drawspr({},1,2)
, it will replace the variables within the function with the arguments passed to the function. So in this case:
s
is{}
(an empty table)x
is1
y
is2
It doesn’t matter if we use the variables s
, x
or y
elsewhere in our program. They only have these values within the drawspr
function - we say that these variables are local to the function, as opposed to global variables which can be accessed anywhere. When we call a function, we don’t need to use all of the parameters. We can call drawspr({})
, using only the s
parameter. What happens in this case? The other parameters will take the default value of nil
, which is a special value meaning ’no value’.
|
|
This is a common trick when a function has optional parameters. Because we can’t draw a sprite with coordinates of nil
, we need to change the value to something else. In this case, if x
is nil
, we want to use s.x
instead. The code can be read as “Set x
to x
if the x
value was given in the function call, otherwise use s.x
(the property of the table)”. This means that in most cases we can call drawspr(s)
to draw the sprite at its current location, but we have the option of drawing it anywhere else if we need to, just by calling drawspr(s,x,y)
, which might be useful.
|
|
Note also that we’re using sspr
instead of spr
. This is not a typo! sspr
is a slightly more complicated version of spr
that allows us to draw sprites of any size (not just multiples of 8 pixels) and can even draw them bigger or smaller on the screen. It’s usually more convenient to stick to spr()
, but for this game I’m making I found that the sprites look slightly better at 12x12 pixels, so I need to use sspr
. Besides, implementing drawspr
means that we should never have to worry about using sspr
directly - in fact we can forget whether we’re using sspr
or spr
. That’s the beauty of using helper functions to abstract away details like this!
allpal
|
|
This short function is used for graphical effects. In Pico-8, the default palette has 16 colours - you can see which colours correspond to which numbers in the sprite editor. But Pico-8 also lets you change which numbers correspond to which colours, by calling the pal()
function. For example, pal(0,7)
would replace black with white.
This function sets all the palette colours to col
. So, calling allpal(0)
would set all colours to black. Why would you want to do this? Well, often we want to apply graphical effects to entire sprites such as a ‘flash’ effect or a ‘damage’ effect. This could be achieved by drawing the sprite as white or red for a few frames. If you call allpal(0)
before spr()
then the sprite will be drawn as normal, but entirely black.
This code also introduces another very common programming concept called for loops. We could have written the code as:
|
|
… but that’s tedious and wastes tokens. So instead, for i=0,15
says to run the code for every value between 0
and 15
, and assign this value to i
each time. There are other different kinds of for
loops, but note that by default, for
will increment the value by 1
on each iteration of the loop.
animate
Finally, we’re going to be animating a lot of sprites in our game, so we want something similar to drawspr
that can be used to animate sprites.
|
|
This is designed to be called in the _update
function and can be used on any sprite. Here’s what it does:
- Increment the current animation frame
s.animfr
bys.animspd
(the animation speed) - If the
animfr
exceeds the available frames for the current animation, loop back to the first frame (1). (The#
in#s.anim
means ’the number of items in thes.anim
table’) - Set the
sprx
property to the animation frame that corresponds to the current animation frame.
We haven’t defined the anim
property yet because we don’t have any animations that use it, but it will be a table of values like {16,32,48}
that correspond to the x coordinate on the sprite sheet.
Saving the program
Leave the _update
and _draw
functions as they are and save the program so far using Ctrl-S
. That’s all the helper functions we’ll need for now - next we’ll look at implementing player movement in our game!