This is a short one. I spent most of the month cleaning up source code to release as libraries. By the time I got back to the build system and the engine run-time, I forgot what I was doing. Then I took a detour to work on a UI system, which is still in a nascent state.
No videos this time, as I’ve got nothing interesting to show (unless you want to see a mouse cursor dragging rectangles around a blank window.)
The UI system, in a very early stage of development.
- Released GaleOps, xmlToTable, stringReader, utf8Tools, and tableToString as libraries
- Partially implemented quadtree-based texture atlas. About 1/3 done
- Started work on uiKit, an event-driven GUI toolkit for LÖVE
A module for reading GraphicsGale images in LÖVE.
I greatly underestimated how much time it would take to clean this up for a proper release. It depends on an XML parsing submodule, which in turn has a few of its own dependencies. All of these were originally just placed in a tools subdirectory in the build system, and I struggled to work on them as individual codebases while also propagating interdependent changes. I ended up taking a top-down approach: put everything into GaleOps and release it as a first step, then release the submodules as individual projects, and then finally circle back to GaleOps and update the prerelease submodules to the 1.0 releases.
I completely rewrote this. The old parser marched through the string one character (byte) at a time, and it couldn’t handle XML Comments, Processing Instructions, or CDATA Sections. It got some things very wrong, such as treating any non-whitespace text after the root element close as forbidden. (Comments and PIs are allowed.) The main parsing loop was also called recursively for every child element, which is fine, but not truly necessary, as you can store the current element details in a separate stack.
It doesn’t handle Document Type Declarations. I haven’t had a need for them so far, because everything that I’ve wanted to parse is from single-user applications where the content layout is pretty simple.
This began as a way to split off some of xmlToTable’s generic parsing code. It creates reader objects which step through a string, using search methods that are anchored to an internal position. Generally, when a search is successful, the internal position advances past the returned match, and when unsuccessful, it stays put (or throws an error.)
I had made some function variations that accept multiple patterns via tables or varargs, returning the first successful match (plus the index) or nil/error if none worked. This could be extended further, in the form of nested tables with some sort of branching logic. In practice though, nothing in xmlToTable actually used the table/varargs versions of functions, so I gutted them.
Confession: I basically wasn’t using Lua string captures prior to this.
Lua 5.3+ includes a utf8 module. It’s bundled in LÖVE 0.9.2 and upwards, but the LuaJIT command line executable doesn’t have it by default. utf8Tools is similar, but not compatible.
This used to be a much larger module, with frivolous stuff like converting U+NNNN strings to code points and vice versa. Having removed the unnecessary functions, it feels redundant, and it might make more sense to just replicate the 5.3 utf8 functions in pure Lua. Oh well.
This is a basic Lua table serializer with some formatting options. Only numbers, strings, booleans and non-cyclic tables are supported, and keys are written out in a specific order unless otherwise specified. Performance with large tables is likely pretty bad, because it has to create one-off tables as part of the sorting process, but it seems alright for outputting converted Tiled maps and tilesets, which is my main use case.
QuadTree-Based Texture Atlas
Pretty much as described in February. I have this partially implemented but untested, and I need to make changes to the resource loaders and animation API in order for everything to function again. I plan to return to this as soon as I get the new UI toolkit sorted out.
(Oops, might need to give this a different name when it’s time to release.)
I started experimenting with a GUI system because I realized that with nativefs, LÖVE is likely pretty suitable for making small visual development tools. It won’t ever supplant a real toolkit with real accessibility features, but the ability to run portions of a game’s Lua source code in separate applications is highly appealing from a dev standpoint.
In relation to Balleg: that game doesn’t need a full WIMP-style GUI, but its menu system is nonetheless very flimsy under the hood. It lacks any kind of mouse input, it doesn’t support scrolling menus or lists, and it’s intertwined enough with the project code that it would be difficult to adapt to other games. Besides all of that, it would be nice to have a GUI layer for troubleshooting purposes.
UI systems are not easy to design or implement, so I tested as many permissively-licensed UI libraries as I could get working from the awesome-love2d list, plus a couple of others that I found along the way. I was particularly impressed with LoveFrames, SLAB, GOOi, and most of all, LUIGI. At this point, I want something a bit lower level than a drop-in solution — possibly something that doesn’t handle rendering and layout at all — so I feel justified in continuing with uiKit.
The goals are:
- Support keyboard/gamepad input in addition to mouse / touch
- The plan is to implement a virtual cursor, which can highlight between zero and one widgets at a time. The cursor’s exact workings may vary from project to project, but pressing enter/confirm and clicking a widget’s contact-box should be generally equivalent actions.
- (Unsure about multi-touch. I need to decide if I’m going to use my one working Android phone as a test system, or to get another phone for that purpose.)
- Don’t generate garbage under normal circumstances, within reason
- Normal circumstances means widgets aren’t being created or destroyed. Recycle event tables when possible, and store pre-split strings in sequences instead of splitting them over and over at run-time.
- There’s only so much you can do about this in Lua, especially when string manipulation is required (a necessity for text input fields.)
- Buffer inputs, in two senses:
- Create event tables from the main LÖVE input callbacks (love.mousemoved, love.keypressed, etc.) and act on them in the order that they are provided. If you only consider the state of the mouse or keyboard as it is at the beginning of love.update(), you can miss events. This can lead to button inputs not working if the framerate dips.
- The event queue doesn’t have to be read completely every frame. You could halt queue processing while an animation plays, and then continue processing events once it’s done. You could also discard the existing queue at key points, to prevent accidentally confirming a dangerous action like deleting a save file.
I got an itch to play a long, meandering game this month, and settled on Betrayal at Kronder, a 1993 DOS RPG. The game is mostly played with a mouse, but it also supports teleporting the cursor to buttons and hotspots with the keyboard. With a few exceptions, the world simulation only executes when the user moves or performs other actions. I found the system interesting to think about, and it’s basically what prompted me to work on uiKit. Anyways, if I finish the game (I’m about halfway through and getting absolutely trashed by the enemies), I might write a bit more about it.
I guess I don’t have anything else for now. Next update should be around the end of April, and hopefully I will have a few interesting UI-related things to show off.