Also move or die is running on love2d, which is an awesome game.
Also I love that trick that you can just zip your files and binary Comcast them to the love2d binary and it will load it.
Lua is so small and simple (but not simplistic) that you can keep it completely in your head. Even if you only get to work on your project once every weekend you won’t have to relearn half of it every time.
https://itch.io/jam/love2d-jam-2026/topic/6082771/how-to-get...
... posted in the site of the 2026 Löve2D Game Jam, that sounds like something also worth mentioning: https://itch.io/jam/love2d-jam-2026
Too late to enter. Jam was last month. But there are 47 games to check out there, plus many from previous years.
But I’m wondering: why do mature tools like this sometimes end up on Hacker News’ front page without any particular news (like new releases or updates)? It’s just a curiosity, not a critique.
[1]: https://projecthawkthorne.com/
It is now available to play straight in the browser(I guess using LÖVE Web Builder?).
[1]:https://schellingb.github.io/LoveWebBuilder/
This engine has really come a long way and enabled such a memorable game for me.
Maybe because you can fit the whole language spec on a single sheet of paper and adding more advanced features is pretty easy.
Love looks really cool. I never got into it personally but I still might
That said, i'm not impressed. A web-based solution is usually better performing, despite all the bloatware necessary. This says a lot about the state of software development unfortunately.
The SDL3 GPU API[1] provided a cross-platform GPU API even before WebGPU.
In Rust it's a good alternative for winit/wgpu. For that reason I added it to areweguiyet.com[2] last week, where apparently it wasn't even listed before.
I am currently using it to develop a space game[3] inspired by the original BBC Elite. Using emscripten to get on the web and QUIC/Webtransport for networking.
[1]: https://wiki.libsdl.org/SDL3/CategoryGPU
[2]: https://areweguiyet.com/#ecosystem
[3]: https://git.levitati.ng/LevitatingBusinessMan/elite/src/bran...
The Launcher is available also for old Android versions, which means that old obsolete Android devices (I have some tablets and phones) can be used for whatever it can be fun to still write some GUI for on some spare touchscreen device.
Löve on the other hand is 100% just code. You'll not have the gui things and the pletora of different components that go with them. Still gives you freedom. Just too much freedom and not as much helpful preset tools.
(Throwing diacritics on English words look extremely silly to me, since I know how åäö are supposed to be pronounced. It makes something like Motorhead just sound laughable rather than metal.)
https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/ba...
I wrote half a blog post when I did the derivation. One day, I should finish it and post it here.
It was chosen around 2008 or so to be the scripting language in Multi Theft Auto: San Andreas.
We build entire worlds in Lua, there were many gamemodes, but my favorite was Roleplay.
Good old carefree times.
löve is getting a vulkan backend. I would have prefered löve to be plain and simple C coded instead of this abomination which is c++ (but it seems coding AIs may be very good at assisting mass c++ to plain and simple C port).
[1] https://github.com/libsdl-org/SDL/issues/11148#issuecomment-...
I see why people might hate Lua. Especially for game dev!
Author is currently building version 12 which will be using SDL3. But it's been in development for quite some time with no clear end date afaik.
There are a lot of free-as-in-freedom alternatives to (and clones of) PICO-8, but TIC-80 is indeed the most popular one, by far. And popularity is important for any software ecosystem. I really like that it supports other languages, even if that kinda inhibits its ability to be embedded into small hardware.
Apparently the nightly release supports DCPM samples now. Dunno why.
How? has Lua changed any?
Or all the references are to old versions?
Moonring[1] is another one that that is written in Löve (apparently by the co-creator of XBox's Fable series). The base game is even available for free. I had lots of fun playing it.
Thanks for the tip. That should make for a fun weekend
On top of that there are love2d specific libraries people have written to deal with 2D games like GUIs and tile libraries.
Then there is the ease of debugging, where you can use lua to have runtime access to the table of variables and can print them on screen if you need to, not to mention dynamically loading new update and draw and input functions.
This is all to say that just downloading SDL is not going to get anywhere close to what love2d has included.
That obviously isn't a replacement for the framework but it is perfectly doable if someone just wants to write a game in Lua with minimal overhead.
Edit: I mention LuaJIT specifically because it lets you create metaclasses around C objects, which is much easier than messing with the Lua stack from C, and it's easy to make a 2d vector class from an SDL Point or a spritesheet or what have you. There are a few rough edges like dealing with pointers and gc but to me it's the best of both worlds (the speed of C, and some implicit type checking, and the flexibility of Lua.)
Obviously you could do it the hard way and the other way around with normal modern Lua but it's such a pain in the ass.
I think love2d is better if what you love is coding, everything is code, love2d just executes Lua.
If what someone wants to do is make (for example) a 2d platformer, or definately for 3d, and the coding is something you need to do to make your game, goody is better, it includes so many batteries, have a built in gui level editor, etc.
One big advantage of love2d (although ironically not loved by many in its audience) is it is the AI friendly engine, as AIs love text and hate GUIs.
(It is neither open source nor free)
LÖVE is an awesome framework you can use to make 2D games in Lua. It's free, open-source, and works on Windows, macOS, Linux, Android, and iOS.
We use our wiki for documentation. If you need further help, feel free to ask on our forums, our Discord server, or our subreddit.
We use the 'main' branch for development of the next major release, and therefore it should not be considered stable.
There are also branches for currently released major versions, which may have fixes and changes meant for upcoming patch releases within that major version.
We tag all our releases (since we started using mercurial and git), and have binary downloads available for them.
Experimental changes are sometimes developed in a separate love-experiments repository.
Files for releases are in the releases section on GitHub. The site has links to files and additional platform content for the latest release.
There are also unstable/nightly builds:
The test suite in testing/ covers all the LÖVE APIs, and tests them the same way developers use them. You can view current test coverage from any action.
You can run the suite locally like you would run a normal LÖVE project, e.g.:love testing
See the readme in the testing folder for more info.
The best places to contribute are through the issue tracker and the official Discord server or IRC channel.
For code contributions, pull requests and patches are welcome. Be sure to read the source code style guide. Changes and new features typically get discussed in the issue tracker or on Discord or the forums before a pull request is made.
[!NOTE] Pull requests, bug reports, and other contributions made with LLM / generative AI technology will not be accepted.
Follow the instructions at the megasource repository page.
Because in-tree builds are not allowed, the Makefiles needs to be generated in a separate build directory. In this example, folder named build is used:
$ cmake -B build -S. --install-prefix $PWD/prefix # this will create the directory `build/`.
$ cmake --build build --target install -j$(nproc) # this will build with all cores and put the files in `prefix/`.
[!NOTE]
CMake 3.15 and earlier doesn't support--install-prefix. In that case, use-DCMAKE_INSTALL_PREFIX=instead.
Download or clone this repository and copy, move, or symlink the macOS/Frameworks subfolder into love's platform/xcode/macosx folder and the shared subfolder into love's platform/xcode folder.
Then use the Xcode project found at platform/xcode/love.xcodeproj to build the love-macosx target.
Building for iOS requires macOS and Xcode.
Download the love-apple-dependencies zip file corresponding to the LÖVE version being used from the Releases page,
unzip it, and place the iOS/libraries subfolder into love's platform/xcode/ios folder and the shared subfolder into love's platform/xcode folder.
Or, download or clone this repository and copy, move, or symlink the iOS/libraries subfolder into love's platform/xcode/ios folder and the shared subfolder into love's platform/xcode folder.
Then use the Xcode project found at platform/xcode/love.xcodeproj to build the love-ios target.
See readme-iOS.rtf for more information.
Visit the Android build repository for build instructions.
https://github.com/antirez/load81
Anyone looking at Lua/SDL/game engines would learn a lot from antirez' fun little afternoon project ..
(My personal pet peeve is that Kodi still doesn't know how to minimize CPU consumption when one is doing nothing on the UI. It should just stop rendering. This means I have to turn Kodi off on my HTPC+server setup to stop it from pushing my CPU in a higher power consumption mode.)
Pity it's not playable in even mildly current versions of love because being backwards compatible takes some slight effort on behalf of framework maintainers.
But there is one gripe -- when packaging apps into executable, TIC-80 pulls templates from the Internet.
On one hand, it's not that big deal, we are online basically all time nowadays. But on the other hand, I would expect that kind of software to be self-contained.
I found a quite simple (but definitely not frictionless) workaround though - you can build the templates yourself, edit source code to work with localhost instead of TIC website, and host the templates on local webserver.
As I said, it's not a frictionless solution, but I don't know C well enough to make more substantial changes to this behaviour.
It's like right-click-to-select.
A single data structure (tables), no built in OOP (you build it with metatables).
Coroutines instead of threads.
I was curious and explored it in detail here - https://vectree.io/c/lua-language-design-metatables-coroutin...
It doesn’t pretend to be more than it is. Anyone can learn it a couple of sessions. It has all the stuff you need to write a program.
Imagine if we had Lua instead of JS in the browser.
In fact the latter was once closer to Lua, but didn’t have the same philosophy of wanting to stay minimal.
For that, I'll keep this in mind: "Unlucky players may look at the source code of a chance-based effect to check if the odds are actually as stated."
there isnt step 2, explain is over
“It makes more sense this way” is not a good enough reason to break convention.
local alive_enemies = {}
for _, enemy in ipairs(enemies) do
if not enemy.dead then
table.insert(alive_enemies, enemy)
end
end
enemies = alive_enemies
with enemies = enemies.filter(enemy => enemy.alive)It's also just plain cool to rock the TIC-80 editor fullscreen with narrow font, coding natively in Lisp and publishing the result to a webpage you can share.
I wish the iOS (app) deployment story was a little smoother for TIC-80.
This is a bit of an apples to oranges scenario, because the algorithm and architecture is not exactly the same, despite the game functioning identical.
The main weak points of LÖVE that we hit were mainly around embedded video playback though, which is probably very well optimized in chromium.
> we had better regard —after all those centuries!— zero as a most natural number
Of course, a counter argument is that we've already made the mistake of indexing with 1 in natural language (first, second, ...). That decision is not free of annoyances, though: the 19th century are year numbers 18xx, floors below the first have a varying names when they could have been negative numbers, etc.
Rinse and repeat over months, with volunteers, in a game engine no less, and I can easily see many projects being unable to not fall into that trap.
Anyways, I think it's less of an issue for people in practicality, most people who use LÖVE today tends to start with the HEAD source version, which also sets them up to easier contribute back upstream, when they inevitably hit something non-optimal, so maybe it works out in favor for everyone in the end anyways.
For example in Sid Meier’s Memoir, this is mentioned.
Quoting from a review of said book:
> People hate randomness: To placate people's busted sense of randomness and overdeveloped sense of fairness, Civ Revolutions had to implement some interesting decisions: any 3:1 battle in favor of human became a guaranteed win. Too many randomly bad outcomes in a row were mitigated.
https://smus.com/books/sid-meiers-memoir/
Some threads on randomness and perceived fairness in video games can be found here on HN too, for example https://news.ycombinator.com/item?id=19399044
The original link being discussed in that thread is 404 now, but archived copies of the original link exist such as for example https://archive.is/8eVqt
https://love2d.org/forums/viewtopic.php?t=94760
It's unfortunate as Love2D is generally VERY snappy on x86. I used it on a 300MHz laptop back in the day.
Right now I have a workflow breaking bug in Inkscape which was fixed last year on main but hasn’t made it to a release yet. So my only option is to compile from source.
There being a stigma about a release being “ready” needs to go. Stuff should get only get merged in to main when it’s ready to go live, or behind a feature flag.
Was good because it identified a personal mental flaw.
The Steam version was created by one guy, but the platform ports have a couple different authors. The Google Play and Xbox PC versions, for instance, have divergences.
I wonder how the ports influence the upstream and each other. How do they keep the codebases in sync, while also accounting for platform differences?
My custom media center is basically just a glorified 10ft-UI file browser. Opens media files in mpv (with some extra GUI to download subtitles and select audio tracks), Wii games in Dolphin, runs shell scripts (I have ones launch Steam Link etc.)
I realize that this might be a case of "simplify by limiting use cases" but I made it for me so it's fine.
It still is an issue nowadays https://discussions.unity.com/t/app-needs-warmup-first-slow-...
Similar story with the GC, it's nice to have, until it causes you problems (wich it will), so you end up having to avoid using it and instead rely on manual techniques
JIT and GC aren't the panacea people make them out to be
I've long been suspicious of the RNG/seed implementation.. but not curious enough to automate testing of it, though.
60MB+ for a calculator is not optimal.
For me, the table is extremely powerful. I like it that it can be used as a sparse array, a hash, a vector, whatever. Of course one must know, at heart, the difference between pairs() and ipairs() and what it means for your data, though ..
enemies = array.filter(enemies, function(e) return e.alive end)
Or even setting a metatable so you can do: enemies = enemies.filter(function(e) return e.alive end)The only difference is that one of the language is embedded and barely takes any place. it's just a few C files :-D It offers just enough functionality while not making it overly complicated to make basic things.
The other one is way way bigger. and even Array.filter didn't exist from the start
But if it is about using it as an embedded language. you want just enough language to get you started and be able to tweak controls. so that the embedded language itself doesn't take up unnecessary space, on its own.
It's a design choice to have a language as small as possible while still offering cool tools.
I like the argument that 1-based is better for indexing and 0-based is better for offsets: https://hisham.hm/2021/01/18/again-on-0-based-vs-1-based-ind...
The real source is mathematics. But some might say it's incomplete.
"Heavy processing delegated to webworkers?" That just sounds like threads but worse.
Ostensibly. Honestly most of it was hacked together with Perl.
Most JITs let you tune when and how they inline. It's also worth knowing how they works and what they can/can't inline.
You linked to monojit. Luajit is a whole other beast. I'd argue it's superior to anything in JS/JAVA/C# land (and I say that as someone with a reasonable understanding of the JVMs C2 JIT).
As an aside with low latency GCs like the JVM's ZGC trading manual memory management for no memory related security vulnerabilities is pretty appealing.
Like everything in programming it's a trade off.
(set enemies (filter |($ :alive) enemies))
Though JavaScript has QuickJS, which is also lightweight.If I remember correctly, Stargate-SG1 at one point had some ideas about this sort of universal language, that multiple species could use for communication, as any sufficiently intelligent specie probably been able to see atoms and so on, but may have completely other way of doing "math-like" stuff.
If I get the root of the argument in the linked article, it is that zero-based indexing is more of a optimization than anything, but I would disagree; there are reasons beyond that (see the examples in my previous comment).
Also, here's an example of an 1-index based system that has caused me some headaches: In music theory, the first note of the scale is called the "first", etc. It also talks about e.g. "stacking thirds", which means take the third of the scale, than take the third from there. However, the offsets are two. (first=offset 0, second=offset 1, third=offset 2). Which is hard to work with in my opinion.
You have an interesting argument about iterating backwards, although I would say; if we need a tie-breaker between the two, iterating forward should have more weight than backwards.
I appreciate your comment, and while trying as best I can to be convinced of the "other side", I still land on 0-indexing. The only argument I buy, is that it matches our natural language starting at 1. Which, of course, is a strong argument.
So, as someone only very peripherally familiar with Lua, can someone please explain the table thing to me? I've heard Lua fans gush that Lua is tables all the way down, except it seems like there's these tables on the one hand that work like arrays, and those other tables on the other hand that work like objects, and you can't mix them up...
Is that not just an ordinary dynamically typed language with arrays and objects then, except it overloads the word "table" to refer to both?
I'm sure I'm missing something, happy to hear what that is.
Yes, there is:
local l = {[0] = 'a', [1] = 'b', [2] = 'c'}
for i, c in ipairs(l) do
print(i, c)
end
This will only print the last two pairs. Lua is 1-indexed, end of story. You can store values at index zero, but it's no different than storing values at index -1 or index 'lolrofl'. It does not exist in the array-part of the table as far as Lua is concerned.but webgl + web workers is good enough these days.
I can't share code sorry, the project got big and I have commercial plans.
But you can tell Gemini 3.1, Opus 4.6 or GPT 5.4 High to generate a demo and they do a decent job most of the times.
that's how I got started, seeing how it was possible to have good game performance with multi threaded workloads on a browser.
So a table is a hashtable. Just about anything can be a key to the hash - a number, a string, a boolean, another table, a userdata. I can't recall if functions and coroutines can be keys but I suspect they could. I definitely know that nil can't be an index.
In Lua - all iterators are implemented as a function + state + current key. When you write "for x in (whatever)", that (whatever) is really a function call like func(state,key). Lua calls it repeatedly to traverse the loop.
Lua will take the first returned value and use it as the new key in the next call to func(). Once the function returns nil as the first value (or just returns no values) - the loop stops.
There are two functions - pairs and ipairs - that really just return the built-in next function with a starting key. pairs returns (lua's next(), the table, nil), and ipairs returns (next(), the table, 0).
(there's a bit more to it than that and some under-the-hood changes between Lua versions but we'll just go with that explanation).
Lua repeatedly calls that next() function with the table and previous key.
Say you had an array like table like { [1] = some_value, [2] = another_value }.
When you write something like "for i,v in ipairs(a_table)" that roughly expands to:
* pairs() returns next(), the table, 0.
* Lua calls next(table, 0) and next returns (1, some_value)
* Lua calls next(table, 1) and next returns (2, another_value)
* Lua calls next(table, 2) and next returns nil.
So - when is a table an array?When you can iterate through it with sequential, integer keys.
Note - you don't necessarily need to use ipairs to iterate. Lua also has a numerical "for" loop so you could do that. Or - if you want to start your arrays at 0 instead of one you can write your own ipairs() like function in just a few lines of code. Observe:
local zpair_it = function(table, key)
key = key + 1
if table[key] then
return key, table[key]
end
end
local zpairs = function(table)
return zpair_it, table, -1
end
local tab = { [0] = 'yay', [1] = 'huzzah' }
for i,v in zpairs(tab) do
print(i,v)
end
There - now instead of using ipairs(), you can use zpairs() with zero-based indexes.As far as using like objects, that's getting into metatables and stuff but - basically lua has a syntactical sugar to make object-orientation nice.
If you write "obj:func()" - that's the same as writing "obj.func(obj)" - assuming "obj" is a table, Lua will fetch the "func" key from the table, then call it as a function with "obj" as the first argument (this can be referred to as self within your function definition).
If Lua tries to fetch "func" from your table and it doesn't exist - it will check to see if your table has a metatable defined with an __index metamethod (or table) and pull func from that. So - your table could just have your object's actual state data on it, and functions are referenced in a separate metatable.
Observe:
local dog_methods = {
bark = function(self)
print("bark bark I'm a",self.breed)
end
}
local dog_metatable = {
__index = dog_methods
}
local huskie = setmetatable({
breed = 'huskie'
}, dog_metatable)
local collie = setmetatable({
breed = 'collie'
}, dog_metatable)
huskie:bark()
collie:bark()
huskie:bark() is equivlanet to huskie.bark(huskie). bark doesn't actually exist on huskie, but huskie has a metatable with an __index table where bark is defined. So the function call is really more like dog_methods.bark(huskie).Anyways wow that was a lot. Highly recommend going through the Lua manual. It's not a long read.
local l = {[0] = 'a', [1] = 'b', [2] = 'c'}
for i = 0,#l do
print(l[i])
end
.. prints:
a
b
c
Lua haters usually don't get past their misunderstanding of tables, but its really quite unfair on the language and those who have used it quite well to do big things ..If you're in Love and/or control the environment you're free to bring in whatever libraries you want. Or to build your wrapper to support multiple files from the user.
Like you could suffer from a bad embedded scripting setup with any language. Granted if it was embedded Python or Javascript you would get a bit more for builtin if they embed a full implementation. But also embedding Lua with support for user supplied libraries is less effort than embedding a whole Python/JS runtime
Last time I used LÖVE that wasn't the case, nor does it seem to be the case today, you can require libraries or even use LuaRocks if that is what you prefer, and everything just works.
Either way, I think it's a nitpick to complain about. I've written a decent amount of Lua and there's only been a handful of times where 1-indexing was even relevant to me.
You don't change something like that because it eventually got picked up by game programmers (never the intent of Lua, something that just happened after it was used by the Grim Fandango team, then it took off in the gaming world).