This month’s prototype level.
Some gameplay footage for the month.
- Added a player ‘above-screen’ indicator
- Cleaned up ‘extra health’ item, added ‘extra life’ item
- Split ‘proto_e’ into submaps and added it to the prototype level set
- Created ‘proto_f’, along with a handful of WIP bosses
- Minor improvements to the actor state system (prompted by first proper use of it in ‘proto_f’)
Here’s a diagram, since it’s difficult to convey in a screenshot.
Balleg already had an indicator for when the player’s shot flies above the viewport. I realized it would be helpful to also have an indicator for the player, in case there are jumps where you have to briefly be above the viewport and therefore out of view. Unlike the shot indicator, this one also adjusts vertically by a small amount according to how far the player is from the top edge.
Life and Health Pickups
Left: Extra life. Right: health replenishment.
There was already a simple health item, depicted as a cup of coffee. I felt the graphic wasn’t large enough to be easily recognizable, and I wasn’t happy with my attempts to draw a larger cup, so I made a new symbol which is very similar to the graphic used for the player’s health indicator in the status bar.
The extra life graphic is a little doll version of the player. I thought I had an item to go with it, but it was really just some sprite art which already appears in the status bar. I added a free-fall variation of the sprite, and another frame for it landing on the ground and compressing briefly.
I coded a property for both items which allows them to float suspended in the air. An additional bubble sprite is set up when this state is active. The player can collect the items in this state, or shoot them to break the bubble and disable the floating state. This is similar to a balloon test actor I made earlier which could hold onto a payload, but IMO this is more appropriate since the hitbox to release the item isn’t hovering above the item itself.
I tweaked the spawnpoint code to allow for spawnpoints which remain “checked out” until a full session reset is applied. This makes it so that extra lives don’t respawn once taken, until the session is truly ended with a game over + continue or new game. As a result, there is a finite number of extra lives per continue.
Yikes -> Mu(tants)
Yikes Mu art that might end up in the menu system.
Decided to drop the Yikes name for the pink enemies and just call them mutants, or ‘mu’ for short in the source code. These guys were initially imagined to be like aliens who would abduct the world’s people and fauna. They’ve changed roles since then, and essentially are the world’s fauna, or the organic counterpart to the bot enemies.
I’m basically just tired of reading the word Yikes over and over.
Proto Level F
This was intended to be a mountain level concept, though I didn’t get any tileset or background art done for it. It’s the first level I’ve done since changing the world format to use managed transfers between small maps, and it’s also the first time that I haven’t been recycling previously-made enemies from old test maps.
Not literal meatballs.
I got hooked on the idea of building several hazards out of one shared boulder-like enemy. So far, they only feature as standalone hazards in a couple of rooms, though the sprite is reused for components of some minibosses in Proto F. Maybe I can do more with them later.
I doctored this image because it’s hard to get a good screenshot of these enemies together.
‘Satyr’ would have been more accurate, but oh well. This is a one-off enemy with two purposes in life:
- Jump on the player’s head
- Ram the player off ledges
Ramming takes precedence, if the goatman and the player are in close proximity and on the same tile row. Goatman can jump over ledges, and will pull himself back from pits if stepping out too far. If the player is out of Goatman’s jumping range, then he settles for a shorter distance, taking pits into account.
As an individual, goatman is pretty good at landing jumps. Not perfect, but he usually gets past the pit. As a group, goatmen frequently push each other off as a result of not coordinating their actions. It might be possible to mitigate this somewhat by having goatmen lock and unlock landing points when they plan their jumps. That wouldn’t help with mid-air collisions, though.
The implementation of this actor requires a very specific terrain layout in order to function: all terrain needs to be tilemapped, there cannot be floors stacked on top of other floors, and the pits either need to be 64 pixels wide or have a bit of height variation (or else they’ll get stuck trying to ram the player from afar when they should really be jumping.) So it’s very fragile, but I’m planning on only having them in one or two areas that adhere to these rules. Eventually I plan to replace the tile-scanning logic with code that loops through a set of rectangle geometry objects which represent safe landing areas.
Top: The current implementation, with no artwork integrated. Bottom: some WIP art for the boss.
Goatboss is a big version of the goatman enemy. It performs a sequence of attacks while switching between vulnerable and invulnerable states.
What initially appears to be a door leading to the next room is in fact a kind of transport vehicle. It attempts to knock you off of a cliff by driving in reverse and firing three kinds of shots. You defeat it by shooting the driver, who is in the compartment to the left. The unimplemented final behavior is to have the driver slump over and drive at full speed back through the hole that he reversed out of. You would then climb over the wreckage of the vehicle in the next room.
As-is, this boss is far too difficult. He should be a small diversion and a cute surprise for new players. It may be worth getting rid of the shots entirely and just making him slowly reverse towards the player.
That’s one awkward mid-air pose.
A spiked column prevents further travel through the room. (Uh, the spikes have yet to be added.) It has a vulnerable target object which shifts up and down while facing the player. Successfully hitting the target causes it to become temporarily invulnerable and spin around the column. Then it emits a persistent bouncing hazard. These hazards accumulate until the column boss is killed.
The initial idea was to have the target continuously spin around the column, making it possible to hit only about 50% of the time. Successful hits would briefly raise the target’s RPM. I also experimented with extending the target out a bit based on current RPM. This looked cool, but made it very difficult to consistently hit the target again until its RPM had lowered back to the original rate. Missing shot after shot isn’t fun.
I toyed with the column slowly closing in on the player, but it crowds the existing bouncing plasma orbs so much that it becomes unplayable. I have some other ideas for variations on this boss, either appearing in other levels or as a series of similar challenges in one particular area.
This is a snake-like creature which slowly rotates toward the player and appends new meatball nodes every time the player lands a shot on its head. The nodes position themselves according to a ring buffer of past head positions.
I sketched out some ideas for a boss that would create and position Meatballs for various attack patterns. I ended up with a pogo-stick attack, a bouncing snake, and a “punch” attack where a line of Meatballs circle the boss and then extend out towards the player.
The results so far are underwhelming. It can probably be shaped into a more interesting boss with some more time.
The ActorState routine for Rockboss.
Up to this point, the ActorState system has been used almost exclusively to implement a generic dormant state for actors, or to pick from one of a few states with no further switching. This month is the first time that I’ve attempted to write “sequenced” actors, primarily bosses and mini-bosses, using ActorStates. (Such actors have sort of existed before this using chains of if-elseif-else in single callbacks, but it’s difficult to modify that kind of code or spin it off into another behavior.) This work has exposed some flaws in the ActorState code that I’m working on cleaning up.
I modified the state-changing actor methods to return constants to the scene runner. This allows the scene loop to handle actor states with a bit more context:
- Increment the actor’s state timer, or leave it alone? If the actor just changed states and the new state hasn’t gotten a chance to run, then leave it at zero.
- Run the actor state callback again in a loop? This allows running multiple state callbacks in sequence in one tick. To prevent infinite loops, there is a failsafe of 64 loops maximum.
I’ve found that returning the result of functions like self:stateJump() is easier than calling it and then writing a separate return statement. Similarly, when getting rid of an actor, writing return self:remove() prevents additional logic in the state callback from being executed.
Anyways, I made these changes mainly because I wanted states which have not yet actually executed to start with self.timer set to 0. That way, the state can implement initialization logic for the state without having to rely on a separate onEnter() callback.
Bug of the Month
When entering a level, any dormant enemies close to the world’s origin point (x0, y0) would instantly wake up on the first tick, regardless of their distance from the player.
Sleeping enemies are supposed to become active when they are sufficiently close to the in-game camera. There was a brief moment in the level initialization tick when the camera was not initialized. By the end of the the tick, it was in the correct position, but for a moment in-between, its default XY position in the world was x0, y0. Therefore, any enemy around that world-space location would activate (plus all other enemies belonging to the same wake-group, if that was set up.)
Why wasn’t this noticed earlier? The player often starts around x0, y0, effectively masking the issue until now.
Proto F isn’t everything I hoped it would be, but the miniboss work and improvements to the ActorState system bode well. I need to clean up some issues with the game engine before proceeding further, then hopefully I can work on more content again. I’ll post another update near the end of next month, hopefully.
Codebase Issue Tickets: 44 (-1)
Total LOC: 123266 (+5539)
Core LOC: 36097 (+223)
Estimated Play-through Time: 11:09.46 (+4:57)