Skip to content
Published April 25, 2019

Today we’ll be looking at more programming concepts which are a bit more advanced. After learning these concepts, you’ll be one step closer towards game development.

Flow Control

Flow control is similar to a flow chart and is at the heart of nearly all programming. You can think of it as making choices based on conditions. There’s a good chance that you’ve used flow control in your daily life. For example, if you said “If it’s not raining out, then I will go for a run”, you’re using flow control since you’re making a choice based on some evidence.

Flow control is highly applicable for games. You might ask whether a player’s hit box is touching an enemy or if a player is pressing a certain key. After these questions are asked, you write a block of code that runs when the condition is met. If the condition is not met, the code will not run. Let’s look at the basic flow control functions inherent to most programming languages.

Conditionals

If conditional

The if conditional simply checks is something is true (or false if you pair it with the not keyword). The condition that you’re checking for should return a true or false; that is, you shouldn’t simply place numbers, strings, tables, or anything that doesn’t evaluate into a bool into it. Here is the basic syntax for an if statement in Lua:

if (condition) then
-- Block of code
end

Notice the keywords then and end. Lua uses these keywords to denote the end of the if line and the end of the code block, respectively. The parentheses are optional, but I personally like to use them to help organize the code a bit.

Let’s say that you are storing the player’s health in a variable called player_health, and you want to find out when the health reaches zero or below. You would write the code like this:

if (player_health <= 0) then
-- Block of code
end

In flow control conditionals (such as the if statement), you perform checks on data using comparison operators. These operators will check two pieces of data against each other and return either true or false, which is what we want for our flow control conditionals. Here is a list of some basic comparison operators and what they do:

== operator: checks if two values are equivalent
< operator: checks if the left value is less than the right value
<= operator: checks if the left value is less than or equal to the right value
> operator: checks if the left value is greater than the right value
>= operator: checks if the left value is greater than or equal to the right value
~= operator: checks if two values are different

A large portion of your game’s code is likely to be made up of if statements. There are two additional optional parts to the ifthenend that you can add at your discretion.

Elseif conditional

Let’s say you have one variable that you are checking. If that variable has multiple different values that you are looking for that are mutually exclusive, you’ll want to use the elseif condition statement. It’s similar in structure to the if statement. Here is the general syntax for it:

if (condition 1) then
-- Block of code

elseif (condition 2) then
-- Block of code

end

The pseudocode above is for two separate conditions, but you can have as many elseif statements in one block as you like. This forms a sort of “daisy chain” that you may encounter often. Some programming languages, such as C, have built-in statements such as switch…case to handle this daily chaining and make it more compact, but unfortunately this feature is not present in Lua. In addition, it’s important to note that the Lua interpreter travels through the if block chain in order, so if more than one condition can be satisfied, Lua will use the first one that works, and ignore the rest. This is known as short-circuiting.

Lastly, you may want to have a “default” piece of code to be executed in your if block if no condition is met. You can accomplish this using the else keyword.

Else conditional

The else conditional statement works as advertised – it’s the piece of code you want to run if none of the other conditions in your code block are met. Here is the syntax for it.

if (condition 1) then
-- Block of code

else
-- Block of code

end

Notice that the keyword then is missing from the else statement. Lua only needs then if there is a specific condition to be checked for. Since else doesn’t check for a condition, but instead activates when the other conditions are not met, then is not needed.

Loops

While loop

The while loop is a conditional statement that works differently from the if statement. While the if statement only checks a condition once, while is a loop that will keep repeating a piece of code until it’s condition is no longer met. Here is the syntax:

while (condition) do
-- Block of code

end

For the while loop, you use the keyword do in place of then. Otherwise, the keywords function identically. This is to keep in line with Lua’s syntax goal of trying to be similar to the English language. It’s important to note that the while loop won’t start if the condition isn’t met at least once.

One thing that’s very important to keep in mind with the while loop (and all loops, for that matter) is that it is very easy to cause an infinite loop, thus freezing your game up completely. Let’s see an example of this.

a = 1
while (a >= 0) do
a = a + 1
end

The reason this code causes an infinite loop is because a will always be at least 0. The loop won’t finish until the while condition becomes false at some point. If your game hangs unexpectedly, it could be due to an infinite loop like this one.

For loop

The last conditional we will look at is the for loop. The for loop is similar to the while loop, except that you use the for loop when you know how many loops to perform. Here is the basic syntax.

 for var=exp1,exp2,exp3 do
-- Block of code

end

Here’s how this works:

  • Exp1 is the initializer value. This is the starting value for variable var.
  • Exp2 is the terminating value. This is the value that variable var must reach for the for loop to terminate.
  • Exp3 is the incremental value. This is the value added to the variable each loop. The default value is 1 if you do not supply a value.

Each expression exp gets evaluated once at the start of the for loop. At the end of each run of the for loop, the incremental value exp3 (default is 1) is added to the initializer value. Then, the for loop checks if exp1 is equal to exp2 (the terminating value). If exp1 is equal to exp2, the loop finishes. Otherwise, the loop continues. This means that you can easily bypass the terminating value and loop forever if you aren’t careful. I generally write in this format:

for i=1,x do    -- x is the number of times you want to loop
-- Block of code

end

There are some other uses of the for loop, but I’ll be holding off on covering it until the section about tables.

Now that we’ve covered the conditional blocks, we need to look at scope briefly so that you don’t a mistake with your lexical scoping.

Scope

Scoping basically covers where you can use the variables you declare. Depending on where you declare your variable (and whether that variable is global or local), your variable can go out of scope, which means you can’t access it anymore. Once it’s out of scope, for all intents and purposes, it’s gone for good.

Let’s start with global variables because they are the easiest to understand in my opinion.

Global variables

If a variable is global, you can use it anywhere, including other files. If you’re just starting out with Pico-8, there’s a good chance you’ll be just using one file, so this isn’t a huge concern, but since the latest Pico-8 update you can include other files using the #include tag. I wouldn’t worry about using other files until you start building larger, more advanced games.

When you declare a variable, that variable is automatically considered global unless you use the local keyword in front of it. For example:

player_x_pos = 13

Now I can use the variable player_x_pos anywhere I feel like it (provided it’s not before player_x_pos was declared). For instance:

player_x_pos = 13 -- I declare the global player_x_pos here
for 1,4 do
player_x_pos += 1 -- I add to the variable
end
printh(player_x_pos) -- Now I can print the variable's value

This means that if I try to use the same name player_x_pos for some other context, I will overwrite the previous definition. As a result, global variables are most often used for variables that should mean the same thing in different contexts. This can get hectic quickly if you declare a lot of global variables and struggle to keep track of them. Because of this, try to limit your usage of global variables to variables that really need to be accessed anywhere. For example, in my games, I often need enemies to know the position of the player, so I usually set the player to a global variable so that any enemy can check it.

There are ways around using global variables such as passing local variables to functions. That way, you can guarantee only certain functions or contexts can modify or look at your variables. It really just depends on the situation if you want to use global variables. Global variables are like salt – if you sprinkle a little here and there for the right context, it can improve things. Use too much, however, and it’ll be a nightmare that leaves a bad taste in your mouth 🙁

Local variables

Now that we’ve covered global variables, let’s take a look at local variables. If you want to mark a variable as local, use the local keyword in front of it.

local str = "Hello there!"

Local variables work best as throwaway variables. They also come in handy as previously mentioned for controlling access to variables.

Here is an example of scoping with local variables in action.

for i=1,5 do
local str = "Hello world!" -- Create the local variable str
printh(str) -- Print the value of str
end
printh(str) -- This will cause an error, as str no longer exists

The variable str only exists in the for loop where we declared it; it ceases to exist once the for loop ends. In this case, if you want to call str from outside the for loop, you would either have to make it global or declare it outside the for block. Think of if statements, for loops, etc. as blocks or shells. You can’t look at them from outside the context that they were declared in. However, you can usestr for other loops or functions that it is outside of. You can put any block inside another block, such as an if statement inside of a for loop. This is called nesting. I’ll show you an example of nesting with our for loop:

 for i=1,5 do
local str = "Hello world!" -- Create the local variable str
if(str ~= nil) then -- Check if our str variable is not empty
printh(str) -- Print the value of str
end
end

You can use str inside the nested if statement, but still not outside of it. The end keyword helps you to keep track of the different levels of scoping. In addition, it is a good idea to use tabs (or 2 spaces, or four spaces, etc.) to visually separate your code by block level or depth. You can see in the previous example that there are three levels in total.

for i=1,5 do                   -- level 1
local str = "Hello world!" -- level 2
if(str ~= nil) then
printh(str) -- level 3
end
end

Remember, when using local variables, you can use them in deeper levels but not earlier levels. So you can utilizestr in level 2 and three, but not level 1. Once level 2 finishes at the end of the for loop, str becomes inaccessible.

Next time, we’ll take a look at tables and functions.