How to get started with Love2d and Fennel

The minimal love2d fennel template is designed to help get you started making games with Love2D and Fennel.

It’s inspired by Phil Hagelberg’s lisp game jam submission exo-encounter-667. The template makes interactive development / hotloading with love2d and fennel painless (or as painless as possible).

This post will walk you through the process of getting started using the template, and how to go about developing games interactively.

Getting started

The first step is to clone the template.

git clone https://codeberg.org/alexjgriffith/min-love2d-fennel.git

You can then start working directly in this cloned directory, just remove the .git folder and init your own.

mv min-love2d-fennel my-new-game
cd my-new-game
rm -rf .git
git init

The latest (or close to the latest) version of fennel will be included in the cloned repository.

The only thing you need to bring to the table is love2d itself. The template has been tested with love2d versions 11.1 through 11.5. It will likely need to be adapted to work with the 12.X pre-release.

You can download love for your system directly from the love2D webpage, or from https://github.com/love2d/love/releases/tag/11.5.

If you’re on linux you can download v 11.5 using wget and move it somewhere on your path.

LOVE_URL=https://github.com/love2d/love/releases/download/11.5/love-11.5-x86_64.AppImage
wget $LOVE_URL -o love
chmod +x love
mv love /somewhere/on/your/path

If you’re using fennel-ls, you can also setup your flsproject file, following the instructions in the Readme.

Customizing the template

There are a few places in the template you should customize before beginning. In the conf.lua you can set the title and identity to be the name of your project. In the makefile you can set your project’s NAME, your name as an AUTHOR, the DESCRIPTION of the project and other identifying information. If you’re planning to upload your games to itch you can also enter your ITCH_ACCOUNT.

Once you have set these values you’re ready to dive into some gamedev!

Template Structure

There are three key files / modules in the template:

  1. main.lua
  2. wrap.fnl
  3. mode-intro.fnl

Love2D’s entry point is always a file called main.lua found at the root of the game directory. The main.lua module in the template initializes fennel and then calls wrap.fnl.

This template is setup to let you write your game as a series of modes. Like a menu mode, a gameplay mode, a pause mode and endgame mode. In practice each mode is just a lua/fennel module.

In wrap.fnl you’ll find the logic for handling modes, and the love2d callbacks. It wraps functions returned by mode modules with these callbacks. Feel free to wrap more functions as you need. Look at love.update as an example.

mode-intro.fnl is an example of what a mode might look like. It has state that is local to the module, it references state that is stored outside the module, and it returns a table of functions that are called in wrap.fnl.

The REPL

When in your project folder run:

love .

You should see a window pop up with a countdown. When it reaches zero the window will close.

You should also see the prompt >> in your shell. This indicates that the REPL is running within the fennel session. You can now interact with the love2d environment just like you would any other fennel environment.

For example, you can copy the following in your shell to stop that dreadful countdown.

(fn love.update [])

While working directly in the REPL is handy for noodling around with a fennel function or exploring the love2d API, it becomes quite cumbersome when working with large functions / groups of functions, which should be grouped together in modules.

The fennel REPL comes with a bunch of built in commands, one of which is the ,reload command.

Try copying the following into the REPL

,reload wrap

You should see that stressful countdown has started again. We’ve reloaded the wrap module, resetting the love.update function we had overwritten to do nothing previously.

Many editors will let you tie commands directly into the repl. For example fennel-mode in Emacs will let you press C-c k to reload a module in a session started using fennel-repl, bound by default to C-c z.

I recommend reading up on other commands that are built into the fennel REPL.

Working with Modes and Hot Reloading

Modifying love2d callbacks like love.draw and love.update interactively can lead to unrecoverable crashes. To avoid this the templates supports using modes instead of directly overwriting love2d callbacks (outside of wrap.fnl). A mode is just a module that returns callbacks like draw and update. You can switch between modes using set-mode.

The update callback takes delta time (dt) as its first argument and the set-mode function as its second. The draw callback takes no arguments. The details on what is passed to other callbacks can be seen in wrap.fnl.

By default wrap.fnl opens mode-intro as its first mode. You can change the mode that is opened in the love.load function, or you can switch to another in an update call using the set-mode function that is passed in.

An absolutely minimum mode may look like this:

;; mode-game.fnl
(fn draw []
  (love.graphics.print "Example Mode!"))
  
{: draw}

It has no update call, and no other callbacks other than draw. It simply writes “Example Mode!” in the top left corner of the screen. Use set-mode to switch to mode-game, either in load.love or the update function in mode-intro.

;; example change update in mode-intro to:
(fn update [dt set-mode] (set-mode :mode-game))

You can swap the print for a printf and center the text by swapping out the draw function with the following:

(fn draw []
  (local screen-width (love.graphics.getWidth))
  (love.graphics.printf "Example Mode!" 0 10 screen-width :center))

Entering this into the REPL will make no change. love.draw is calling the draw function in the mode-game module specifically. Instead try using the ,reload command, either in the command line, or in your editor.

,reload mode-game

You should now see “Example Mode!” printed in the center of the screen!

Errors and Recovery

Not all errors are recoverable. However if there is an error during a draw or update callback, rather then crashing to the love.errorhandler the template will first switch to a mode that will let you try and recover.

Once you’ve identified the error, and reloaded the offending module, you can press space to try and recover. Note sometimes you will have to reload multiple modules in order to recover.

This saves a lot of headaches when hotloading modules with typos or minor logical errors. It also lets you setup watchers that hotload your modules on save with confidence that if you save mid logical thought your game will not crash.

Managing State

There are many ways to manage state in any program or game. This section is just my opinionated position on how to do it in love2d games when you’d like to work intreactivly and hotload modules.

When you require a module it checks to see if that module is already in packages. If it is, rather than re-executing the module it just passes you the table representing the loaded module. Since you’re not re-executing the module file, any local state in the module will be retained once its instantiated.

Hotloading a module throws a wrench in the assumption that module local state is retained. When you hotload a module you’re ejecting the current module from packages and replacing it with a re-executed module file. Any state that was instantiated will be re-instantiated. If you have local variables or tables containing state required by the module stored in the module, and those variables are not defined when the module is first executed you’ll likely crash your program or end up with subtle state bugs.

I’d recommend storing state you want to retain between hotloading in a module dedicated to holding state. In this template that module file is called state.fnl, but you could name it anything you’d like.

My rules of thumb when handling state are:

  1. loading all large resources (like images and music), in the love.load command, or the activate callback of the first module you load. Those resources should be stored in a module that will not be hotloaded at any point.
  2. storing state that needs to be shared between modes or state that needs to be retained between hotloads in a separate module. It should be accessed using require as close to where the state is used as is possible. This module may contain logic that supports resetting the state, for example when you want to restart the game.
  3. keeping parameters that are defined as the module is loaded, or are transient, and are specific to the module as locals at the top level of the module / mode.

Making Your Own Game

With just the callbacks update and draw you can make most simple love2d games without an issue and you can wrap other love2d callbacks as you need. If you’re familiar with lua and have worked with love2d before you could also try out other mode management tools like gamestate. Note, the error mode code works specifically with the mode layout defined in the template. You’ll have to roll your own if you go with another manager.

If you don’t care about hotloading and are just making a game with a simple gameplay mode you could also just write your game directly in the wrap module, eschewing the whole mode concept.

Building your Project

If you’re on linux, mac or have access to wsl you can build and deploy your project using the makefile.

You can make the linux, mac, windows and web builds all in one go using make release or you can build each individually, e.g. make linux.

If you’re uploading to itch you should use butler. Once butler is installed and authorized you can use make upload to upload all of your builds to itch.

Looking in the releases folder you will also find the .love file, which you can manually upload as well.

If you’re building for the web, be warned lovejs has to rely on Lua5.1 rather than luajit, so make sure you write your code in a way that is compatible with both.

In Closing

I always have the most fun developing when I can see the changes I am writing in real time. I hope the minimal love2d fennel template lets you enjoy it just as much.

If you’re looking for inspiration I’ll point you to the blog post that got me started down the interactive game development pipeline: in which a game jam is recounted.

If you’d like even more inspiration check out the numerous games listed in the fennel wiki codebases.