A testing area, viewed through Tiled. Top section: elevators and lifts. Bottom section: nondescript spawn points for various traps and prototype enemies.
I’ve been working through my backlog of sketches and building gameplay prototypes from them. Some things end up being neat, others turn out pretty bad. Unfortunately, the player’s sphere attack sometimes makes it difficult to know ahead of time if an idea is going to be successful.
Terminology change: I renamed the ‘jump-through platform’ terrain to planks. It was getting difficult to search for code related to this terrain, because the source file that deals with them is also called platformer. I was also occasionally getting them confused with actors that are floating platforms.
Input
Made a change to how the engine deals with the user pressing both left+right or up+down at the same time. Up to this point, both directions have cancelled each other out such that holding two opposing directions results in neither key reporting being as held. I made it so that you can use different rules for horizontal and vertical axes:
- cancel_out: Same as how it was before. Hold both directions, nothing is reported
- newest_first: Only report the direction that is most recently held
- oldest_first: Only report the direction that has been held the longest
- use_both: Don’t bother, just report both
use_both is not allowed by default, and is silently interpreted as cancel_out unless the developer / player sets a flag in the input module to allow it. I find that allowing opposite directions to be reported simultaneously is a common source of bugs. Maybe you have some code like “if holding left then do this, else do that”, and then elsewhere, you have “if holding left then do this; if holding right then do that”. These will behave the same way if we make directions either-or, and will behave differently if we allow both through.
Testing the player’s behavior in my game with use_both for both axes: you can walk in-place by holding left+right, and you can make high shots while crouching by holding down+up. The latter might be desirable, the former just looks goofy, and neither can be accomplished when played using a gamepad with a conventional d-pad or analog stick for directional movement.
New Terrain
Added sloped variations of the following terrain types:
Sloped conveyors
At some point, I must have decided that slopes would be mutually exclusive to certain kinds of terrain, because the conveyor response was wrapped in a block of code that only executes when the actor is not on a slope. I moved the code out of that block, defined some slopes with conveyor properties, and it seems to work.
Sloped ceilings
I had the terrainDefs partially written out for this a while ago, but shelved the idea because it seemed difficult to implement. It took some effort to set up and work out the bugs, but the effect of the player and player’s shot reacting to the ceiling angles was worth pursuing and pretty neat.
Sloped planks
This includes both the “allow player to fall-through” and “don’t” variety. I’ve limited the number of angles to only slight slopes, because I imagine that 45-degree diagonal planks could cause issues. They are also limited in how they can be placed, similar to how sloped ground is limited: no direct stacking one sloped plank on top of another in the tilemap, and an actor’s bottom-center point is always used to determine if it can be grounded. There are also fewer safety checks for actors bumping up against ceilings while standing on them.
Sloped walls
These are sloped tiles that actors can never stand on. When an actor moves into the tile, they are pushed out horizontally… wait, come back! I swear they’ll only be used sparingly.
So I had a difficult time getting them working. Like with the sloped ceilings, I had written out the terrainDefs and made test art in the tileset a while ago, but then I shelved the whole thing. To get this started, I copied and modified the code that checks floor slopes. The first issue is that the terrainDefs, which were never plugged into any working code until now, had incorrect slope values, with either all tiles reversed, or each individual tile having their left and right slope values inverted. I had to test the player moving against every 16×16 piece of every angle of slope to make sure that the correct collision response happened. The second issue is that these walls can be left-facing or right-facing, and the check and response for one way is the opposite of the other. The floor slope code that I appropriated for this only had to deal with the floor, never the ceiling. It’s all good now, just took a while to identify and fix all of these issues.
Various arrangements of these terrains will cause problems with actors falling through solid ground. I was pleased that this slightly-rotated square shape more or less seems to work, though:
Some shots don’t ricochet correctly at the corners, and the player can’t teeter on the edge of the sloped floors because only the bottom-center point is used to determine standing, but shapes like this could be used sparingly in some environments without the risk of breaking the game.
After some testing, it looks like this kind of shape with slanted floor against slanted ceiling might work as well, though I’d probably prefer to use slanted planks instead:
Additional Sprite modes
Added a couple of additional modes for sprite instances:
Tile Mode
Sprites display a single background tile instead of a normal animation. Could be useful for environmental animation purposes. To test this, I made a prototype actor that incrementally rips tiles out of the terrain layer:
It does the following for every tile in the operating area:
- Create a one-off actor that displays the same tile graphic
- Place that actor where the tile is, then erase the tile from the map
- Program the actor to fall away and self-destruct after 1-2 seconds
You could also do the opposite, constructing a map structure from actors displaying tiles flying in from off-screen.
Rect Mode
Testing a generic red rectangle in place of the player’s sprite.
When this is enabled, sprites display a coloured rectangle with a black outline. I want these as an option for prototyping new actors so that I don’t have to worry about drawing up placeholder art, or even have to think about allocating space on a spritesheet.
Way back when I started working on Bolero as a platformer prototype, rectangles were the only thing it could draw. The devlog pic I used at the time even has the player standing on a floating platform. I’ve come full circle.
Messages / Events / Signals / Counters
Over the course of this project, I have accrued a handful of abstract logic modules which were never thoroughly tested or integrated in any meaningful way. I thought they’d be good for menu systems, creature behavior / AI, and observing statistics over the lifetime of a game session, but I’ve always found a simpler way to accomplish what I need. Lua is really good about that, and it’d probably be a different story if I were writing a game in C or something.
I have now come to the point where I do need some kind of messaging system between entities, and unfortunately I found that my existing stuff wasn’t much help. I think the issue is that I wrote it in the core engine library, but nothing in the core really needs those abstractions. So I gutted it all and started on a much simpler signaling system that is outside of the core, in the toolkit section, closer to the gameplay logic where it’s currently needed.
Here is an example scenario: the player is trapped in a room with an enemy which is blocking the only way out. There are spike pits to the left and right of the player. When this enemy is defeated, a bridge structure needs to appear and grant the player safe exit from the area.
This enemy is not a unique, single-use boss character. It’s something that may appear many times throughout the game, and it’s not a given that it always needs to create a bridge, or that it needs to create the same kind of bridge. Hard-coding checks in every creature is doable, but it won’t scale.
I made a new service and toolkit to handle this. It keeps a table of handler functions for different signals, and these handlers can modify the contents of the sending actor, the actor’s scene, or the scene’s map. There are a couple of automatic signals: one when the service is installed, and one when it’s uninstalled, which roughly coincide with when the actor is created and destroyed. So in Tiled, I can add some properties to a spawnpoint to make a signal emit when that actor is eventually removed:
sig_cleanup = "map_patch" sig_cleanup_a = "bridge" sig_cleanup_b = "left-to-right"
The signaling toolkit will get this message as the actor’s signaling service is uninstalled. The handler for map_patch spawns a special actor that can make changes to a portion of the map (incrementally, left-to-right in this case), and bridge refers to a named rectangular zone and a named point on the map which represent the source tiles to copy and the destination to paste to.
In this specific example, there is the risk of the bridge-making actor not being able to spawn if the scene’s actor array is already maxed out. Ultimately I may need to stuff functionality like that into the main scene context script, but this is good enough to start.
Actors can send signals at will by calling a signalSend method, and I’ve begun using them for various cause-and-effect sequences:
- Doors that trigger map loads
- End-of-level bosses triggering a transition to the next level
- Buttons sending activation signals to make obstacles move out of the way
- Map “trigger zones” emitting signals when the player overlaps their rectangular bounding boxes
I’ve set this up to use five fields: the signal ID (what is supposed to happen), and then four context-specific payload variables (A, B, C and D). It’d be cleaner and easier to combine the signal name and payloads into one string with a delimiter, like “give:magic_key”, maybe even allowing parsing multiple signals in one go. I am hesitant at this time to break out the Lua string library for this purpose, because at this point, I have no idea how often these signals are going to be emitted. I’ll revisit this decision if it becomes impractical. One ID string and four payload variables is enough to identify a specific actor or set of actors by one key field (A) and its current value (B), and set another field (C) to something else (D).
Another example: I made a general-purpose counter / accumulator actor which uses signals to keep a running tally of events and then emits a signal when that tally has reached a certain threshold. Enemies can be set up so that their startup signals add to the counter’s threshold value, and when defeated, add to the counter’s tally. Let’s say that when tally == threshold, the counter’s signal opens a door or something. If the player leaves this particular map and returns, since the enemies we defeated earlier are no longer present, both the tally and threshold remain at 0, as neither are incremented, and so the door still opens. There are potential issues with doing it this way, but it’s good enough for prototyping at this stage.
More Tiled Integration
I got tired of writing out tile attributes in source code like this…
-- Conveyors tile_def = tileset.registerTileDef(my_tileset, 4, 17, "conveyor-lr") tile_def.animated = true tile_def.n_frames = 4 tile_def.speed = 10 tile_def = tileset.registerTileDef(my_tileset, 8, 17, "conveyor-rl") tile_def.animated = true tile_def.n_frames = 4 tile_def.speed = 10
… and decided that I wanted to define them within the Tiled GUI, in the tileset definition, like this…
… which is saved as a TMX (XML) file, like this…
<tile id="2180"> <properties> <property name="animated" type="bool" value="true"/> <property name="n_frames" type="int" value="4"/> <property name="speed" type="int" value="10"/> <property name="terrain_id" value="conveyor-lr"/> </properties> </tile> <tile id="2184"> <properties> <property name="animated" type="bool" value="true"/> <property name="n_frames" type="int" value="4"/> <property name="speed" type="int" value="10"/> <property name="terrain_id" value="conveyor-rl"/> </properties> </tile>
… and then exported to a Lua source file during the build process, like this.
{ id = 2180, properties = { ["animated"] = true, ["n_frames"] = 4, ["speed"] = 10, ["terrain_id"] = "conveyor-lr" } }, { id = 2184, properties = { ["animated"] = true, ["n_frames"] = 4, ["speed"] = 10, ["terrain_id"] = "conveyor-rl" } },
In the process of changing this over, I found that the Tiled TMX-to-Lua exporter actually embeds the tileset metadata into every exported map. This apparently happens for legacy reasons, but it’s unwanted in my case because I’m explicitly exporting the tileset definition to Lua and then linking them together at runtime. I integrated the Serpent Lua serialization library into my packaging script to read in all exported map files, remove the tileset info, and then overwrite them.
When Serpent dumps the contents of a table, by default, it removes whitespace and unnecessary indentation. This is how I learned that my text editor freezes when reading files with very wide lines 🙁
Floating Platforms, Elevators
Wrote some more logic for the floating platforms so that they can move clockwise, counterclockwise, left and right, up and down, diagonally, and also so that they can move along points of a polygon or polyline defined in the map’s shape geometry.
I made an elevator entity that the player can manually direct to discrete stops. Earlier, I had platforms with manual control, but it was just a very plain “hold up to raise the platform, hold down to lower it, let go and it stops” kind of thing. This elevator uses polylines to determine where the stops are, and which stops are terminating ends. As a side effect of sharing code with the other, “non-elevator” platforms that use polylines, the stop-point coordinates can be misaligned, and the lift will still arrive at the correct position, as long as it’s not blocked by the tilemap or another solid actor.
I added a simple failsafe to make the elevator automatically approach the player if they are at the wrong level. It’s not foolproof and needs work, but the general idea is there.
An exaggerated example of a lift with multiple stop points.
I solved one small-but-infuriating visual issue from Hibernator: when the player stands on a platform, the player sprite and the platform sprite shift at different times because their coordinates are floating-point but their sprite coordinates are floored to integers while rendering. This results in visual jitter between the two sprites, and the amount of jitter is dependent on the difference between the decimal part of the player’s coordinates and the decimal part of the platform’s. Scrolling makes the issue much worse. I’ve gone with this solution:
- Record the platform’s floored X and Y values
- When the platform’s current floored XY coord is different from the recorded XY, record the difference between the two, and apply that difference to any actor marked as standing on this platform. Only shift the actors when this condition is true.
Hanging from Poles
Some basic pole-hanging sprites. They look a bit better in motion.
I put together some hanging mechanics for the player that I’m satisfied with. Previous attempts to mock up pixel art for this were hindered by the player’s visual design, particularly the length of his arms, and where they connect to his Humpty Dumpty-like, egg-shaped body. I can extend the arm length and shuffle the connection points a little bit, but it looks like a different character if I go too far. Additionally, his face needs to be visible so that there is a sensible origin point for the spheres that emerge from his mouth. He can’t have his back to the user, and we don’t want the face to be too far right or left, as it may give the incorrect impression that the fully-charged sphere is only protecting one side of the character.
Anyways, I was able to fit him onto the horizontal poles by moving the forehead behind the bar, which leaves room for his hands wrapped around the pole to shuffle closer to the sprite’s center and then back. For vertical poles, he leans outward in his facing direction, and I’ve tweaked the hitbox slightly to be offset as well.
The player’s hanging graphics are split into two separate sprites: one behind the terrain layer, and one in front of it. The effect breaks if the player gets too close to a solid tile while in this state, so I’ve added additional limits to how close the player can get to solid tiles while hanging. It’s not effective in all circumstances, so I’m not 100% happy, but I don’t have a better solution right now and I need to move onto other tasks.
Camera rewrite
Did some refactoring of the camera system. It was in a pretty bad state as a result of an earlier, partial refactoring attempt. It still has a ways to go, and I’ll probably be fiddling with it for the rest of the development of this game.
How it works: rectangular scroll zones are defined as shapes in the map file. Each zone specifies attributes that control how the camera behaves when its target point is overlapping that zone. (Attributes include things like “don’t let the camera move beyond the left edge of this zone.”) If multiple zones overlap each other, then the camera retains the attributes of the last-visited zone until it leaves its boundaries. If the target enters two overlapping zones at the same time, then the zone that comes first in the geometry array is selected as a tie-breaker. When the target is not within any zone, the camera keeps the player dead-center. It’s possible for external scripts or actors to override the zone setting and provide their own parameters, for example to implement special camera rules during a boss fight.
So after cleaning up the module a bit, I added a camera mode that has a gentler upward rate of scrolling. I’m having a hard time explaining how it works, but basically it’s intended to make upward climbs through vertical areas a little easier to track visually. Here is a diagram:
Oops, this diagram isn’t quite correct. It’s supposed to convey that the camera’s target Y coordinate only updates when the player is standing on solid ground, or holding onto a bar, or when they’re in the upper green or lower red sectors of the viewport. So as you skip and hop your way up a set of progressively higher platforms, the screen does scroll up gently, attempting to match the height of the last platform you landed on. It doesn’t just stop the moment you jump.
The initial reason for revisiting the camera code was that since last working on it, I added a 24-pixel-high status bar that’s anchored to the bottom of the screen, and the camera wasn’t aware of it. I ended up solving this outside of the camera module: I made a separate actor that detects overlap with the player, and quickly pushes them through this dead zone area.
Miscellaneous
Cleaned up some code related to taking screenshots. LOVE has a convenience function for this, but I commented out the surrounding code (polling the ‘take screenshot’ key, code to ensure a screenshot directory exists, etc.) when I started work on input binding.
I also made a variant that saves only the contents of the game’s 480×270 virtual canvas directly (so no scaling or interpolation, and no overlay messages). This can be accomplished pretty easily with a few method calls to convert the canvas to ImageData and then encode it to PNG:
-- (LOVE 11.3) local canvas_data = my_canvas:newImageData() local canvas_image = canvas_data:encode("png") local file_ok = love.filesystem.write("canvas_shot.png", canvas_image) if not file_ok then -- Error! end
The imageData:encode() method can take an optional second argument to write the image to disk, so you could do this in two steps instead of three, but I don’t see a return value in the docs to confirm that the action was successful. LOVE’s screenshot function can’t return a status value because the screenshot action is deferred to the end of frame rendering, but it will print an error message to the terminal if it failed.
I made spritesheets autoload. I had to also make animation definitions autoload after spritesheets, as initialization of the former depends on the latter already being set up and available.
These lopsided Falling Frames have no practical purpose, but are kind of cute in motion:
Project Outlook
This month, I got to implement the kinds of prototype sequences that I’ve wanted for a long time. I have enough infrastructure now that I can slap together a basic test enemy pretty quickly. I’m glad I’ve gotten to this point, though I need to change gears soon and work on higher-level ideas for stages and set pieces. I really want there to be a good structure to every level, and not just have each one be a jumble of level design fragments which happen to be linked together (though this could also be what I end up with.)
I was tossing out old sketches once I had prototype implementations working, but now I’m going to try and keep them somewhere in case I need to review them. I also went through the project’s mockup folder and sorted out things that are no longer relevant to the current game, like this completely different protagonist mockup:
Sir Not-Appearing-In-This-Game.
Next status post should be on or shortly after March 31st.