Programming Games

Class 2 - Variables and Functions

Play homework

Jelpi

Tennis for Two - 1958

Topics

Description

This week we install Pico-8 desktop version and began to create a working game. We’ll understand the basics of a ‘game loop’, draw shapes on the screen and take player input.

Class notes

Variables, continued

So far we have mostly used integers: whole numbers.

For numbers with decimals we use a dot. These are called ‘floats’.

a = 10.4
b = 2.63
c = 0.1
pi = 3.141592

To round a number, we have several options:

flr() -- rounds down
ceil() --rounds up

Note: there is no round() function. But we could make one. This will come in the next section on functions!

Take a look at the following code:

X = 5
Y = 3
Z = X + Y

First we say X = 5. When we give a variable a value, we call that an assignment. We assign 5 to X, and 3 to Y. Next we assign X + Y to Z. So now Z equals 8. Remember that you can always check the value of a variable with print. If we were to change the value of X or Y after Z = X + Y, it would not affect Z. It would still be 8.

X = 5
Y = 3
Z = X + Y
X = 2
Y = 40
print(Z)
--Output: 8

This is because to the computer Z is not X + Y, it’s simply 8.

__

Variable naming rules

There are a few rules when naming a variable. First of all, your variable may have a number in it, but not at the start.

test8 --Good
te8st --Good
8test --Bad, error!

Your variable name also can’t include any special characters like @#$%^&*.

And finally, your variable name can’t be a keyword. A keyword is a word that the programming language uses. Here’s a list of keywords in Lua:

and       break     do        else      elseif
end       false     for       function  if
in        local     nil       not       or
repeat    return    then      true      until     while

You also don’t want to name a variable the same word as a Pico-8 command. For example, it’s a bad idea to make a variable called spr since that’s how you draw a sprite.


Usage

Variables can be used to keep track of things. For example, we can have the variable coins, and every time we pick up a coin we can do coins = coins + 1. ___

Summary

Variables are words in which we can store a value like a number or text. You can name them whatever you want, with a few exceptions. Variables are case-sensitive.

Functions

With functions, we can store pieces of code. This allows us to execute this code whenever we want. It also lets us organize our code into logical parts.

Usage

Often you want to execute certain code in multiple locations. Instead of copying that code each time you want to use it, we can simply add a function call. And if we want to change the behaviour of this function, we only need to change it in one location, which is the function itself. This way we avoid repeating code. Don’t repeat yourself, it’s one of the most important programming principles.

function example()
  print("Hello World!")
end

You start by writing the keyword function, followed by the name of the function. A function is a type of variable, so the same rules apply as when you name a variable. This function’s name is example. After the name we put parentheses (). Now we can start typing the code we want to put inside our function. In this case, we put in print("Hello World!") When you’re done you close the function with an end.

Note that when you run the code, you’ll see no “Hello World!” in your console, this is because we still have to execute the function. We execute a function like this:

example()
--Output: "Hello World!"

You simply type the function’s name, followed by parentheses. This is what we call a function-call.


Parameters

Take a look at this code:

function sayNumber(num)
	print("I like the number " .. num)
end

sayNumber(15)
sayNumber(2)
sayNumber(44)
sayNumber(100)
print(num)
--Output:
--"I like the number 15"
--"I like the number 2"
--"I like the number 44"
--"I like the number 100"
--nil

Inside the parentheses of the function we can put what we call parameters. Parameters are temporary variables that only exist inside the function. In this case we place the parameter num. And now we can use num like any other variable.

We execute our function multiple times, each time with a different number. And thus each time we print the same sentence, but with a different number. The number we put inside the parentheses is what we call an argument. So in the first function-call, we pass the argument 15 to the parameter num.

At the end of our code we print num, outside of our function. This gives us nil. This means that num has no value. It’s not a number, or string, or function. It’s nothing. Because like I said before, parameters are variables that are only available inside the function.


Return

Functions can return a value, which we can store inside a variable, for example. You can return a value by using the return keyword.

function giveMeFive()
	return 5
end

a = giveMeFive()
print(a)
--Output: 5

a becomes the value that giveMeFive returns.

Another example:

-- Multiple parameters and arguments are separated by a comma
function sum(a, b)
	return a + b
end

print(sum(200, 95))
--Output:
--295

Our function sum returns the sum of a and b. We don’t necessarily need to put the value our function returns in a variable first. We can directly print the value.


Back to rounding a number

In our variable section, I mentioned there is no built-in rounding function. There is only the built-in flr() and ceil() functions. So let’s make our own rounding function:

function round(a)
  return flr(a+0.5)
end

--now we can call it
print(round(32.3)) --32
print(round(87.8632)) --88

Summary

Functions can store code that we can execute at any time. We call a function by writing its name followed by parentheses. We can put values inside these parentheses. These values are passed to the function’s parameters, which are temporary variables that only exist within the function. Functions can also return a value. Functions remove repetition and that is a good thing.


Note: Global/local variables in Lua versus other programming languages

A global variable can be used anywhere in your program. A local variable is only present with that value in its current scope where it was created. If you create a local variable inside a function, that variable cannot be used inside another function with the same scope.

If you know another language previously, you may be aware of the difference between local and global variables. By default, variables that are created in Lua including those created inside a function have global scope. To create a specifically local variable you must declare the variable with the word local.

x = 60 --global variable x is 60 in all other functions/locations
local x = 30 --local variable x = 30 in the current scope only

Intro to Pico-8

PICO-8 is available for purchase as an app for Windows, Mac OS X, Linux (Intel), and Raspberry Pi. It also came bundled with the PocketC.H.I.P. portable computer, and works on many other handheld retro game handheld devices (such as Anbernic) and on Raspberry Pi computers. See the PICO-8 website for purchasing information.

PICO-8 intends to capture the fun and creativity of writing programs for the small personal computers of the 1980’s, without the hassles of an arcane platform. The specifications are purposefully limited so that making games is easy and fun. The console has built-in tools for creating graphics, sound, music, and code, all of which run within the console’s 128-by-128 pixel screen. You program PICO-8 using the Lua programming language.

Games are saved as cartridges (or “carts”), which are just small files that can be shared over the Internet. You can publish a cartridge to the PICO-8 forum, where anyone can play it directly in a web browser without the PICO-8 app, discuss it, and download it to examine it in the app’s tools. Publishing a cart also makes it accessible from within the app’s Splore cartridge explorer.

You can also export cartridges with a standalone player that can be embedded into any web page, suitable for commercial publication on game networks such as itch.io.

PICO-8 has an active developer community, with hundreds of games, tools, and resources for learning how to make games.

Splore

Splore is a graphical interface for exploring PICO-8 cartridges. You can use Splore to browse, search, and play cartridges published to the forum (BBS) directly over the Internet. You can also use Splore to navigate the Cartridge Storage area, with or without an Internet connection.

To activate Splore, run the splore command (or its abbreviation, s) from the PICO-8 command prompt.

Select a cartridge to run it. To return to Splore from the running cart, press the Esc key to pause the cart and display the Pause menu, then select Splore.

To return to the command prompt, press Esc from within the Splore interface.

Saving carts locally

When you select a published cartridge, PICO-8 downloads the cart, loads it into memory, and runs it. The cart is stored on your computer in a cache separate from the cartridge storage area.

To save the cart to the storage area:

While the cart is running, press Esc to pause the cart. Select Splore to return to the browse interface. Press Esc again to exit Splore and return to the command prompt. Use the save command to save the cart.

As usual, you can press Esc from the prompt to examine the contents of the cart.

Examples

> splore

> s

Pico-8 specifications

Getting Started

The easiest way to start playing games is to visit the PICO-8 forum in a web browser. All cartridges published to the forum can be played in a web browser without the PICO-8 app.

From the PICO-8 app or a network-connected device (such as PocketC.H.I.P.), you can use the built-in Splore utility to browse and play all carts published to the forum. Type splore at the PICO-8 prompt and hit enter.

PICO-8 includes a small set of demonstration carts built in. To access these carts, you must first run the install_demos command at the PICO-8 prompt. This creates a demos/ folder in the PICO-8 file system with carts that can be loaded, run, and edited.

You can download carts from the forum by clicking on the “Cartridge” link and saving the file. The file is a .p8.png file and appears as an image of a cartridge in a web browser. Use the folder command to open the PICO-8 file system location in your operating system’s file browser, then move the file to that location to make it accessible to PICO-8.

When you execute folder in your PICO-8 prompt, your operating system’s file browser opens a window showing the files and folders in the cartridge storage area. You can copy files in and out of this area to make them available to PICO-8.

To save a cartridge discovered within Splore, load and run the cart, hit Escape to open the pause menu, select Splore, then hit Escape again to return to the PICO-8 prompt. The loaded cart is still in memory. Use the save command to save it as a local file.

Pico-8 command prompt

The PICO-8 command prompt lets you interact with PICO-8 and the currently loaded cartridge using typed commands. By default, the command prompt is the first thing you see when starting PICO-8.

You use the command prompt to load and save cartridges, manage PICO-8 files, organize them into folders (“directories”), and perform other actions such as starting [[Splore]].

> install_demos
> load demos/jelpi
> run

For a list of available commands, see [[CommandReference]].

You can type Lua statements (such as print()) at the prompt to execute them. This is a good way to experiment with the PICO-8 APIReference and try Lua.

Note: The PICO-8 prompt only accepts commands and statements. To see the value of an expression, you must use the print() statement.

Getting back to the command prompt

When playing a cartridge, you can interrupt it and return to the command prompt by pressing the Esc key. You can enter Lua statements at the prompt to examine or change the program’s global variables. In many cases, you can use the resume command to continue the program.

If you started the cartridge from Splore, pressing Escape opens the Splore pause menu. To get to the command prompt from here, select Splore from this menu, then press Escape again.

At the command prompt, pressing Esc opens the cartridge editor interface. To return to the command prompt from here, press Esc again.

The command prompt is exclusive to the PICO-8 app, and is not available from the PICO-8 web player.

Debugging: Frame Advance

Typing a period, “.”, and hitting enter will advance your program one frame by calling _update() and then _draw(). This can be useful to test behavior from frame to frame.

Program structure

The way we structure our software in Pico-8 is based around 3 main functions. The function _init() happens once, at the start of the game. The function _update happens continuously. By default, it is 30 times a second. All code that we need to constantly run should go in here. An example, this is where we’d check continuously for a player’s button presses, to see if the player should move around the screen, for example. This is immediately followed by the function _draw(), which, as you can probably guess, is where all drawing related functions should go. This is where we’ll draw the player, enemies, terrain, the background, and anything else.

So: _init() -> _update() -> _draw() -> _update() -> _draw() -> _update() -> _draw(), etc

Behind the scenes, Pico-8 calls these functions, and we create them, and fill them with code. This is what we call a callback.

Here’s what that looks like, with some typical and basic commands inserted.

x,y=40,52 --starting position for player
score=0

function _init()
end

function _update()
  --buttons
  if btnp(0) then
    x=x-10
  end
  if btnp(1) then
   x=x+10
  end
end

function _draw()
  cls(0) --reset screen black
  spr(1,x,y) --draw player to screen at x,y
  print(score,120,0,8)
end

Summary

LÖVE is a tool that makes it easier for us to make games. LÖVE uses a programming language called Lua. Everything starting with love. is part of the LÖVE framework. The wiki tells us everything we need to know about how to use LÖVE.


Player Input with if statements and keypresses

With if-statements, we can allow pieces of code to only be executed when a condition is met.

You create an if-statement like this:

if condition then
	-- code
end

A condition, or statement, is something that’s either true or false.

For example: 5 > 9

The > means, higher than. So the statement is that 5 is higher than 9, which is false. If we want to check if a value is equal to another value, we need to use 2 equal-signs (==).

For example: 4 == 7

1 equal-sign is for assigning, 2 equal-signs is for comparing.

x = 10 --Assign 10 to x
x == 10 --Compare 10 to x

We can also use >= and <= to check if values are higher or equal to each other or if the values are lower and equal to each other.

10 <= 10 --true, 10 equals to 10
15 >= 4 --true, 15 is higher than 4

So the code above is a shorthand for

10 == 10 or 10 < 10
15 == 4 or 15 > 4

Boolean

A variable can also be true or false. This type of variable is what we call a boolean.

Let’s make a new variable called move, with the value true, and check if move is true in our if-statement.

function _init()
  x = 10
  move = true
end

function _update()
  -- Remember, 2 equal signs!
  if move == true then
    x = x + 10 
  end
end

move is true, so our rectangle moves. But move == true is actually redundant. We’re checking if it’s true that the value of move is true. Simply using move as a statement is good enough.

if move then
  x = x + 10 
end

If we want to check if move is false, we can put a not in front of it.

if not move then
  --only runs if move is set to false
  x = x + 10
end

If we want to check if a number is NOT equal to another number, we use a tilde (~).

if 4 ~= 5 then
  x = x + 10
end

We can also assign true or false to a variable with a statement.

For example: move = 6 > 3.

If we check if move is true, and then change move to false inside the if-statement, it’s not as if we jump out of the if-statement. All the code below will still be executed.

if move then
  move = false
  print("This will still be executed!")
  x = x + 10
end

Arrow keys

Let’s make sprite 1 move based on if we hold down the right arrowkey. For this we use the function btn to check continuously or btnp to check if a button was pressed and released.

if btnp(0) then
  x = x + 10
end

Instead of button “0” we can also use the right arrow to represent the right button. Press Shift+R. To get left, Shift+L. To get down press Shift+D. To get up press Shift+U.

We can also use else to tell our game what to do when the condition is false. Let’s make our rectangle move to the left, when we don’t press right.

if btn(0) then
  x = x + 10
else
  x = x - 10
end

Notice my use of spacing (2 spaces to move to the right) to help me make my code easier to read.

We can also check if another statement is true, after the first is false, with elseif. Let’s make it so that after checking if the right arrowkey is down, and it’s not, we’ll check if the left arrowkey is down.

if btn(0) then
  x = x + 10
elseif btn(1) then
  x = x - 10
end

Try to make the sprite move up and down as well.


and & or

With and we can check if multiple statements are true.

if 5 < 9 and 14 > 7 then
	print("Both statements are true")
end

With or, the if-statement will execute if any of the statements is true.

if 20 < 9 or 14 > 7 or 5 == 10 then
	print("One of these statements is true")
end

Summary

With if-statements, we can allow pieces of code to only be executed when a condition is met. We can check if a number is higher, lower or equal to another number/value. A variable can be true or false. This type of variable is what we call a boolean. We can use else to tell our game what to execute when the statement was false, or elseif to do another check.


Code homework

Part 1: Explore Pico-8

Part 2: Build out a small scene for a charater to explore

For coding homework this week, you can add onto the assignment you began last week with a character and terrain, or build something completely new.

For homework, you will be creating an experimental video game character and making it interactive.

Credits