I'm going to try and keep track of all the Stuff* I do this year. I want to be able to look back from the smouldering ruins of 2027 and think it wasn't all that bad and at least I got some things done.
*stuff meaning hobby gamedev, tools, fun stuff that I publish to the world.
January
Peachy update
I'd been neglecting my little Aseprite LOVE library for a while and I was never quite happy with how it worked.
I spent a little time this month fixing it up and addressing some issues and quirks:
- Added support for slices
- I use these a lot. They're essentially just rectangles you can draw on the sprite and attach data to.
- They're a bit weird in how they work with animated sprites but the upcoming layer types will hopefully fix a lot of that. I want to support all of these in peachy too.
- Added support for hash-type exports
- Previously only supported array-types exports. This was always a pain point. I had a note in the readme about it but nobody reads those.
- Now you can just export either a hash or array style JSON file and peachy handles it.
- No more cron dependency
- Genuinely don't know why I had this anyway.
- Easy enough to rip out and incidentally fixes a bug where if your (game's) frame time was higher than a frame's duration then it wouldn't skip properly.
- Fixed bugs
- The relative path bug is gone so you can put the peachy folder anywhere without having to change the PATH variable
- Using image dimensions instead of trusting the metadata in the JSON
- this was needed for the spritesheet combiner (see below)
- Added LSP annotations
Aseprite sheet combiner
I had this code (or code like it) sitting around for a while but I finally cleaned it up and made it usable.
What does it do? Past me wrote this excellent summary in the readme:
A utility for combining multiple Aseprite files into a single spritesheet with corrected animation frame positions.
This lets you have loose .aseprite files for your assets but still combine them into one spritesheet (for runtime draw performance via batching).
I can have an artist (or just me :)) work on individual .aseprite files without having to worry about clobbering atlases or merging big (binary) Aseprite files. Everything is nicely contained until it needs to be spritepacked as part of the build process.
Incidentally a big improvement I want to make to this is properly spritepacking, it relies on Aseprite's packing atm. Might just be able to crop and trim but then slices etc. won't make sense? I think. Will look into.
The Newest York
I had a realisation that New York might not be the Newest York and wrote a bit of code and did a bit of manual research into figuring it out and published it as a blogpost/largely just rambling mess.
spoilers: the newest one isn't new york and honestly they should hand over the title.
lua-ioc container
This is staggeringly unhelpful and goes against everything Lua stands for but I wrote a little IoC container (library???) in/for it over a lunch break.
You write code like this:
local ioc = Container(IoCMode.DependencyInjection)
ioc:register(LogService, LogService, BindingType.Singleton)
ioc:register(DoSomethingService, DoSomethingService, BindingType.Transient)
ioc:register(ExtraParamService, ExtraParamService, BindingType.Transient)
local doSomethingService1 = ioc:resolve(DoSomethingService)
doSomethingService1:doSomething()
doSomethingService1.logService:log("Hello from doSomethingService!")
and your wildest dependency injected dreams come true. It just works!
Unfortunately if you look under the hood it's extremely cursed and I don't recommend actually using it.
You write your constructor functions like this:
---Creates a new DoSomethingService instance.
---@param container Container The IoC container to resolve dependencies from.
function DoSomethingService.new(logService, extraParamService)
local self = setmetatable({}, DoSomethingService)
self.logService = logService
self.extraParamService = extraParamService
return self
end
and then the container will resolve the dependencies automagically using Lua's debug library:
---Resolves dependencies for a given implementation.
---@param constructor function The constructor for the implementation to resolve dependencies for.
---@return table ... The resolved dependencies.
function Container:resolveDependencies(constructor)
local params = {}
local info = debug.getinfo(constructor, "u")
for i = 1, info.nparams do
local parameterName = debug.getlocal(constructor, i)
assert(parameterName, ("Failed to get parameter name for parameter %d of %s"):format(i, tostring(constructor)))
local className = transformArgumentNameToClassName(parameterName)
local type = self:getTypeByName(className)
table.insert(params, self:resolve(type))
end
return unpack(params)
end
for those of you not so moonly inclined this:
- Gets a list of all the parameters in the constructor of the thing we're trying to resolve the dependencies of
- Transforms the name of each parameter (so: logService becomes LogService)
- Tries to find that name in the container
- Provides an instance of the dependencies (creating it if necessary i.e. transient or the first time we've seen a particular singleton)
So this is obviously:
- extremely fragile since the name has to be exactly correct
- slow potentially since it's using the debug library
- will break if you use
luacto strip debug info (but who does that?)
For fun I also added a more sane (perhaps) service locator mode which doesn't use the debug stuff at all:
---Creates a new DoSomethingService instance.
---@param container Container The IoC container to resolve dependencies from.
function DoSomethingService.new(container)
local self = setmetatable({}, DoSomethingService)
self.logService = container:resolve(LogService)
self.extraParamService = container:resolve(ExtraParamService)
return self
end
It's essentially the same except the only thing injected is the IoC container itself and the constructors are responsible for fetching their dependencies. Maybe a bit more sane, but on the other hand it turns it into a service locator...
ennui
I started working on a UI library for LOVE, just like everyone else.
I haven't released this yet but I've posted about it a few times in a LOVE discord.
It's fairly complete at this point:
- I have a fullish suite of example widgets:
- Button
- Checkbox
- CollapseableHeader
- ComboBox
- DockableWindow
- Dropdown
- DropdownMenu
- Group
- HorizontalStackpanel
- Host (this one is the only special case - its the root of an ennui context)
- Image
- Menu
- RadioButton
- Rectangle
- ScrollArea
- Slider
- StackPanel
- TabBar
- TextButton
- TextInput
- Text
- Treeview
- TreeviewNode
- Window
- Reactivity in the form of:
- Watchers
- Computed properties
- Draggable widgets
- Dockspaces (though this needs a bit more work, you can't manually split without dropping a widget)
- Tab indexes (widgets can have/be tab contexts)
- A work in progress inspector to examine and debug layouts and computed properties
- Lots of layouting stuff:
- Users can create custom "layout strategies"
- Some obvious ones are included, horizontally and vertically laying out children, grids etc.
- Autosizing, filling space, percentage/ratio sizes, fixed pixel sizes
- Probably a lot of other cool stuff.
The inspector I'm particularly proud of (though it's still early). It works as a completely separate ennui Host so you can toggle it easily and change which Host you're debugging. It can even inspect itself!
This site
I finally posted something. You're reading it.
Got some styling on it and everything.
til next time
anyway thats everything until february when I hopefully release ennui :)