More work on Tiled integration. Top: Tiled map editor, Bottom: In-game screenshot with debug drawing enabled to show geometry and spawn-points associated with the map.
Work Since Last Devlog
I went a little overboard this week and did a lot more than usual. I’ve grouped the changes by topic below.
Platformer Suff
Added ‘conveyor belt’ and ‘trampoline’ tile properties as a way to test player-tile interactions. Not sure if they’ll make it into the final Bolero 2 game.
Scenes
Added “first” and “last” script variables for scenes. When given a function, they’ll be run before and after a scene’s actors are processed on a given tick. This is possibly a bad idea, since scripted actors are already a thing, but it might be good to have some logic tied to a scene that can’t be damaged in actor-to-actor interactions.
Added per-actor count and limit tables to scenes as a way to prevent things like the player actor being spawned multiple times.
Spawning, Tiled Integration
Rewrote the spawn tag system. Previously, each actor capable of being spawned from a map was assigned a number representing a tileset tile, typically located in the lower-right corner of the image where no real art is stored. When loading a map, the game would do one pass over a special spawnpoint layer, and create any actors that had matching spawn tag numbers. That was OK for Hibernator, a game with a single background tileset, but it wouldn’t scale very well: every tileset would need to reserve the same specific tiles for actors, and there was no way to specify custom parameters for an actor, except to spawn a temporary ad hoc actor who would then spawn the correct desired actor, make the necessary changes to it, and then disappear. The new system splits the problem across a couple of areas:
- There are now spawnDefs, which handle custom actor tailoring.
- Each tileset has its own spawnTag sparse array, whose only field values are one of: nil, the name of a spawnDef, or the name of a template actor.
After updating the spawning system, I realized it wouldn’t be too difficult to import Tiled gameobjects to use as spawnpoints as well. Tile objects, points, ellipses, rectangles, polygons and text are now imported into the map table, with some special exceptions to treat any shape as a spawnpoint if it has the correct type and name identifiers.
SpawnDefs can take an optional function or table to set up the desired variations. One additional benefit of this is that when actors spawn other actors, they can refer to the spawnDef to get specific variations without having to dig into the guts of the new actor’s tables.
If an actor spawns another actor using a spawnDef alias, and passes its own specialization table/function, and the spawnDef already has a spec table/function, then the spawnDef’s spec is applied first, followed by the caller’s. This isn’t intended for OOP-style inheritance, more for basic configuration, like “make this actor point in this direction,” or “this actor only appears in this specific location on difficulty setting 5.”
Engine Organization, Logging, Debugging
Separated the logger from the rest of the engine, so that it only needs a setup function, the LÖVE API, and the Lua standard library to work. Added different log levels (‘warning’, ‘info’, etc) that can be turned on or off. Pulled the screenshot wrapper out of the logger and into its own file. I wanted to try adding either a file or file size limit for logs, but that’ll have to wait another day.
Added generic project details (unknown author, untitled project, etc) in case the engine user doesn’t specify those things at startup. Moved some miscellaneous debug stuff (really just printing _G (global variables) with a blacklist for common Lua + LÖVE fields) into its own file.
Changed the engine’s global root variable / table name from res to bol, and with the exception of the engine’s main.lua, replaced every instance of calling a submodule through the global path (like bol.foo.bar()) with an equivalent call to a local variable set up with require(). The love.this.that() calls remain omnipresent, but at least this prevents some interference at the engine level.
Input
I started adding gamepad functionality. This was patched into Hibernator after getting some feedback, but it was done rather badly, and after the code split for Bolero 2. I’m partway through splitting mouse and keyboard functions away from the main input source file.
Building and Packaging
Removed the “apply alpha transparency to sprites and fonts with Pillow” step from the build process. Somehow I got the idea that alpha transparency wasn’t working in my graphics editor under Wine, but it is. The only workflow problem with this change is that image fonts with white text are now appearing as white against a white background in my graphics program, so I finally learned how to change LÖVE ImageData so that I can invert image font colors at run-time.
Resource Management
Rewrote how graphics resources are loaded in the engine so that they are loaded all at once in love.load(), instead of whenever module:addThingToGame() was called. They can now be reloaded independently at run-time with a debug command. This required splitting the addThing() functions into two separate actions: register(), which sets up the bare minimum table fields needed to load the asset, and initialize(), which does the actual file check, LÖVE load calls, and additional data setup (making arrays of quads, editing ImageData, etc.)
Graphics
Shuffled some code around so that a one-frame splash screen can be drawn prior to resources being loaded. I think ideally I would need to load the resources in a separate thread so that the application remains responsive, but this is an OK start.
Started work on supporting multiple viewports into a scene. This could be used for splitscreen effects, fading from one area to another within the same scene, etc. I’m still fighting bugs related to wrapping the map around when scrolling out of bounds, though. The whole map-drawing function needs a kick in the pants.
Two viewports into the same scene.
Thoughts
I made good progress this week, but also kind of wiped myself out. Next week will be slower.
The codebase has gotten a fair bit larger this week, and I’m definitely feeling a need to reflect on how it’s structured and organized. LDoc has been working well, very much worth taking the time to integrate into the codebase. The main hurdle I had was not understanding how to document the fields of a table argument, but you can just use “@param my_table.my_field” and they will correctly show up as sub-items.
I haven’t touched the slope positioning bugs at all this week. Maybe the act of writing that I need a roadmap in the previous devlog prompted all these changes, as I tried to think of what I should prioritize, but got sucked into writing improvements as I looked over the code. I feel discouraged despite making good progress this week, but I’ll tell myself that if I keep at it, eventually there won’t be things that need correction, and I’ll be back to working on a game instead of working on the things that support the game.
- Not sure. Maybe I’ll put together that roadmap.