# Show your Pi Some LÖVE
by Seth Kenlon
The Raspberry Pi is famous for introducing kids to open source
software and programming. It's an affordable, practical introduction
to professional-grade computing, disguised as hackable fun. The
application that's done the most to get young children started in
programming has been Mitch Resnick's Scratch (which was fortunately
[forked by the Pi Foundation](
http://github.com/raspberrypi/scratch)
when Scratch 2 switched to the non-open Adobe Air), but an inevitable
question is what someone should graduate to after they've outgrown
drag-and-drop programming.
There are many candidates for the next level of programming after a
drag-and-drop intro like Scratch. There's the excellent
[PyGame](
http://pygame.org), a Java subset called
[Processing](
http://processing.org), the
[Godot](
http://godotengine.org), and many others. The trick is to find
an engine that is easy enough to ease the transition from the instant
gratification of drag-and-drop, but complex enough that it
accurately represents what professional programmers actually do all
day.
A particularly robust game engine is called
[LÖVE](
http://love2d.org). It uses the scripting language
[Lua](
http://lua.org), which doesn't get as much attention as Python,
but is actually [heavily
embedded](
https://developer.valvesoftware.com/wiki/Dota_2_Workshop_Tools/Lua_Abilities_and_Modifiers)
(both literally and figuratively) in the modern video game
industry. It's listed as a skill by almost all the major game studios,
it's an option in the proprietary Unity and Unreal game engines, and
it generally pops up in all kinds of unexpected places in the real
world. To make a long story short, Lua is a language worth learning,
especially if you're planning on going into game development. As for
as the LÖVE engine is concerned, it's about as good as the PyGame
framework in terms of features, and since it has no IDE, it is very
lightweight and, in a way, less complex to learn than something like
Godot.
Better yet, LÖVE runs on the Pi natively, but its projects can be open
and run on any platform that Lua does. That includes Linux, Windows,
and Mac, but also Android and even the oh-so-closed iOS. In other
words, it's not a bad platform to get started in mobile development,
too.
![Mr. Rescue, an open source game available on itch.io](love_rescue.png)
Now that I've sold you on LÖVE as the next step up from Scratch on the
Pi, let's dig in and see how it works.
## Intalling LÖVE
As usual, installing LÖVE is just one easy command on the Raspberry Pi:
$ sudo apt install love2d
If you're running [Fedora on your Pi](
https://opensource.com/article/17/3/how-install-fedora-on-raspberry-pi), use `dnf` instead:
$ sudo dnf install love2d
Either way, the package management system pulls in Lua, SDL, and other
dependencies that LÖVE needs to run.
## Hello World
There's not much to see in the LÖVE engine. It's really just a
framework, meaning you can use whatever text editor you prefer. First
thing to try is a "hello world" program, just 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:
cwide = 520
chigh = 333
love.window.setTitle(' Hello Wörld ')
love.window.setMode(cwide, chigh)
function love.load()
love.graphics.setBackgroundColor(177, 106, 248)
-- font = love.graphics.setNewFont("SkirtGirl.ttf", 72)
end
function love.draw()
love.graphics.setColor(255, 33, 78)
love.graphics.print("Hello World", cwide/4, chigh/3.33)
end
Save that as `main.lua`.
A distributable LÖVE package is just a 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:
$ zip hello.love main.lua
And launch it:
$ love ./hello.love
![Hello World](love_hello.png)
The code is pretty easy to understand. The `cwide` and `chigh`
variables are global, available to the whole script, and they set the
width and height of the game world. In the first block of code (called
a `function`), the background color is set, and in the second function
the "hello world" gets printed to the screen.
The line preceded by two dashes (--) is a comment. Including a `.ttf`
in the `.love` package and uncommenting the `setNewFont` line renders
text in that font face.
$ zip hello.love main.lua SkirtGirl.ttf
And launch it:
$ love ./hello.love
## The Basics
One big difference between Scratch and a professional programming
language is that with Scratch, you can learn a few basic principles
and then just randomly explore until you've discovered all the other
features.
With lower-level programming languages like Lua, the things you learn
first aren't things you can just copy and paste to produce a playable
game. You learn how to use the language, and then you look up
functions that accomplish actions that you want your game to
feature. Until you learn how the language works, it's difficult to
blindly stumble your way into a working game world.
Luckily, you can learn the language as you build the start of a game.
Along the way, you pick up some tricks that you can reuse later to make
your game work the way you want it to work.
To begin with, you need to know the three main functions in the core
LÖVE engine:
**function love.load()** is the function that gets triggered when you
launch a LÖVE game (it's called the "init" or "void setup()" function
in other languages). It's used to set up the groundwork for your game
world. It only gets executed once, so the only thing it contains is
code that persists throughout your game. It's, more or less, the
equivalent of the **When Green Flag is Clicked** block and the
**Stage** script area in Scratch.
**function love.update(dt)** is the function that gets constantly
updated during game play. Anything in a love.update(dt) function is
refreshed as the game is played. If you've played any game, whether
it's Five Nights at Freddy's or Skyrim or anything else, and you've
monitored your framerate (fps), everything happening as the frames
tick would be in an update loop (not literally, since those games
weren't in LÖVE, but something like an update function).
**function love.draw()** is a function that causes the engine to
instantiate the graphical components that you created in the
**love.load()** function of your game. You can load a sprite or create
a mountain, but if you don't draw it then it never shows up in your
game.
Those are the three functions that serve as the foundation for any
element you create in LÖVE. You can create new functions of your own,
too. First, let's explore the LÖVE framework.
## Sprites
The basics of the "Hello, World" is a good starting point. The same
basic three functions still serve as the basic skeleton of the
application. Unlike rendering simple text on the screen, though, this
time we create a sprite using a graphic from
[openclipart.org](
https://openclipart.org).
The code examples and assets for this example are available in a [git
repository](
https://notabug.org/seth/lessons_love2d). Clone it for
yourself if you want to follow along:
git clone
https://notabug.org/seth/lessons_love2d.git
Most objects in LÖVE are stored in arrays. An array is a little like a
list in Scratch; it's a bunch of characteristics placed into a common
container. Generally, when you create a sprite in LÖVE, you create an
array to hold attributes about the sprite, and then list the
attributes in `love.load` that you want the object to have.
In the `love.draw` function, the sprite gets drawn to the screen.
fish = {}
cwide = 520
chigh = 333
love.window.setMode(cwide, chigh)
love.window.setTitle(' Collide ')
function love.load()
fish.x = 0
fish.y = 0
fish.img = love.graphics.newImage('images/fish.png')
end
function love.update(dt)
-- this is a comment
end
function love.draw()
love.graphics.draw(fish.img, fish.x, fish.y, 0, 1, 1, 0, 0)
end
The fish's natural predator is one of the most vicious creatures of
the antarctic: the penguin. Create a penguin sprite in the same way as
the fish sprite was created, with the addition of a `player.speed`,
which is how many pixels the penguin will move once we get around to
setting up player controls.
fish = {}
player= {}
cwide = 520
chigh = 333
love.window.setMode(cwide, chigh)
love.window.setTitle(' Collide ')
function love.load()
fish.x = 0
fish.y = 0
fish.img = love.graphics.newImage('images/fish.png')
player.x = 100
player.y = 100
player.img = love.graphics.newImage('images/tux.png')
player.speed = 10
end
function love.update(dt)
-- this is a comment
end
function love.draw()
love.graphics.draw(fish.img, fish.x, fish.y, 0,1,1,0, 0)
love.graphics.draw(player.img,player.x,player.y,0,1,1,0, 0)
end
To keep things tidy, place the `png` files in an `images`
directory. To test the game as it is so far, zip up the `main.lua` and
`png` files.
$ zip game.zip main.lua -r images
$ mv game.zip game.love
$ love ./game.love
![Our current cast of characters.](love_sprites.png)
## Movement
There are several ways to implement movement in LÖVE, including
functions for mouse, joystick, and keyboard. We've already established
variables for the player's `x` and `y` positions, and for how many
pixels the player moves, so use an if/then statement to detect key presses, and then redefine the variable holding the position of the player sprite using simple maths:
player = {}
fish = {}
cwide = 520
chigh = 333
love.window.setMode(cwide, chigh)
love.window.setTitle(' Collide ')
function love.load()
fish.x = 0
fish.y = 0
fish.img = love.graphics.newImage( 'images/fish.png' )
player.x = 100
player.y = 100
player.img = love.graphics.newImage('images/tux.png')
player.speed = 10
end
function love.update(dt)
if love.keyboard.isDown("right") then
player.x = player.x+player.speed
elseif love.keyboard.isDown("left") then
player.x = player.x-player.speed
elseif love.keyboard.isDown("up") then
player.y = player.y-player.speed
elseif love.keyboard.isDown("down") then
player.y = player.y+player.speed
end
end
function love.draw()
love.graphics.draw(player.img,player.x,player.y,0,1,1,0, 0)
love.graphics.draw(fish.img, fish.x, fish.y, 0, 1, 1, 0, 0)
end
Test the code to confirm that movement works as expected. Remember to
re-zip all the files before testing so that you don't accidentally
test the old version of the game.
To make the movement make a little more sense, we can create functions
to change the direction a sprite is facing depending on what key is
pressed.
This is the equivalent of these kinds of Scratch code blocks:
![Looking and moving in Scratch](scratch-right.png)
Generate a flipped version of the player sprite using Image Magick:
$ convert tux.png -flop tuxleft.png
And then write a function to swap out the image. You need two functions: one to swap out the image, and another to restore it to the original:
function rotate_left()
player.img = love.graphics.newImage('images/tuxleft.png')
end
function rotate_right()
player.img = love.graphics.newImage('images/tux.png' )
end
Use these functions where appropriate:
function love.update(dt)
if love.keyboard.isDown("right") then
player.x = player.x+player.speed
rotate_right()
elseif love.keyboard.isDown("left") then
player.x = player.x-player.speed
rotate_left()
elseif love.keyboard.isDown("up") then
player.y = player.y-player.speed
elseif love.keyboard.isDown("down") then
player.y = player.y+player.speed
end
end
## Automated movement
Using the same convention of the sprite's `x` value, and a little
mathematics, you can make the fish automatically move across the
screen from edge to edge.
This is the equivalent of doing this in Scratch:
![Automated movement in Scratch](scratch-edge.png)
There is no `if on edge, bounce` in LÓVE, but by checking whether the
fish's `x` value has reached the far left of the screen, which is `0`
pixels, or the far right, which is the same as the width of the canvas
(the `cwide` variable), you can determine whether the sprite has gone
off the edge or not.
If the fish is at the far left, then it has reached the edge of the
screen, so you increment the fish's position forcing it to move
right. If the fish is at the far right, then decrement the fish's
position, making it move left.
function automove(obj,x,y,ox,oy)
if obj.x == cwide then
local edgeright = 0
elseif obj.x == 0 then
local edgeright = 1
end
if edgeright == 1 then
obj.x = obj.x + x
else
obj.x = obj.x - x
end
end
There's a sequence of events that happens to be important in this
case. In the first `if` block, you check for the value of `x` and
assign a temporary (local) variable to indicate where the fish needs
to go next. Then in a second, separate `if` block, you move the
fish. For bonus points, try doing it all in one `if` statement and see
if you can understand why it fails. For more bonus points, see if you
can figure out a different way of moving the fish.
To implement the fish's movement function, call the function at the
bottom of the `love.update` loop:
automove(fish,1,0,fish.img:getWidth(),fish.img:getHeight() )
If you test the script, you'll notice that the fish goes all the way
off the screen when it hits the right edge. It's doing this because a
sprite's `x` value is based on its upper left pixel. I'll leave it as
an exercise for you to figure out what variable you should subtract
from `cwide` when checking for the fish's position.
## Collision detection
Video games are all about collisions. It's when things bump into each
other, whether those things are an unfortunate hero taking a dive into
a lava pit, or a bad guy getting blasted with a mana bolt, stuff is
supposed to happen.
Before detecting a collision, let's decide what we want to have happen
when a collision happens. Since you already know how to change a
sprite's appearance, we'll create two functions: one to change the
fish to just the bones left over after the penguin has caught it, and
the other to change the fish back to life any time the penguin isn't
around.
function falive()
fish.img = love.graphics.newImage('images/fish.png')
end
function fdead()
fish.img = love.graphics.newImage('images/fishbones.png')
end
With these functions set up, it's time to calculate collisions.
In Scratch, there are code blocks to check whether two sprites are
touching.
![Scratch collision](scratch-collision.png)
The same concept, in principle, applies in LÖVE. There are
several different ways to detect collisions, including external
libraries like [HC](
https://github.com/vrld/HC) and `love.physics`,
but a good compromise between the two is a custom function to detect
an overlap in sprite boundaries.
function CheckCollision(x1,y1,w1,h1, x2,y2,w2,h2)
return x1 < x2+w2 and
x2 < x1+w1 and
y1 < y2+h2 and
y2 < y1+h1
end
The math is complex, but it makes sense if you take a moment to think
about it. The goal is detect whether two images are overlapping. If
the images are overlapping, they can be said to have collided. Here's
an illustration of two boxes *not* overlapping, with just some sample
`y` values to keep things simple:
![Two boxes](overlap_no.png)
Take a line from the function and crunch the numbers:
y2 < y1 + h1
110 < 0 + 100
That's obviously a false statement, so the function must return `false`. In other words, the boxes have not collided.
Now look at the same logic with two overlapping boxes:
![Overlapping boxes](overlap.png)
y2 < y1 + h1
50 < 0 + 100
This one is clearly true. Assuming all statements in the function are
also true (and they would be, had I bothered adding `x` values), then
the function returns `true`.
To leverage the collision check, just evaluate it with an `if`
statement. You know now that if the `CheckCollision` function returns
`true`, then there is a collision. The `CheckCollision` function has
been written very generically, so when calling it, you need to feed it
the appropriate values so it knows which object is which.
Most of the values are intuitive. You need LÖVE to use the `x` and `y`
positions of each object being checked for collision, and the size of
the object. The only values that are special in this case is the one
that gets swapped out depending on the collision state. For those,
hard code the size of the dead fish rather than the live fish, or else
the state of the collision will get changed in the middle of the
collision detection. In fact, if you want to see it glitch, you can do
it the wrong way first:
if CheckCollision(fish.x,fish.y,fish.img:getWidth(),fish.img:getHeight(), player.x,player.y,player.img:getWidth(),player.img:getHeight() ) then
fdead()
else
falive()
end
The right way is to get the size of the smaller sprite image. You can do this with Image Magick:
$ identify images/fishbones.png
images/fishbones.png PNG 150x61 [...]
Then hard coded the "hot spot" with the appropriate dimensions:
if CheckCollision(fish.x,fish.y,150,61, player.x,player.y,player.img:getWidth(),player.img:getHeight() ) then
fdead()
else
falive()
end
The side effect of hard coding the collision detection area is that
when the penguin touches just the edge of the fish, the fish doesn't
get gobbled up. For more precise collision detection, explore the
[HC](
https://github.com/vrld/HC) library or `love.physics`.
## Final code
It's not much of a game, but it demonstrates the important elements of
video games:
player = {}
fish = {}
cwide = 520
chigh = 333
love.window.setTitle(' Hello Game Wörld ')
love.window.setMode(cwide, chigh)
function love.load()
fish.x = 0
fish.y = 0
fish.img = love.graphics.newImage( 'images/fish.png' )
player.x = 150
player.y = 150
player.img = love.graphics.newImage('images/tux.png')
player.speed = 10
end
function love.update(dt)
if love.keyboard.isDown("right") then
player.x = player.x+player.speed
elseif love.keyboard.isDown("left") then
player.x = player.x-player.speed
elseif love.keyboard.isDown("up") then
player.y = player.y-player.speed
elseif love.keyboard.isDown("down") then
player.y = player.y+player.speed
end
if CheckCollision(fish.x,fish.y,151,61, player.x,player.y,player.img:getWidth(),player.img:getHeight() ) then
fdead()
else
falive()
end
automove(fish,1,0,fish.wide,fish.high)
end
function love.draw()
love.graphics.draw(player.img,player.x,player.y,0,1,1,0, 0)
love.graphics.draw(fish.img, fish.x, fish.y, 0, 1, 1, 0, 0)
end
function automove(obj,x,y,ox,oy)
if obj.x == cwide-fish.img:getWidth() then
edgeright = 0
elseif obj.x == 0 then
edgeright = 1
end
if edgeright == 1 then
obj.x = obj.x + x
else
obj.x = obj.x - x
end
end
function CheckCollision(x1,y1,w1,h1, x2,y2,w2,h2)
return x1 < x2+w2 and
x2 < x1+w1 and
y1 < y2+h2 and
y2 < y1+h1
end
function rotate_left()
player.img = love.graphics.newImage('images/tuxleft.png')
end
function rotate_right()
player.img = love.graphics.newImage('images/tux.png' )
end
function falive()
fish.img = love.graphics.newImage('images/fish.png')
end
function fdead()
fish.img = love.graphics.newImage('images/fishbones.png')
end
From here you can use the principles you've learned to
create more exciting work. Collisions are the basis for most
interactions in video games, whether it's to trigger a conversation
with an NPC, manage combat, pick up items, set off traps, or most
anything else, so if you master that, the rest is repetition and elbow
grease.
So go and make a video game! Share it with friends, play it on mobile,
and, as always, keep leveling up.