Programming Games

Class 9 - Getting Started with LÖVE

Play homework

Oh My Giraffe

Tennis for Two - 1958

Topics

Description

This week we take what we know of Lua and Pico-8 and apply it to using another framework: Love. We install Love and began to create video game graphics. We’ll review the basics of a ‘game loop’, draw shapes on the screen and take player input, comparing this with the approach of Pico-8 and using the added ability to draw more complex images to the screen.

Class notes

Installation

LÖVE

Go to love2d.org.

You should download either the 32-bit or the 64-bit installer. On a modern computer you likely have a 64-bit computer.

Open the installer. Click on Next. Click on I agree. Now you can decide where you install LÖVE. It doesn’t matter where you install LÖVE, but make sure you remember the location because we need it in a moment. This folder will be referred to as the Installation Folder.

On a PC I may place my installation folder at C:/Program Files/LOVE.

Click on Next. Click on Install.

When LÖVE is done installing, click on Finish.


ZeroBrane Studio

Now we need to install a text editor. We’re going to use ZeroBrane Studio in this tutorial.

Go to studio.zerobrane.com, and click on “Download”.

Here you get the option to donate to ZeroBrane Studio. If you don’t want to donate click on “Take me to the download page this time”,

Open the installer, and install ZeroBrane Studio in your preferred folder.

When ZeroBrane Studio is done installing, open it.

Now we need to make a Project Folder. Open your file explorer and create a folder wherever you like, and name it whatever you want. In ZeroBrane Studio, click on the “Select Project Folder” icon, and select the folder you just created.

In ZeroBrane Studio, create a new file. File -> New, or use the shortcut Ctrl + N.

Inside this file, write the following code:

function love.draw()
	love.graphics.print("Hello World!", 100, 100)
end

Go to File -> Save, or use the shortcut Ctrl + S. Save the file as main.lua.

IMPORTANT: DO THIS ONCE TO SETUP: Go to Project -> Lua Interpreter and select LÖVE.

Now whenever you press the F6 or press the Play button, a window should open with the text “Hello World!”.

In case nothing happens and the following text shows up: Can’t find love2d executable in any of the following folders, you installed LÖVE somewhere where ZeroBrane Studio can’t find it. Go to Edit -> Preferrences -> Settings: User. Put in the following:

Windows:

path.love2d = 'C:/path/to/love.exe'

And replace 'C:/path/to/love.exe' with the path to where you installed LÖVE. Make sure to use frontslashes (/).

Mac:

path.love2d = '/Applications/love.app/Contents/MacOS/love'

A few more things

Did you copy/paste the example code? I encourage you to type the code I show you yourself. That might seem like a lot of extra work, but by doing so it will help you memorize everything a lot better.

The one thing you don’t need to type yourself are comments.

-- This line is a comment. This is not code.
-- The next line is code:

print(123)

--Output: 123

Every line starting with 2 minus-signs (–) is a comment. The computer ignores it, meaning we can type anything we want without getting an error. I can use comments to explain certain code better. When typing over the code you don’t have to copy the comments.

With print we can send information to our Output console. This is the box at the bottom of our editor. When you close the game, it should say the text “123”. I add the text --Output: to show you the expected output. This is not to be confused with love.graphics.print.

If you put the following code at the top of your main.lua you will see the prints right away. How this works is not important, basically you turn off that the program should wait to be closed before showing the prints.

io.stdout:setvbuf("no")

Alternative text editors

Getting started with LÖVE

What is LÖVE?

LÖVE is what we call a framework. Simply said: It’s a tool that makes programming games easier.

LÖVE is made with C++ and OpenGL, which are both considered to be very difficult. The source code of LÖVE is very complex. But all this complex code makes creating a game a lot easier for us.

For example, with love.graphics.ellipse(), we can draw an ellipse. This is the source code behind it:

void Graphics::ellipse(DrawMode mode, float x, float y, float a, float b, int points)
{
	float two_pi = static_cast<float>(LOVE_M_PI * 2);
	if (points <= 0) points = 1;
	float angle_shift = (two_pi / points);
	float phi = .0f;

	float *coords = new float[2 * (points + 1)];
	for (int i = 0; i < points; ++i, phi += angle_shift)
	{
		coords[2*i+0] = x + a * cosf(phi);
		coords[2*i+1] = y + b * sinf(phi);
	}

	coords[2*points+0] = coords[0];
	coords[2*points+1] = coords[1];

	polygon(mode, coords, (points + 1) * 2);

	delete[] coords;
}

You might not understand this code above at all (I barely do so myself), and that’s exactly why we use LÖVE. It takes care of the hard parts of programming a game, and leaves the fun part for us.

Lua

Lua is the programming language that LÖVE uses. Lua is a programming language on its own, and is not made by or for LÖVE. The creators of LÖVE simply chose Lua as the language for their framework.

So what part of what we code is LÖVE, and what is Lua? Very simple: Everything starting with love. is part of the LÖVE framework. Everything else is Lua.

These are functions part of the LÖVE framework:

love.graphics.circle("fill", 10, 10, 100, 25)
love.graphics.rectangle("line", 200, 30, 120, 100)

And this is simply Lua:

function test(a, b)
	return a + b
end
print(test(10, 20))
--Output: 30

If it’s still confusing to you, that’s okay. It’s not the most important thing right now.


How does LÖVE work?

LÖVE calls 3 functions. First it calls love.load(). In here we create our variables.

After that it calls love.update() and love.draw(), repeatedly in that order.

So: love.load -> love.update -> love.draw -> love.update -> love.draw -> love.update, etc.

Behind the scenes, LÖVE calls these functions, and we to create them, and fill them with code. This is what we call a callback.

LÖVE is made out of modules, love.graphics, love.audio, love.filesystem. There are about 15 modules, and each module focuses on 1 thing. Everything that you draw is done with love.graphics. And anything with sound is done with love.audio.

For now, let’s focus on love.graphics.

LÖVE has a wiki with an explanation for every function. Let’s say we want to draw a rectangle. On the wiki we go to love.graphics, and on the page we search for “rectangle”. There we find love.graphics.rectangle.

This page tells us what this function does and what arguments it needs. The first argument is mode, and needs to be of the type DrawMode. We can click on DrawMode to get more info about this type.

DrawMode is a string that is either “fill” or “line”, and controls how shapes are drawn.

All following arguments are numbers.

So if we want to draw a filled rectangle, we can do the following:

function love.draw()
	love.graphics.rectangle("fill", 100, 200, 50, 80)
end

Now when you run the game, you’ll see a filled rectangle.

The functions that LÖVE provides is what we call the API. API stands for Application Programming Interface. You can read the Wikipedia article to learn what an API exactly is, but in this case it simply means the LÖVE functions you can use.


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.


Hello, World!

There’s not much to see in the LÖVE engine. It's really a framework, meaning you can use whatever text editor you prefer. First thing to try is a "hello world" program, to make sure it launches, and to introduce you to the basic syntax of both the Lua language and the LÖVE framework. Open a text editor and enter this text.

Note that you’ll need to download the public domain font And The Door Creaked and unzip and place it in the project folder. Alternatively, use a different font and change the location and name of the font file. If you cut the font altogether then you will get a generic font and default size in your program if you remove that line.

function love.load()
  cwide = 520
  chigh = 333
  
  love.window.setTitle(' Hello Wörld ')
  love.window.setMode(cwide, chigh)
  font = love.graphics.setNewFont("creaked.otf", 72)
end

function love.draw()
  love.graphics.setBackgroundColor(0,0,0)
  
  love.graphics.setColor(1, 0.03, 0.2)
  love.graphics.print("Hello World", cwide/4, chigh/3.33)
end

Save that as main.lua.

A distributable LÖVE package is just a standard zip file, with the .love extension. The main file, which must always be named main.lua, must be at the top level of the zip file. Create it like this (in the command line):

zip hello.love main.lua

And launch it:

love ./hello.love

Moving a rectangle

Now we can start with what I like to call “The fun part”. We’re going to make stuff move!

Let’s start with the 3 main callbacks.

function love.load()

end


function love.update()


end


function love.draw()

end

Next, we draw a rectangle.

function love.draw()
	love.graphics.rectangle("line", 100, 50, 200, 150)
end

The second and third argument of this function are the x and y position.

x means “horizontal position on the screen”. 0 is the left side of the screen.

y means “vertical position on the screen”. 0 is the top side of the screen.

Now we want to make the rectangle move. It’s time to start thinking like a programmer. What exactly needs to happen in order for the rectangle to move to the right? The x-position needs to go up. 100, 101, 102, 103, 104, etc. But we can’t change 100 to 101. 100 is simply 100. We need to have something that can change in any number we want it to be. That’s right, a variable!

In love.load, create a new variable called x, and replace the 100 in love.graphics.rectangle with x.

function love.load()
	x = 100	
end

function love.draw()
	love.graphics.rectangle("line", x, 50, 200, 150)
end

So now the x-position of our rectangle is the value of x.

Note that the variable name x is just a name. We could’ve named it icecream or unicorn or whatever. Functions don’t care about variable names, it only cares about its value.

Now we want to make the rectangle move. We do this in love.update. Every update we want to increase x by 5. In other words, x needs to be value of x + 5. And that’s exactly how we write it.

function love.update()
	x = x + 5
end

So now when x equals 100, it will change x into 100 + 5. Then next update x is 105 and x will change into 105 + 5, etc.

Run the game. The rectangle should now be moving.

___

Delta time

We got a moving rectangle, but there’s one small problem. If you were to run the game on a different computer, the rectangle might move with a different speed. This is because not all computers update at the same rate, and that can cause problems.

For example, let’s say that Computer A runs with 100 fps (frames per second), and Computer B runs with 200 fps.

100 x 5 = 500

200 x 5 = 1000

So in 1 second, x has increased with 500 on computer A, while on computer B x has increased with 1000.

Luckily, there’s a solution for this: delta time.

When LÖVE calls love.update, it passes an argument. Add the parameter dt (short for delta time) in the love.update, and let’s print it.

function love.update(dt)
	print(dt)
	x = x + 5
end

Delta time is the time that has passed between the previous and the current update. So on computer A, which runs with 100 fps, delta time on average would be 1 / 100, which is 0.01.

On computer B, delta time would be 1 / 200, which is 0.005.

So in 1 second, computer A updates 100 times, and increases x by 5 x 0.01, and computer B updates 200 times and increases x by 5 x 0.005.

100 x 5 * 0.01 = 5

200 x 5 * 0.005 = 5

By using delta time our rectangle will move with the same speed on all computers.

function love.update(dt)
	x = x + 5 * dt

Now our rectangle moves 5 pixels per second, on all computers. Change 5 to 100 to make it go faster.


Summary

We use a variable that we increase on each update to make the rectangle move. When increasing we multiply the added value by delta time. Delta time is the time between the previous and current update. Using delta time makes sure that our rectangle moves with the same speed on all computers.

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.

Put an if-statement around the code of x increasing.

function love.update(dt)
	if 5 > 9 then
		x = x + 100 * dt
	end
end

When you run the game you’ll notice that our rectangle isn’t moving. This is because the statement is false. If we were to change the statement to 5 < 9 (5 is lower than 9), then the statement is true, and the code inside the if-statement will execute.

With this, we can for example make x go up to 600, and then make it stop moving, with x < 600.

function love.update(dt)
	if x < 600 then
		x = x + 100 * dt
	end
end

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 love.load()
	x = 100
	move = true
end

function love.update(dt)
	-- Remember, 2 equal signs!
	if move == true then
		x = x + 100 * dt
	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 + 100 * dt
end

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

if not move then
	x = x + 100 * dt
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 + 100 * dt
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 + 100 * dt
end

Arrow keys

Let’s make the rectangle move based on if we hold down the right arrowkey. For this we use the function love.keyboard.isDown (wiki).

Notice how the D of Down is uppercase. This is type of casing is what we call camelCasing. We start the first word in lowercase, and then every following word’s first character we type in uppercase. This type of casing is also what we will be using for our variables throughout this tutorial.

We pass the string “right” as first argument to check if the right arrowkey is down.

if love.keyboard.isDown("right") then
	x = x + 100 * dt
end

So now only when we hold down the right arrowkey does our rectangle move.

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 love.keyboard.isDown("right") then
	x = x + 100 * dt
else
	x = x - 100 * dt --We decrease the value of x
end

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 love.keyboard.isDown("right") then
	x = x + 100 * dt
elseif love.keyboard.isDown("left") then
	x = x - 100 * dt
end

Try to make the rectangle 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

One more thing

To be more precise, if-statements check if the statement is NOT false or nil.

if true then print(1) end --Not false or nil, executes.
if false then print(2) end --False, doesn't execute.
if nil then print(3) end --Nil, doesn't execute
if 5 then print(4) end --Not false or nil, executes
if "hello" then print(5) end --Not false or nil, executes
--Output: 1, 4, 5

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.


Working with images

Creating and using images is a very easy task in LÖVE. First we need an image. I’m going to use this image:

Of course, you can use any image you like, as long as it’s of the type .png. Make sure the image is in the same folder as your main.lua.

First we need to load the image, and store it in a variable. For this we will use love.graphics.newImage(path). Pass the name of the image as a string as first argument. So if you have a

function love.load()
	myImage = love.graphics.newImage("sheep.png")
end

You can also put your image in a subdirectory, but in that case make sure to include the whole path.

myImage = love.graphics.newImage("path/to/sheep.png")

Now our image is stored inside myImage. We can use love.graphics.draw to draw our image.

function love.draw()
	love.graphics.draw(myImage, 100, 100)
end

And that is how you draw an image.


.draw() arguments

Let’s take a look at all the arguments of love.graphics.draw(). All arguments besides the image are optional.

image

The image you want to draw.

x and y

The horizontal and vertical position of where you want to draw the image.

r

The rotation (or angle). All angles in LÖVE are radians. I’ll explain more about radians later.

sx and sy

The x-scale and y-scale. If you want to make your image twice as big you do

love.graphics.draw(myImage, 100, 100, 0, 2, 2)

You can also use this to mirror an image with

love.graphics.draw(myImage, 100, 100, 0, -1, 1)

ox and oy

The x-origin and y-origin of the image.

By default, all the scaling and rotating is based on the top-left of the image.

This is based on the origin of the image. If we want to scale the image from the center, we’ll have to put the origin in the center of the image.

love.graphics.draw(myImage, 100, 100, 0, 2, 2, 39, 50)

kx and ky

These are for shearing (which doesn’t have a k at all so I’m not sure what to make of it).

With it you can skew images.

love.graphics.print, which we used before to draw text, has these same arguments.

x, y, r, sx, sy, ox, oy, kx, ky

Again, all these arguments except image can be left out. We call these optional arguments.

You can learn about LÖVE functions by reading the API documentation.


Image object

The image that love.graphics.newImage returns, is actually an object. An Image object. It has functions that we can use to edit our image, or get data about it.

For example, we can use :getWidth() and :getHeight() to get the width and height of the image. We can use this to put the origin in the center of our image.

function love.load()
    myImage = love.graphics.newImage("sheep.png")
    width = myImage:getWidth()
    height = myImage:getHeight()
end

function love.draw()
	love.graphics.draw(myImage, 100, 100, 0, 2, 2, width/2, height/2)
end

Color

You can change in what color the image is drawn with love.graphics.setColor(r, g, b). It sets the color for everything you draw, so not only images but rectangles, shapes and lines as well. It uses the RGB-system. Although this officially ranges from 0 to 255, with LÖVE it ranges from 0 to 1. So instead of (255, 200, 40) you would use (1, 0.78, 0.15). If you only know the color using the 0-255 range, you can calculate the number you want with number/255. There is also the fourth argument a which stands for alpha and decides the transparency of everything you draw. Don’t forget to set the color back to white if you don’t want the color for any other draw calls. You can set the background color with love.graphics.setBackgroundColor(r, g, b). Since we only want to call it once, we can call it in love.load.

function love.load()
    myImage = love.graphics.newImage("sheep.png")
    love.graphics.setBackgroundColor(1, 1, 1)
end

function love.draw()
	love.graphics.setColor(255/255, 200/255, 40/255, 127/255)
	love.graphics.setColor(1, 0.78, 0.15, 0.5)
	-- Or ...
	love.graphics.draw(myImage, 100, 100)
	-- Not passing an argument for alpha automatically sets it to 1 again.
	love.graphics.setColor(1, 1, 1)
	love.graphics.draw(myImage, 200, 100)
end


Summary

We load an image with myImage = love.graphics.newImage("path/to/image.png"), which returns an Image object that we can store in a variable. We can pass this variable to love.graphics.draw(myImage) to draw the image. This function has optional arguments for the position, angle and scale of the image. An Image object has functions that you can use to get data about it. We can use love.graphics.setColor(r, g, b) to change in what color the image and everything else is drawn.


Code homework

This is a warmup assignment to help you get started experimenting in Love2d, looking up commands in the reference, and trying your hand and starting to make games.

During this assignment, don’t forget there is a useful wiki and tutorials page with links to info and code snippets you can use.

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

To turn in, upload your file as a compressed zipped folder and a screenshot.

Credits