Extending LOVE2D

Posted on Feb 22, 2022

Why

If you are making a game that you want to publish, it is very likely that you will be required to implement an SDK that is not part of love’s standard library. Example of such libraries are Steamworks and EOS (Epic Online Services). While none of them is required in order to publish your game on their respective platforms, they both provide loads of useful functionality that you can take advantage of in your game. They both provide SDKs implemented in the C language, making it an easy task to wrap it with some glue code and make it a usable module in your Lua Love game code. Here is a step-by-step guide on how to do it.

Disclaimer

This has only been tested on a 64bit Windows 10 machine, I have no experience on how these steps work on a MacOS or Linux operating systems.

How

  1. Pull megasource building tool source code from here.
  2. Pull love2d source code into megasource’s libs directory from here using the following command: git clone https://github.com/love2d/love libs/love.
  3. Change the directory to libs/love/src/modules and create a new directory with the name of a module you want to create.

In this example I’ll be creating module named misc, so replace it everywhere you see it with a module name of your choice. New directory, in my case, is src/modules/misc.

  1. Open CMakeLists.txt file belonging to love2d library (lib/love/CMakeLists.txt).
  2. Somewhere between modules that are already defined, add a new variable and assign all the source files from your module.
set(LOVE_SRC_MODULE_MISC
	src/modules/misc/Misc.cpp
	src/modules/misc/Misc.h
	src/modules/misc/wrap_Misc.cpp
	src/modules/misc/wrap_Misc.h)

source_group("modules\\misc" FILES ${LOVE_SRC_MODULE_MISC}

Don’t worry about source files at the moment, we will create them later.

  1. Scroll down to the LOVE_LIB_SRC variable and insert your module’s sources there.
set(LOVE_LIB_SRC
	${LOVE_SRC_COMMON}
	# Modules
    .
    .
    .
    ${LOVE_SRC_MODULE_MISC}
)
  1. Save and close CMakeLists.txt file.
  2. Open src/common/Module.h header file and find an enum with the name ModuleType.
  3. Put M_MISC enum entry right before M_MAX_ENUM.
  4. Save and close Module.h file.
  5. Open src/common/config.h header file.
  6. Find defines for all the modules (should be right below Autotools config.h comment) and add a new define somewhere among others
#	define LOVE_ENABLE_FONT
#	define LOVE_ENABLE_GRAPHICS
    .
    .
    .
#   define LOVE_ENABLE_MISC
  1. Save and close the file.
  2. Open src/modules/love/love.cpp source file.
  3. Find defines for enabling other modules and put a new entry for your module somewhere in between.
#ifdef LOVE_ENABLE_MISC
#	include "misc/Misc.h"
#endif

Again, this file doesn’t exist yet, but this is fine, we will get to that later.

  1. Scroll down a bit within the same file where there are declarations of luaopen functions and add an entry for your own module.
#if defined(LOVE_ENABLE_MISC)
	extern int luaopen_love_misc(lua_State*);
#endif
  1. Scroll even further to find a modules variable definition with an array full of all the modules and add an entry again.
#if defined(LOVE_ENABLE_MISC)
	{ "love.misc", luaopen_love_misc },
#endif
  1. Finally, time to create source code for our module. Go to your module directory and create 4 files:
  • Misc.h
  • Misc.cpp
  • wrap_Misc.h
  • wrap_Misc.cpp
  1. Inside your Misc.h file, create a class that inherits from Module class and override these two functions:
  • ModuleType getModuleType() const - this function must return module enum type we created in step 9.
  • const char* getName() const- this must return a name for your module, in my case love.misc.
  1. Declare functions you would like to implement as part of your module, in my case its double customAdd(double a, double b).

Full code for the Misc class header file:

#ifndef LOVE_MISC_H
#define LOVE_MISC_H

#include "common/Module.h"

namespace love
{
namespace misc
{
class Misc : public Module
{
public:
    static love::Type type;

    ModuleType getModuleType() const override { return M_MISC; }
    const char* getName() const override
    {
        return "love.misc";
    }

    double customAdd(double a, double b);
};   
}
}

#endif
  1. Inside your Misc.cpp file implement functions you declared for your module and define a love::Type type static variable.

Full code for the Misc class source file:

#include "Misc.h"

namespace love
{
namespace misc
{
love::Type Misc::type("misc", &Module::type);

double Misc::customAdd(double a, double b)
{
    return a + b;
}
}
}
  1. Inside your wrap_Misc.h file declare an extern function that we used in a step 16 and 17.
#ifndef LOVE_WRAP_MISC_H
#define LOVE_WRAP_MISC_H

#include "common/runtime.h"

namespace love
{
namespace misc
{
extern "C" LOVE_EXPORT int luaopen_love_misc(lua_State *L);
}
}

#endif

Make sure you include common/runtime.h header. Oterwise you will receive error C2065: 'lua_State': undeclared identifier error during compilation. 23. Finally, inside wrap_Misc.cpp file define Lua wrappers for all your functions implemented in Misc class.

int w_customAdd(lua_State* L)
{
    double a = lua_tonumber(L, 1);
	double b = lua_tonumber(L, 2);
	double c = instance()->customAdd(a, b);
	lua_pushnumber(L, c);
	return 1;
}
  1. Add all functions to an array together with their names.
// List of functions to wrap.
static const luaL_Reg functions[] =
{
	{ "customAdd", w_customAdd },

	{ 0, 0 }
};
  1. At the end, implement the extern function we decleared in a header file effectively registering our module.
extern "C" int luaopen_love_misc(lua_State* L)
{
    Misc* instance = instance();
    if(instance == nullptr)
    {
        luax_catchexcept(L, [&]() { instance = new love::misc::Misc(); });
    }
    else
    {
        instance()->retain();
    }

    WrappedModule w;
	w.module = instance;
	w.name = "misc";
	w.type = &Misc::type;
	w.functions = functions;
	w.types = 0;

	int n = luax_register_module(L, w);
	return n;
}

Full code for wrap_Misc.cpp source file:

#include "wrap_Misc.h"
#include "Misc.h"
#include "common/Module.h"

namespace love
{
namespace misc
{
#define instance() (Module::getInstance<Misc>(Module::M_MISC))

int w_customAdd(lua_State* L)
{
    double a = lua_tonumber(L, 1);
	double b = lua_tonumber(L, 2);
	double c = instance()->customAdd(a, b);
	lua_pushnumber(L, c);
	return 1;
}

// List of functions to wrap.
static const luaL_Reg functions[] =
{
	{ "customAdd", w_customAdd },

	{ 0, 0 }
};

extern "C" int luaopen_love_misc(lua_State* L)
{
    Misc* instance = instance();
    if(instance == nullptr)
    {
        luax_catchexcept(L, [&]() { instance = new love::misc::Misc(); });
    }
    else
    {
        instance()->retain();
    }

    WrappedModule w;
	w.module = instance;
	w.name = "misc";
	w.type = &Misc::type;
	w.functions = functions;
	w.types = 0;

	int n = luax_register_module(L, w);
	return n;
}
}
}
  1. Go to the root directory of the megasource project and call following commands:
  • cmake -G "Visual Studio 16 2019" -A Win32 -H. -Bbuild
  • cmake --build build --target love/love --config Release
  1. Your module is now compiled and accessible from Lua code executed using love executable.
  2. In order to use it you must first require the module by calling require "love.misc".
  3. Use functions from your module like any other function: result = love.misc.customAdd(10, 15).