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.

1
2
3
4
5
6
7
function _init()
	start_game()
end

function start_game()
	init_globals()
end

init_globals

1
2
3
4
5
6
function init_globals()
	_debug=""
	t=0
	player_x=0
	player_y=64
end

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

1
2
3
4
5
6
function make()
	return({
		x=64,
		y=64,
	})
end

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:

1
player=make()

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

1
2
3
4
5
function drawspr(s,x,y)
	x=x or s.x
	y=y or s.y
	sspr(s.sprx,s.spry,s.sprw,s.sprh,x,y,s.sprw,s.sprh,s.flipx,s.flipy)
end

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.

1
function drawspr(s,x,y)

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 is 1
  • y is 2

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’.

1
x=x or s.x

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.

1
sspr()

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

1
2
3
4
5
function allpal(col)
	for i=0,15 do
		pal(i,col)
	end
end

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:

1
2
3
4
5
pal(0,col)
pal(1,col)
pal(2,col)
...
pal(15,col)

… 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.

1
2
3
4
5
6
7
function animate(s)
	s.animfr += s.animspd
	if flr(s.animfr) > #s.anim then
		s.animfr=1
	end
	s.sprx=s.anim[flr(s.animfr)]
end

This is designed to be called in the _update function and can be used on any sprite. Here’s what it does:

  1. Increment the current animation frame s.animfr by s.animspd (the animation speed)
  2. 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 the s.anim table’)
  3. 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!