This kills the frog

Remember when I said that making a hole inside a big collider was a good idea? It was not. After rewriting the physics system for the third time, it was time to start working on more fun stuff. The frog death system™.

Something we wanted to improve from the PICO-8 version of the game, was to make it harder for the player to die. The game can be hard, as you need to mix platforming with puzzle solving abilities.

On top of that, dying while pulling the pieces towards you was no fun, so we came up with a system to try to save you, moving you out of danger.

First we figured we only are going to try to save the frog if you are getting squished by a piece that is moving vertically.

The most important part is to figure out which direction we should move you. Our first approach was to compare the piece center with the player center, and move the player to the closest side to freedom.

-- Fake code local pieceCenter = piece:getCenter() local playerCenter = player:getCenter() if playerCenter < pieceCenter then player:moveLeft() else player:moveRight() end

This simple solution had a small problem, our pieces have different shapes and the center is not always, well, in the center.

So we created a small system to place the center of the pieces by hand

Now we have to figure out how many pixels we want the system to move the player each frame. If we move it too much, it would look like we are teleporting the player. If we move it too little, then it can get stuck for many frames and look like the game broke.

We decided we didn't want the player to die until the very last moment. When the piece is squishing it so much that there is no room to escape.

We can get this information based on the overlapping rect between the piece and the player. If the height of the rect is less than 10 pixels (the frog collider is 11 pixels tall), we keep trying to break you free.

We figured that 1 pixel at a time would look the best most of the time. There are some times when you are on top of a piece that's falling and if you pull it towards you, you get stuck sliding between a piece sandwich. So in that specific case, we move you 2 pixels at a time to get you out faster.

Another thing we realized was that it didn't feel good when you died while you were moving in one direction, and our system forced you to move in the opposite direction.

You would still be dead if we moved the direction you were trying to move. But it felt unfair because we were forcing you to move in to a direction you didn't intend.

So now if you press any of the D-PAD arrows while being squished, we force the direction you are pressing to try to get you out. You die more times than before, but it always feels like it's your fault, not the game's fault.

After implementing this, we found another issue. If you stopped pressing the D-PAD after we started moving you, then our system would try to move you in the opposite direction if you hadn't cleared the piece center.

This looked bad as the character was moving without you controlling it in random looking directions.

We decided to save the D-PAD direction if you were pressing it while being squished and try to save you on that direction even if you stopped pressing it.

Sometimes you would only get squished by a small amount on one of the player corners. If you were moving in to the piece and get caught, then our system would pull you in and kill you without anything you could do about it.

We added another rule. If the piece is crushing you by only a small amount, then we try to move you in the closest direction to freedom, regardless of what you are pressing.

Finally, if we fail to save you and the intersection rect between the piece and the frog is taller than 10pixels. This kills the frog.

Here is the code we ended up having, there is a lot of context missing, but helps to understand the system a little better.

function Player:SquishY(other, x, y, w, h, dy) local px = self.x local ox, oy = table.unpack(other.overrideCenter) ox, oy = other.x + ox, other.y + oy local dirX = self.flip and 1 or -1 local correctingDir = self.correctingDir local velX = self.velocity.x local sideDist = 8 local maxSquish = 10 local speed = 1 local cornerThreshold = 4 local speedThreshold = 0.4 if other.targetPosition then speed = 2 end -- When you get squish from a piece that it's moving upwards, -- check if there is a solid on top of you and if there is -- this kills the frog if dy < 0 then if self:checkSolid(0, -1) then return self:die() end end -- The player always overrides the direction where you are going -- to be tried to be saved. -- If the player stops pressing a button we use the last direction -- the user pressed. if (input.btn("left") or velX < -speedThreshold) and w > cornerThreshold then dirX = -1 self.correctingDir = dirX elseif (input.btn("right") or velX > speedThreshold) and w > cornerThreshold then dirX = 1 self.correctingDir = dirX else if correctingDir == nil then if (px < ox) then dirX = -1 elseif (px > ox) then dirX = 1 end else dirX = correctingDir end end if h <= maxSquish then actorCornerCorrect(self, dirX * speed, 0, sideDist, {0}) self.squishedY = true else self:die() end end

Other Posts

You can subscribe via RSS or follow us @amanogames_

On starting a game

On starting a game

A couple of things I would recommend when starting your first game on the Playdate.

How to correct a corner

How to correct a corner

There are many techniques that you can apply so that a platformer game feels good. One of those is corner correction.

On "Bouncy" Animation

On "Bouncy" Animation

Another Equally important decision, is choosing which poses you want to emphasize in order to get that reactive feeling when a character interacts with the world.

The collision stair case

The collision stair case

As stated on the previous post, updating all the pieces all the time was a bad idea. We needed to figure out a way to update only the ones that needed to be updated after another block got destroyed. The quick and dirty solution was to check all the pieces inside a bounding box on top of the piece that got destroyed.

About Amano & the collision conundrum

About Amano & the collision conundrum

So, a couple of months back, Mario and I were happily working away on The game, finding out the workflow and working out the kinks of developing for the PlayDate. We laid down the main mechanic, blocks were falling and colliding correctly the character was moving alright but we were doing everything on the simulator, NOT testing on the actual device. so when we decided to take it for a spin…  it crashed.

Pullfrog postmortem, Long Live Pullfrog 2-Bits

Pullfrog postmortem, Long Live Pullfrog 2-Bits

So towards the end of the year, Mario managed to get his hands on a Development console for the handheld "Playdate" and we decided to attempt do make a second version of Pullfrog, this time featuring a playful little crank and seemingly less restrictions except for the apparent ones like the black and white color of the screen. Oh the naivety.