Wednesday, June 18, 2014

Introduction to Löve2D

Hello Again,

Today I'm going to talk about Löve2D, an easy to use Lua game development framework. It is licensed under the zlib license, so there is nothing to worry about its license.

The biggest advantage of Löve2D is that you can get things going really fast with it, it is easy to learn and Lua is extremely flexible. This environment requires less planning than my usual C++/SDL, but you still need to plan a bit ahead. The counterpart is that we have breaking changes in every major version, but no backwards compatibility. So, a game made with Löve2D 0.8 probably won't work in 0.9.1, what I consider a major issue. You should always distribute your game with the binary part included to avoid this problem (more info on this here).

Keep in mind that the usual structure of a Löve2D game leaves your source code accessible, so it is not ideal for commercial projects where you wouldn't want to publish your source code. Still, this makes games made with it very extensible, especially if you release the source code as a .love file. Modding the game is as easy as adding some extra Lua files and editing a line or two in the existing ones.


Hello World!

All the code in this post is released under the MIT License.

The Löve2D framework is partially based on callbacks. These are the main ones:

  • love.load() - A function called in the initialization, runs only once
  • love.update(dt) - Called every frame, dt is the elapsed time since last call
  • love.draw() - Called on the draw step, this is where your render logic comes
  • love.mousepressed(x, y, button) - called when a mouse button is pressed
  • love.mousereleased(x, y, button) - as above, when released
  • love.keypressed(key) - Called when a key is pressed on the keyboard
  • love.keyreleased(key) - as above, when released
  • love.focus(f) - Called when the game loses/gains focus

Löve2D Hello World:

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

Löve2D requires that you have a file named main.lua. It is the core file for your game, and will always be executed by Löve2D. You should include in this file all the callbacks you want from the list above (they are optional though). In this example main.lua file I only overwrite the love.draw function and all this does is print "Hello World!" at the window origin (top-left corner) in white text and black background. This function is called every frame, so remember it is actually being written over and over again.

The rendering usually clocks at 60Hz and you have an easy way to access it. The love.timer.getFPS() function returns the FPS, straight and easy.

x = love.window.getWidth()/2
y = love.window.getHeight()/2

function love.draw ()
    love.graphics.print ("FPS: "..love.timer.getFPS(), x, y)
end

This would print the FPS continuously with the text originating at the middle of the window.

Let's step up a little now and use some more callbacks.

Löve2D Draw On Click:

function love.load()
    Lena = love.graphics.newImage("Lena-small.png")
    
    runningTime = 0

end

function love.update (dt)
    runningTime = runningTime + dt
end

function love.draw ()
    love.graphics.draw(Lena, drawX, drawY)
    love.graphics.print("Total Running Time: "..runningTime.."s", 10, 10)
    love.graphics.print("FPS: "..love.timer.getFPS(), 10, 40)
    
end

function love.mousepressed (x, y, button)
    drawX = x
    drawY = y
end

This main.lua file loads an image called Lena-small.png and draws it wherever we click with the mouse. It also keeps track of the running time (with update() to exemplify). You can use whatever image you want in here, this is just an example.

In here, we are using four different callbacks.
The load() function is called first thing when our game is loaded by Löve2D, and never touched again. The update(dt) function is where we should update our simulation using the elapsed time -dt-. Similarly, mousepressed() is called whenever a button of the mouse is pressed, any button, or the scroll changes its position.

As you can see, the module love.graphics is responsible for anything related to rendering. So, if you are going to load, render, manipulate or do anything with graphics, you'll touch the love.graphics module.

Did you notice that I called love.draw while drawX and drawY were still nil? These variables are only created on the first call to the love.mousepressed callback. Löve2D usually has default values, (0,0) in this case.

Quick Questions:
  1. What happens if we add the keyword local before drawY = y in the love.mousepressed function?
  2. As above, but with drawX? (You can find the love.graphics.draw docs here)
Answers:
  1. The image would move only horizontally, as the drawY is a local variable inside the mouse callback inaccessible globally. This would make drawY nil, so the drawing function receive only drawX love.graphics.draw(Lena, drawX).
  2. The script would crash on the first click as drawY ceases to be nil and turns into a number. The draw function would assume a form that expects an image, a quad (more on that soon) and the x and y coordinates as the first 4 parameters but, as drawX was nil, the quad is invalid and the script crashes. The drawY parameter would actually be interpreted as the x coordinate, in this case.

Wrapping Up

What have we learnt here?
  1. Löve2D has multiple callback functions that you can overload to create your game.
  2. The most important callbacks are love.update, love.draw and the input ones. These are probably the bare minimum for a game.
  3. Most functions have default values for when you don't want to pass on something (works pretty much the same way as in C++).
  4. Variable scopes can get messy, plan ahead. This will become especially visible when we start to move up into more complex stuff.

What's Next?

In the next post, we'll see how to draw an animated sprite with Löve2D, how to serialize it, initialize it and render the correct rectangles from the spritesheet. To do so we should go through some basics of data serialization, one of the strongest points of Lua.

Before going any further, make sure you run this code in your own machine and mess around a bit with it. Try making the image move around with the arrow keys (the key codes are simply 'up,' 'left,' 'right' and 'down'), not only with the mouse. A possible implementation can be found below.

Over and Out.

Moving Image:
--[[
    main.lua - 2014
    
    Copyright Dejaime Antônio de Oliveira Neto, 2014
    
    Released under the MIT license.
    Visit for more information:
    http://opensource.org/licenses/MIT
]]

function love.load()
    Lena = love.graphics.newImage("Lena-small.png")
    
    runningTime = 0
    moveX = 0
    moveY = 0
    drawX = 0
    drawY = 0
end

function love.update (dt)
    runningTime = runningTime + dt
    drawX = drawX + moveX
    drawY = drawY + moveY
end

function love.draw ()
    love.graphics.draw(Lena, drawX, drawY)
    --love.graphics.print(drawX.."X and "..drawY.."Y")
    love.graphics.print("Total Running Time: "..runningTime.."s", 10, 10)
    love.graphics.print("FPS: "..love.timer.getFPS(), 10, 40)
    
end

function love.mousepressed (x, y, button)
    drawX = x
    drawY = y
end


local speed = 5

function love.keypressed (k)
    if k == 'up' then
        moveY = moveY - speed
    elseif k == 'down' then
        moveY = moveY + speed
    elseif k == 'right' then
        moveX = moveX + speed
    elseif k == 'left' then
        moveX = moveX - speed
    elseif k == 'escape' then
        love.event.quit()
    end
end

function love.keyreleased (k)
    if k == 'up' then
        moveY = moveY + speed
    elseif k == 'down' then
        moveY = moveY - speed
    elseif k == 'right' then
        moveX = moveX - speed
    elseif k == 'left' then
        moveX = moveX + speed
    end
end