Bolero Devlog (October 2020)

Some no-art prototype structures: oscillating pillars with holes at their midpoints, which are constructed from multiple actors. Red regions are hazardous, blue regions are safe to touch. The white region is a hazard that can be destroyed by the player’s shot.

 

At the end of September, I was pretty worried about inconsistencies in the behavior of the player’s shot. I tore the code apart to try and make it work better, and this lead to a redesign of the attack system. I got a lot of small tasks done, and made basic versions of various platforming devices, though I still haven’t reached the point where I’m making playable levels.

Attack Redesign

So it became clear this month that I can’t rely on the player’s shot self-destructing after a few surface impacts. It worked good 99% of the time, but when it didn’t, it really poisoned the experience. The two main situations where this happened were:

  • The shot skips nearly parallel across a gentle incline, rapidly touching the surface over and over, and then vanishing in less than half a second.
  • An upward shot lands on a ledge in such a way that it just barely makes it. Because the shot’s vertical momentum is mostly exhausted, it just tap-tap-taps against the surface and disappears.

Here’s a diagram of both issues:

 

I tried enforcing a minimum bounce velocity (like if the sphere’s Y velocity was less than 128 pixels per second, then force it to be the negative of that), and later, implementing an auxiliary “rolling” state for the shot when its movement vector is close to parallel with surface angles. But this stuff makes the sphere’s movement less predictable, and that’s not a good feature for a projectile which could ricochet back and hurt the player.

The shot now self-destructs three seconds after being fired, which is a lot easier to keep track of.

I struck on an idea that I really liked: when you hold the attack button and collide with one of your own shots, you pick it back up, and can immediately relaunch it or use it as a shield. In the right circumstances, this can be much faster than creating and firing new shots.

After a day or two of playing around with this, I completely removed the sphere’s “hazardous to player” attribute. I liked this anti-feature, but it only worked well when the shot was 100% something to be avoided once launched. Now, with catching, taking damage because you weren’t holding the action button at the right moment feels pretty cheap.

I had a hard time figuring out how to modestly signal to the user that they can grab shots while holding the attack button. I ended up drawing variations of the player’s existing graphics with his arms outstretched in three directions (up, forward, down). I also experimented with making the shot steer towards the player when his hands are out, but it felt too powerful.

He kind of looks like a child single-mindedly chasing after a runaway toy, which is not what I intended, but I think I’ll keep it:

 

One last problem. The player can grab and hang off of horizontal bars with two hands. How can he hold onto the bar, continue shifting left and right, and also reach out to catch a shot at the same time? Instead of making him let go of the bar with one hand (and having to deal with the mobility issues that would ensue — this would be the only catching variation to impose movement limits), I shaped his legs to kind of look like a claw crane. It doesn’t quite make sense, but combined with the player’s facial expression changing, and looking up or down depending on the location of the shot, I think it’s just enough to convey that you can retrieve the shot.

 

 

Collision Sub-shapes

Testing for overlap between two right triangle subshapes. When an overlap is detected, the player debug-draws a persistent red dot at center XY.

I added sub-shapes to the actor-to-actor hitboxes. They’re intended for some special cases (namely certain kinds of projectiles and targets — maybe < 5% of all in-game actors) and they won’t have widespread support across the platforming module. The sub-shapes are:

  • Rectangle (default, just the already-defined hitbox.)
  • Circle, with a radius of hitbox_width / 2
  • Two diagonal lines, one top-left to bottom-right (“backslash”), and one bottom-left to top-right (“forward slash”)
  • Four right triangles, with the hypotenuse facing upper-left, upper-right, bottom-left and bottom-right.

Because this is being hacked in after the fact, the sub-shape dimensions are always based on the size of the bounding box and not the other way around.

Top: the eight sub-shapes. Bottom: some sub-shape examples with non-square bounding boxes.

I needed separate functions to compare each kind of shape against each other kind. I found 2DEngine’s collision detection tutorial super helpful, along with the eBook Collision Detection by Jeffrey Thompson. Once those were in place, I made a 2D array of functions to account for each combo.

Regarding support in the platforming module: actors which are blocking (behave like floors, walls or ceilings) won’t take hitbox subshapes into account. There are too many special cases to deal with, and I’m not sure I can fully resolve them. In particular, transitions from walking on non-rectangular blocking actors to walking on tilemapped terrain would be really tricky, and I have no idea where I’d even start with circles.

 

Miscellaneous

I discarded a ‘command console’ actor. The player was able to hop into it, press buttons which forwarded signals to other actors, and then hop back out. I liked it, but couldn’t figure out how to make it coexist with the game’s primary charge -> throw action. You couldn’t charge while in the console seat, and you couldn’t get into the seat while holding a charge. It would be better suited to an adventure or puzzle game.

 

I added actor-based versions of hangbars and bodies of water. Before this month, all bars and water were tilemap-based. Extending this behavior has been on my todo list for a long time, as it would open up many new possibilities for getting around levels.

I’ve been gravitating toward general-purpose actors for things like basic floating platforms, ‘hazardous’ regions of larger structures, and other modular things which don’t really do much on their own. Many of these actors are really, really similar, and it makes sense in those cases to just spawn a base actor and twiddle the dimensions and graphics a bit.

I added some camera settings to optionally stop the player from wandering off the bounds of the visible screen area. While I would prefer to never have invisible barriers, there are cases (some kinds of boss fights) where allowing the player to wander off would look silly or cause scripting issues. Sometimes visible mechanisms to corral the player draw too much attention to themselves.

I made a deferred script trigger / queue “job” system. The main use for this is to add a bit of idle time between defeating mini-bosses and opening the path forward, for pacing reasons.

I’ve been struggling with how to implement actor generators because I’m not sure if the game should even have continuously respawning enemies. They cause a loophole in the score system, and if the generator somehow breaks and spawns enemies into empty space beyond the bounds of the play area, it could cause a resource leak. At the same time, if a level design calls for constant pressure from one class of enemies, then it looks silly if they just stop appearing. For now, to keep things relatively simple, I’m implementing generators as actors, and I’m only dealing with actors spawning in from the left and right sides.

 

Project Outlook

Inching along. I was a bit worried about how I started this month, but I’m pleased with the changes to the player’s attack, and I did OK with actor prototyping. My goal for November is to transition from making jumbled test rooms to more structured prototype levels. I’ll post again around the end of next month.

 

Stats

I neglected to collect stats for the first two days of the month.

Codebase issue tickets:

 

Project lines of code:

Leave a Comment

Your email address will not be published. Required fields are marked *