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.gitYou 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 initThe 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:
main.luawrap.fnlmode-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-gameYou 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:
- loading all large resources (like images and music), in the
love.loadcommand, or theactivatecallback of the first module you load. Those resources should be stored in a module that will not be hotloaded at any point. - 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
requireas 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. - 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.