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


Tuesday, June 10, 2014

Getting Started with Lua

Hello again,

Warning: there is an update of the blog itself on the next paragraphs. You may skip to the Lua section below if you're not interested.

This blog has been inactive for quite some time as I took some time away from my game development hobby. Good news is: something happened that made me regret this immensely, so I am back. Bad news is: I must focus on my main personal project amap, period in which I'll leave our C++ game unattended given the giant amount of work it would be (as I chose to code the engine from SDL up).

I don't plan on abandoning this though, as I owe this same thing that brought me back to game development so quickly to this very blog. Creating a quality C++ framework that is flexible yet easy to use is something that is sure to take months of work. But I can't spare all this time right now nor want to take the easy route and create lower quality stuff as I wouldn't learn anything from it, in contrast to creating a quality one with which I, myself, would learn a lot by doing. So I'll change this blog's direction for the time being and retake its old course some months from now.

For now, I'll change my focus into getting results faster. I'll be using Löve2D for at least the next weeks, trying to get a hold of how it all works. It is a Lua framework, but my choice has nothing to do with the language, but with the framework itself. It has one of the easiest interfaces I've seen and, if necessary, I can always step into C and do what's necessary, although I don't believe it would really be a necessity.

Lua


It is a scripting language very popular on the game development world, possibly the most popular. The language itself is too big to fit in a blog post, and I am not proficient enough to give a good overview, but I'll still give you guys some short snippets to show the basics of the language.

HelloWorld.lua

print("Hello World!") --Self Explanatory
This print command would output the line to the console, as simple as that. To run this lua script, just type issue the console command [ lua path/HelloWorld.lua ] and it should output the message.

Lua doesn't support the most common syntaxes for most things, what can be a pain sometimes, but you'll soon get the hang of how it works. In the next example, I'll create a Lua array (table with numeric indexes) with two strings in it, and then print them.

Hell-o World.lua

myTable = { --Creates a Table
    "Hell",     --  First Entry
    "o World!"  --  Second Entry
}

print(myTable[1]..myTable[2])-- these .. concatenates strings
myTable here is an 'array' with two entries, that we can access by their numeric values (since it is not a map). The .. operator concatenates strings, really useful.

Did you notice that Lua's array indexes actually start with 1? Some people consider this the best thing about Lua, personally I don't like it as I already had to write workarounds for this when interfacing with C. Doesn't make much difference in the end though.

Metamorphosis.lua

print (myVar)   --myVar doesn't exist yet
                --Output:   nil

myVar = 1       --Creates myVar of number type (double)
print (myVar)   --Output:   1

myVar = {1,3,5} --Changes myVar into an array
print (myVar)   --Output:   table:0x1ed8de0

myVar = myVar[1]..myVar[2]..myVar[3] --Treats numbers as strings
print (myVar)   --Output 135
The output of this script is as follows:
[dejaime@manjaro BuildAndGun]$ lua Metamorphosis.lua
nil
1
table: 0x1e65d90
135

If you try to use a variable that doesn't exist, it will be treated as nil. nil in Lua is a special type of data that means "no data". It is treated as false when used in conditionals.

Lua uses double for all its numeric operations. If you want to change this behavior, you can do so by editing the Lua configuration file. More details on this can be found here.

Notice how a table is printed. First, Lua warns you that it is printing a table, and then it spits its memory address. This happens because it doesn't know exactly how to print a table, but, in case you want Lua to print your table in a given manner, you can use what's called a metatable. You can think of this as overloading some behaviors, but it is not exactly what is happening. If you wish to know more, take a look here.

MetaTable.lua

myTable = {
    "This ",
    "Is ",
    "Awesome!"
}
print(myTable) -- Outputs as usual: "table: 0x1f34abc"

myMetaTable = {
    __tostring = function (table)
        local resultingString = "" -- an empty string
        for i = 1, #table do -- # means total entries
            resultingString = resultingString..(tostring(table[i]))
        end
        return resultingString
    end
}

setmetatable(myTable, myMetaTable) -- sets the behavior for myTable

print(myTable) --Output: "This Is Awesome!"
As you can see, metatables must be explicitly set. In here, we used __tostring, but one can also overwrite several behaviors such as addition (__add), equality (__eq), userdata garbage collection (__gc, not for tables).

That # operator (#table) returns the total number of entries in a given array. 

Other notable thing about this snippet is the word local, right before resultingString. In Lua, everything is defaulted as global, but sometimes you'll want to set variables as local as they can be >3x faster to operate with (as in my benchmarks).

GlobalLocal.lua

GlobalPI = 3.14159

local FileLocalPI = 3.14159

function benchmark ()
    local FunctionLocalPI = 3.14159

    local total_operations = 10^8
    local trashVar

    local initial_time = os.time()

    for i=1,total_operations do
        GlobalPI = i*GlobalPI
        GlobalPI = i^GlobalPI
        GlobalPI = i/GlobalPI
        GlobalPI = i+GlobalPI
        GlobalPI = i-GlobalPI
    end
    
    print("Global: "..os.time() - initial_time.."s")
    initial_time = os.time()

    for i=1,total_operations do
        FileLocalPI = i*FileLocalPI
        FileLocalPI = i^FileLocalPI
        FileLocalPI = i/FileLocalPI
        FileLocalPI = i+FileLocalPI
        FileLocalPI = i-FileLocalPI
    end

    print("File level local: "..os.time() - initial_time.."s")
    initial_time = os.time()

    for i=1,total_operations do
        FunctionLocalPI = i*FunctionLocalPI
        FunctionLocalPI = i^FunctionLocalPI
        FunctionLocalPI = i/FunctionLocalPI
        FunctionLocalPI = i+FunctionLocalPI
        FunctionLocalPI = i-FunctionLocalPI
    end


    print("Function level local: "..os.time() - initial_time.."s")
end

benchmark()
The output I had for this script on my personal machine was the following:
[dejaime@manjaro BuildAndGun]$ lua GlobalLocal.lua
Global: 27s
File level local: 15s
Function level local: 6s
[dejaime@manjaro BuildAndGun]$ lua GlobalLocal.lua
Global: 29s
File level local: 15s
Function level local: 7s
As you can see, global variables are heavier than file-level local variables which are, in turn, heavier than function-level local variables. This is extremely situational though, but sometimes it is worth it copying a global into a local variable if you're going to perform too many operations with it. At least make the test wherever you think globals are holding you back and see if there is any performance gain. I guess this is enough to illustrate the importance of the local keyword in Lua. Keep this in mind when designing performance sensitive code.

Functions.lua

function GlobalPrintAll (tbl)
    for key,value in pairs(tbl) do
        print(key, value)
    end
end


myTable = {
    marco = "polo",
    humpty = "dumpty",
    hot = "dog",
    PrintAll = GlobalPrintAll
}

print("\nExplicit Argument:")
myTable.PrintAll(myTable)

print("\nImplicit Argument:")
myTable:PrintAll()
Output:
[dejaime@manjaro BuildAndGun]$ lua Functions.lua

Explicit Argument:
PrintAll    function: 0x746b20
hot dog
marco   polo
humpty  dumpty

Implicit Argument:
PrintAll    function: 0x746b20
hot dog
marco   polo
humpty  dumpty



Here, you can see that Lua can hold functions as variables. That is why so many Lua frameworks rely on callbacks, it is simply convenient to use callbacks in Lua.

Other important thing, especially when you guys go around hunting for examples in Lua, is the difference between . and : .
When using period, the function is called as one would expect. But the colon is actually OO eye candy, it passes the table itself to the function when calling it. In other words, table:function(p1, p2) is the same as table.function(table, p1, p2) .

ColonPeriod.lua

function GlobalFunction(printme, metoo)
    print(printme, metoo)
end

myTable = {}
myTable["l"] = "Yolo"
myTable["d"] = "Yodo"
myTable["Function"] = GlobalFunction

GlobalFunction(myTable["l"], myTable["d"])
myTable.Function(myTable["l"], myTable["d"])
myTable:Function(myTable["l"], myTable["d"])

Output:
[dejaime@manjaro Desktop]$ lua HelloWorld.lua
Yolo    Yodo
Yolo    Yodo
table: 0x20d9220    Yolo
Did you notice that the last call had its parameters displaced due to the myTable variable being passed along in the first slot? You need to be careful with this kind of thing, since in Lua there is no type checking, finding a problem where you used a colon instead of a period can be quite frustrating. The key is to be consistent and try to use one of the two whenever possible. It is up to personal preference though.


Lua is a really flexible language, and I certainly didn't do it justice with these few snippets. But I hope this was a good introduction nonetheless, and maybe sparked some interest. I'll be using it in the next posts while using Löve2D.

That'll be it for today,
Over and Out.