Experimenting with LOVE2D: Part 1

August 2020 ยท 5 minute read

Recently, I found an urge to make a game. I’m not going to talk about the story/world building stuff here for now, just some of the technical aspects.

I have zero experience writing any type of game software, so I expect this to be a project that takes forever. It is entirely possible that it never actually becomes a reality, but for the time being I am actually having fun learning the basics.

I am interested in building the mechanics of the game from scratch, but am not interested in attempting to build an engine on my own. I know I need to start small and use an engine that works for what I’m going for, but doesn’t require a team to get started.

Finding LOVE

There were a lot of options, but ultimately I decided to build with LOVE. It checks a lot of boxes for me:

I’m a big fan of Lua and use it for lots of small things, so it being the language to write in made me quite happy. As LOVE supports FFI, I can drop in to other libraries as needed.

The Boring Parts

I mainly write software that runs on servers and have only written desktop GUIs in a very limited fashion. I’ve written a handful of JS, HTML and CSS, but the browser handles all of that drawing/painting/etc for you.

Now I have to figure all of that out on my own.

While I would love (no pun intended) to dig in to this here, I want to share some of the boring stuff I’ve been working on: packaging and testing.

Packaging Assets

LOVE has documentation on distribution - ie how to package your game up using .love files (zip) and subsequently how to include the engine along with it. This allows you to package up the code as well as assets in one go.

I imagine that updates to a small game are perfectly fine to be distributed in this fashion, but I wanted to try something a bit different as far as the assets are concerned: custom packaging.

What I came up with was fairly simple: tiny packed files.

struct Package {
	u16 PatchID
	u32 Count
	Entity [Count]Entities
}

struct Entity {
	u32 Hash
	u32 Size
	byte [Size]Content
}

I wrote a simple packer in Go that takes a directory and handles asset packaging from there.

On the LOVE side, I implemented a package set, loader, and entity types.

Package:

Set:

The hope here is that subsequent updates to assets or code contained within the packages can be dropped in by just downloading a new package file, rather than an entire .love and executable.

100% overkill for a game that doesn’t even have an alpha build, but it’s a pet project :)

Testing

As previously mentioned, I have zero experiencing building games. I do, however, have experience writing software in general - and I hate manually testing things that could instead be automated.

As I write code and explore the LOVE APIs, I have written a fair amount of tests on the Lua side of things. For example, the set/package/entity idea above each have their own tests.

Unfortunately, I don’t quite have my local Lua install and LOVE’s version in sync. Lua, for example, doesn’t have a way to scan a directory for files without something like luafs, and I have not been successful in figuring out how to include these kinds of packages with LOVE itself. I may learn how to do that in the future, but I didn’t want to focus on it too much and would rather build the game. I worked around this by using popen and find on *nix and dir on Windows.

luaunit is a pure-Lua, easy to use test setup that aligns with what I’m used to, so I plugged that in and wrote tests for the non-LOVE reliant code. The problem was that anything using the LOVE APIs directly couldn’t be tested in an automated fashion.

I decided to hack around this by running tests inside of LOVE itself.

Inside of main.lua, I came up with:

function love.load()
    ...

    if os.getenv("_RUN_LOVE_TESTS") == "Y" then
        local suite = require('tests.tests')
        local pprint = require('thirdparty.pprint')

        -- handle results from suite run, writing suite.output to a file,
        -- and showing a message box that indicates success or failure
        os.exit( ... )
    end
end

tests/tests.lua is a bit ugly, but it does the trick:

local lu = require('thirdparty.luaunit')

local is_in_love = os.getenv("_RUN_LOVE_TESTS") == "Y"
local love_only = {
	['test_pkg'] = true,
	['test_set'] = true,
	...
}

... file discovery code using popen for running tests outside of love

if not is_in_love then
    for i, v in ipairs(files) do
        if love_only[v] then
            print('skipping ' .. v .. ': requires LOVE')
            files[i] = nil
        end
    end
end

for i = 1, #files do
    local file = string.sub(files[i], 1, #files[i] - 4)
    if file ~= "tests" then
        require('tests.' .. file)
    end
end

if is_in_love then
    local suite = lu.LuaUnit.new()
    suite:runSuite()
    return suite
else
    os.exit(lu.LuaUnit.run())
end

With all of that set up, I can run both lua tests/tests.lua -v from the terminal as well as run _RUN_LOVE_TESTS=Y love src. The non-pure-lua tests will be executed within an actual instance of LOVE, allowing full access to any APIs needed, and a log will be written so I can inspect failures.

End.jpeg

That’s it for now. I expect to write about LOVE again as I work on this, but don’t have any particular timeline envisioned. I’m still learning the ropes in LOVE itself - as well as building sprites, maps, etc.

Hopefully there will be more to share in the future!