A reworked HUD, preliminary work on water terrain, and some skipping-and-hopping enemies created as a test.
I had writer’s block (or whatever the gamedev equivalent of that is) since December. To try and kickstart progress again, I worked on some maintenance ideas that I had been putting off. I rewrote the command line parser and the backtick console so that it’s possible to add new commands from outside the core codebase. I also removed any instances of goto. Lua has no continue keyword, so I was using goto as a substitute, and also to conditionally skip to the bottom of some functions. I didn’t realize that it isn’t supported in all versions of Lua though, so it would cause a syntax error if LÖVE is compiled with PUC Lua 5.1. (On this topic, I’m also relying on a LuaJIT bitwise library for decoding some Tiled attributes.)
This helped me regain momentum and overcome my block. I then reworked the placeholder HUD, added on-screen score indicators for when successfully hitting or defeating opponents, and then moved on to some more serious changes to actors and animations.
Actors
I consolidated some common enemy behaviour into shared functions. I did this earlier for platformer movement, and it’s been similarly helpful to have more consistency and less copy-pasted code with respect to how non-player characters take damage, flinch, give points and so on.
I made a new placeholder enemy that takes multiple hits to defeat. Up to this point, every other enemy in this game has been one-hit-kill, and the player’s projectile has shot through them like a bowling ball flying through a glass window. This new test enemy destroys the projectile on successful impact, and with some knockback and a counterattack, it feels pretty good. So I’ll probably use this as the basis for other tough enemies.
I extended actors to hold multiple sprite instances. Technically, actors could already do this, but it required management within the actor callbacks to create, update and draw, and the code that handled this tended to get messy.
Positioning New Actors
I changed how actors spawn new actors so that there are more positioning options. Before, the new actor was always placed so that its top-left corner matched the calling actor’s top-left. After reviewing all instances of actors calling addActor(), I found that the vast majority of them would immediately apply offsets so that the new actor was centered with the caller. So that’s the new default, but I can see situations where matching corners may still be desirable, so I’ve also added some arguments to automatically position the new actor against different edges. Here are some examples:
Top-left: Caller center against new center
Top-right: Caller upper-left against new upper-left
Bottom: Caller right-center against new left-center
This accounts for the center and edges of the bounding boxes, plus an option to use the actors’ anchor_x and anchor_y offsets (plus inverted versions: width – anchor_x, and height – anchor_y.)
I guess the next step after this would be to add support for positioning using the dimensions of the current animation frame, and also specific XY points associated with a frame’s metadata. I don’t think this project will get to that point, but you’d really want it for any kind of game with more complex animations. A tool for assigning those points visually would be much better than writing it out in code.
(Failed attempt at an) interface for Actor-to-Actor AABB Collision Response
I tried to simplify collision detection and response from the perspective of writing actor behavior callbacks, but I ultimately ended up undoing all of my work and returning to where I started.
The process up to now has been like this:
- Actors opt into collision-checking
- Before executing actor tick code, the scene runner loops through every opted-in actor to check for overlap against other opted-in actors, building a separate list for each actor. When two actors collide, both have a record of themselves overlapping the other.
- Within each actor’s tick callback, there is a for loop going through each collider, and a bunch of if-then blocks identifying the collider’s properties (hurts_player, is_solid, etc.) and deciding whether or not to react.
So #3 here kind of sucks, because there are plenty of actors that should behave the same way when encountering actors with certain properties, but the conditional checks and responses for this stuff are in for loops within callbacks. It would be easier to manage this stuff by storing the conditions and responses within a library of collision handlers, and then attaching those handlers to actors on initialization. This would also allow actor services to deal with collisions directly.
But my mind’s cloudy on how to actually implement this in the codebase as-is. The response code can already be encapsulated within shared modules, so that aspect of it is already covered. Maybe I’ll return to it as I make more creatures, or maybe it’s something that would be easier to think about when starting the next project with a clean slate.
Animation
I rewrote the frame structure of animations so that they can contain multiple quad chunks from the same spritesheet. Setting up animations to use this is an enormous pain in the neck without some sort of visual editor (which I don’t have), but it could come in handy in a few situations, such as cases where I need to extend a narrow or wide obstacle that’s built out of the same few visual components.
I had not dealt with the code to anchor and center sprites in a while, and I had a pretty bad time trying to coerce it into supporting rotation and flipping+mirroring for both the overall sprite, and then each individual chunk. In the end, I rewrote most of the sprite-drawing function to properly use the love.graphics coordinate system (translate, scale and rotate.)
Here are some screenshots from when I was doing this work. In these images, the player’s standing frame has had 64 additional quads appended in a grid layout, with various flipping and mirroring settings.
The numbers fall out of formation as the player sprite is rotated.
Uhh
That’s better.
I rewrote the function to change actor animations, splitting it into three separate ones:
- animSet(): Only change animation and reset playback counters if old and new animation IDs are not the same
- animReset(): Always reset playback counters, even if it’s the same animation
- animswap(): Substitute one animation for another with comparable frame count and speed, not resetting playback counters. Intended for specific circumstances, like swapping between two running animations.
I added some new fields to the animation structures so that sprites can automatically switch to another animation after looping a certain number of times. Might be helpful in situations where several animations begin uniquely, but terminate with the same final set of frames.
Incremental Tilemap Modifications
I made a generic actor that can copy one area of a map and paste it into another area. This is important for scripting level events, and I’m not sure why I put off working on it for so long. It copies rectangular regions as a set of ‘frames’, one after the other. Between those frames, there are a few possible transitions to choose from, such as updating to the new frame on a per-column or per-row basis. A blend of these kinds of objects should provide enough variation for this project.
Isolating the source tiles on a separate map structure would reduce the chances of those tiles being overwritten and corrupted, so I’ll look into that if I experience issues. I think MegaZeux has something similar.
Explosion FX Generators
Started work on a generic actor that spawns explosion sprites in various patterns. It needs to be a secondary actor so that explosion sprites can continue to spawn after the originating actor is removed from the scene. Needs a ton of work, but here is an example of spawning several of these objects and having each then explosions in a spiral pattern:
Trajectories and Intersect Tests
Spent some time reading about getting the time of flight for projectiles. (Disclaimer: I’m bad at math.) If you know the time in the air, you can adjust the object’s X velocity to target a specific location. When the object is expected to begin and end on the same vertical plane (like, the ground is totally flat), the formula to calculate time in the air is straightforward:
-- Estimated flight time (in logic ticks) data.est_flight = -self.yvel * 2 / move.gravity
When the start and end planes are different, the equation is more complicated:
local displacement = 0 if player then displacement = (self.y + self.h/2) - (player.y + player.h/2) end local g = move.gravity local g_half = g * 0.5 local yvel = self.yvel data.est_flight = (yvel - (math.sqrt(math.abs((yvel*yvel) - 4 * (g_half * displacement))))) / -g
Passing a negative value to math.sqrt will result in nan, which is extremely unwanted in this case. Also, equations you’ll find online usually assume that the Y axis increases upwards, but most video game coordinate systems have Y increasing downwards. So you have to negate the Y velocity and gravity variables In those equations.
I got it so that projectiles can seek / rotate toward targets using atan2. I always forget how to do this.
Added a line-to-rectangle collision function from 2DEngine’s tutorial, with the intent of using it for slightly angled javelin-style projectiles.
Troubleshooting a Crash to Desktop
I ran into a crash-to-desktop issue while working on implementing some new creatures. In some specific gameplay circumstances, memory usage on the main Lua context would rise and rise until the LuaJIT memory limit was reached, at which point LOVE would just close. My first thought was that I have a memory leak somewhere, maybe related to pooled tables, but calling a manual garbage collection event brought usage back down to about 10 MB.
Why isn’t the garbage collector catching this? Maybe it’s getting overwhelmed, leaving no time to schedule a collection event? The issue only seems to happen when many bullets are continuously created and destroyed, and only when those bullets have the platformer callbacks assigned to them. But I’m not sure what, specifically in there, is generating the garbage. Nothing in the platformer toolkit is popping out to me as being a GC issue.
It gets weirder, though. The issue simply goes away if I count memory usage with collectgarbage(“count”) twice within the scene loop. I have no idea why.
I attempted to build an isolated, hands-free version of the game which could cause a crash without requiring the user to spend 5-10 minutes playing a small test room over and over. I can replicate a bit of the high memory usage, but I can’t duplicate the crash this way.
Here are some results of the hands-free test running for five minutes on Fedora 30 and Windows 10, with and without the calls to collectgarbage(“count”) in the scene loop. These numbers vary from run to run.
+-------------------+----------------+-------------+------------+ |OS | LOVE Version | Highest Memory Usage | | | | With Without | +-------------------+----------------+-------------+------------+ |Fedora 30 | 11.2 64-bit | 23 MB | 293 MB | |Fedora 30 | 11.3 64-bit | 21 MB | 595 MB | |Windows 10 64-Bit | 11.2 32-bit | 19 MB | 34 MB | |Windows 10 64-Bit | 11.3 32-bit | 18 MB | 33 MB | |Windows 10 64-Bit | 11.2 64-bit | 17 MB | 333 MB | |Windows 10 64-Bit | 11.3 64-bit | 17 MB | 44 MB | +-------------------+----------------+-------------+------------+
I spent hours on this and got nowhere, not even a proper duplication that I could show people who might be able to determine what’s happening. In the short-term, I’m definitely leaving those “count” calls in the code, and I’ve also added a bit to the main loop to force a GC event and log an error if usage is over 512 MB*. That causes a lag spike as the collector sweeps through all allocations, but it’s far preferable to a crash-to-desktop with no error.
* I’ve added a failsafe to disable this if a collection doesn’t actually reduce Lua memory usage to less than 512 MB. I guess that wouldn’t work so well in cases where the game is legitimately using nearly that amount of memory.
Metatables
While refactoring code to try and isolate or mitigate the above crashing issue, I did end up with one change which had a positive effect besides those magic collectgarbage(“count”) calls. I changed how actors are instantiated so that shared functions and state are stored in metatables.
Metatables let you assign special handling instructions to a table. You can simulate features of other programming languages, such as class inheritance and operator overloading, making tables read-only, and many other interesting things. A table’s metatable can be itself, or a unique table that only it has access to, or a table that’s shared among many other tables.
I haven’t used them up to this point because they add a layer of indirection I didn’t think I needed. But now, actors are setting up tons of the same old info each time they’re instantiated (in particular, assigning a bunch of OOP methods), and it just makes more sense to maintain one metatable per type of actor with all of that set up once and then accessed through the __index metamethod.
Besides improving the memory issue, the game also seems to run a bit faster now.
Codebase
I changed the project directory structure to more cleanly separate the core engine, project resources and 3rd party libraries. I’ve wanted to do this for a while, but I kept putting it off. I liked how Zabuyaki is split up into lib, res and src folders, and so I’ve done something similar. Eventually, I’d like to have the engine completely separated from the project data such that you could run multiple games from the same engine base.
I changed how actor definitions are loaded. I used to keep the behavior files (startup / tick / cleanup callbacks) separate from the function calls which actually add them into the game. For every single actor. That made sense early on, when I was constantly changing stuff around, and wanted to have backups of individual files handy (enemy.lua, enemy2.lua, enemy2alt.lua, etc.) Later on, as I got further into development, switching between the behavior files and the actor registry list was annoying, but bearable. Now, with over 40 actors, it’s an enormous pain to jump between files, and an active hindrance to prototyping new creatures. So now there is only one file that defines an actor, and the resource loader walks the entire ‘behavior’ folder and attempts to register every ‘.lua’ file that it finds there.
I made similar changes to widget definitions. I haven’t decided yet if I want to do the same for audio and visual assets, maps, and other structures.
Scene Transitions
I finally fixed a longstanding issue where one frame / tick of an uninitialized scene flickers when switching scenes or subscenes. This has been a longstanding problem in my engine, and it was ultimately the result of allowing scene switches to occur in the middle of tick processing. It seems obvious to me now that of course this would cause problems, but I guess I didn’t understand at the time. Deferring the switch to the start or end of the scene run() function resolves it.
Other Stuff
I made it so that after about a second of uninterrupted walking, the player character somersaults when jumping. It has no practical benefit but looks neat. The player also gets dizzy stars when taking damage consecutively in a short period of time.
More useful: I got a variant of the jump-through platform working, which the player is not able to manually fall down through by pressing down+jump, but which still allows player’s shots to pass through. This would be great for allowing the player to enter certain rooms without being able to backtrack, or to have bridge-like areas with spikes and enemies beneath the player, and not have to worry about the player accidentally down+jumping to their doom.
Started work on allowing map layers to fade in and out by changing the alpha level when they are drawn, and tying those changes to trigger zones on the map. I have it working just for the terrain overlay for now, but should be able to extend it to the terrain layer and background as well. Here’s a screenshot of the overlay fading as the player has passed a trigger zone with an alpha value of 0.0:
Project Outlook
I think this was a pretty good month. As far as content goes, I sketched out some opponent and hazard ideas, but didn’t get too far with implementing them before the out-of-memory crashes took priority. Then, I got preoccupied with modifications to actors and the animation system, and restructuring the overall codebase. While I didn’t get much content done, these things were worth pursuing in the end because they will make it easier to prototype new ideas.
My feelings about this project have changed. I’m no longer worried about simply getting it out the door in a timely manner. I want to dive deep into the charge -> shoot mechanic, and make the levels and gameplay as intense and as sharp as I can.
I’m happy with the switch from weekly to monthly devlog posts. It was good when starting out, but lately there have been too many instances of me just having nothing to say at the end of the week.
That’s all I have for now. I’ll make another devlog post at the end of February.