rane's moonsugar musings

in a funk? try a jam

So at some point in May I was getting really burnt out on ProjEx, gluing bits of the stage 1 endboss day after day after day. At the same time a Vtuber I watch (check her out, she's a dear) announced her annual game jam. Two months to make a community themed game. A perfect tiny side project, I thought.

Some quick protos later I thought I might have a game in this. A sidescrolling action platformer, specific genre undecided but I envisioned something of a junction between wario land and sonic. Very pizzatowerish if you will.

buckos

But having a decent main character movement proto is hardly indicative of anything. Work had to be done. Tilesets slapped, characters animated. Animating the player character went pretty well at first, but soon I hit a snag. I wanted the character to recoil in pain if hitting the wall dead on. Sure, Godot has a theoretically neat is_on_wall() function but... it wasn't ideal. For one, the lil' bucko randomly bounced off when climbing up slopes. For another, when hitting the right wall, the guy correctly rebounded to the left. But when hitting the left wall, it still rebounded to the left. What the hell?

Unable to figure out just how did the slope thing happen, I eventually set the "max climbable slope" to be 50 degrees instead of the 45 that's default and used in the tileset I made. That seemed to solve it. The leftward bounce was a bit more confusing.

The code that discerned when and how to bounce checked for following things:

If all these conditions were satisfied, the game called a hurt_the_bucko(from_where) universal function. This function was asking for the coordinates of damaging object to determine which direction to bounce away. It then did a complex dance of disabled properties to make sure the hitboxes... but I'm getting ahead of myself. Eventually the culprit of the wrong bounce was trivial; the code was built upon earlier code was built upon earlier code, before hurt_the_bucko() existed and at some point I used a substraction where there should have been an addition and left it there.

Aargh.

Anyway up next were the enemies. Both enemies and player are of the class CharacterBody2D, which is a middleground between RigidBody2D which can be only moved by physics (i.e. you don't change its position, you tell it "force 200 newtons from the right" and the engine calculates away), and Area2D which can be only moved by altering its position Vector2 property. It's useful since I wanted the enemies to automatically go up and down slopes, and in fact even have a little air time while doing so. As a result the player character and the pests were moving along, yeah, but collision detection... wasn't working.

It wasn't working because these characters actually were tangible objects to each other. That's great, I could push the not-MCP mook around, except if they're tangible then their hitboxes do not overlap (they only touch) outside of rounding errors, and so hit detection routines do not fire off (again, no overlap). How to cope with that?

I coped with that by equipping the player character with a second hitbox, this one an Area2D with a circular CollisionShape2D that was, crucially, just a few pixels bigger than the little guy's CharacterBody collision shape, and tied that Area2D to the code that handled hit detection. It worked great, until it didn't. When the player character came out of its mercy invincibility phase, it often happened that they were already hugging the baddie. And then... nothing happened. Because the signal checked was a self-explanatory body_entered, and there was no body_overlapping signal.

fatass bucko rammed by impotent crayon
he's really not up to it today

But, digging through area-based object specs I found there's a function I can call instead. It returns a list of bodies or areas (two functions) that are inside the CollisionShape right now. So now, whenever the bucko comes out of invincibility, it will look up if anything touching it right now is a hostile body, and if so, bounce again. Some tweaking ensured that this happens only a fraction second after the player gets controls back, so no stunlock occurs. Some more testing later I found out that if the player is caught between the enemy and the wall, some weird behavior and potential glitching may happen. To remedy that I wanted to make the player intangible while invincible.

Well, I got my wish for sure, the first few attempts at coding up a solution made the player totally intangible - so much so, they just went through the ground and fell forever. Tweaking and touching I eventually made sure that the only thing that passed through the character were the enemies, and the properties were restored correctly later.

Next up was the powerbomb. The player character is generally a non-violent sort but to remove enemies and to smash through cracked tiles I gave him a ground smash attack. It definitely worked... but the devil was, as always, in the details. See, the thing is, I wanted the guy to smash through any number of cracked tiles without losing momentum. Nobody likes it when you have to repeate jump-smash five times to get through five tiles on top of each other; however by the time the bucko's hurtbox detected an incoming tile, it was too late to process its explosion before the impact.

The solution? Bucko got a third CollisionShape2D, tied to a second Area2D node, called "pouncebox". This one is a not-too-wide rectangle, starting at the character's bottom and extending about a whole body height further down. This one got tied to the code destroying things that are about to be pounced on, and it worked wonderfully, things were detected early enough to be deleted out of the way in time, but late enough to avoid making it looks like things are deleted without touching.

From then on, things went smoothly more or less, although there was one last real hurdle. You see, one of the gameplay elements I've devised is a button disabling laser gates. But to make it a bit more memorable I've decided that it would not work in an instant; instead an impulse would follow along a track made out of animated objects, and the player could possibly follow what will a button toggle. So the challenge was to make an object that would pass activation information to the next intended object, and so on, until the gate. Crucially, I wanted this to work like automatically; I didn't want to spend any time hard-coding each of the possibly hundreds of tiles where to pass on the signal. So how to do it?

The solution I devised is as follows: each object is an Area2D, whith a - crucially - circular collision shape slightly bigger than the actual sprite, so that it extends past its side, but not past its corners. That way each area overlaps with its neighbors in each cardinal direction, but nowhere else. Then, when the function call to activate arrives, a function is called, with a small delay, that checks for overlapping areas - same method as mentioned above for enemy detection - and if area is of the right type, it is activated as well. Thus, I ensured that the signal propagates in a nice, visualised way that is kind of satisfying.

The project is not over yet; with a month to go I still need to draw some UI bits and, well, design the stages, but I believe that I have enough of the game ready to design a few entertaining stages, just right for a jam.

smug idiot getting his appendage stuck in elevator door

#dev