« Back to Blog

Phaser Platformer Series: 11 Improved Ladders

posted on 24 May 2021

There’s a few kinks to work out with our ladder. We’ve inadvertently made the jumping aspect of the game much worse. The coyote time / edge timer no longer works if you try and jump from the top a ladder. Worse than that, if you are standing on top of the ladder the jumping just seems broken. Jumping is the primary mechanic of our platformer, so if that doesn’t feel good, the game won’t feel good. On mobile going up and down the ladder feels like a bit of a chore and clunky so we need to fix that too.

Here’s what we’ll be making:

See the Pen
Phaser Platformer: 11 Improved Ladders
by Digitherium (@digitherium)
on CodePen.

If you are viewing on a mobile, you can open a version of it here without all the codepen chrome taking up screen space so you play it in landscape mode.

The issue is that we are checking if our hero is on the ladder and if so, we’re drastically changing the physics of the player. If the hero is standing on *top* of the ladder we actually want it to behave just like an ordinary platform. Our hero should be able to run and jump off of it. So as well as checking if the player is on the ladder, we need an extra check to see if they are on top of it.

If we look at the physics of our platforms, they are using a collider not an overlap for collision detection. This means our hero is physically stopped by platforms, they can’t pass through each other – they collide. The collider function is set up with one line of code that pretty much does everything for us. It’s so neat, it’s a shame we can’t just use a collider if we are on top of the ladder, and overlap the rest of the time.

It turns out we can actually do that. We can keep our overlap code between the hero and ladder intact and just add a new collider function.

If you check the phaser 3 documentation for Arcade physics you’ll see the collide function expects the following arguments:

collide(object1 [, object2] [, collideCallback] [, processCallback] [, callbackContext])

At its most basic we just pass in two objects, like the player and the platforms and it does the rest. We can also pass in a callback function to be fired on collision (collideCallback) and a callback function to be fired just *before* the collision code runs (processCallback). In process callback we have to return true or false, and returning false cancels the collision. It’s essentially a check that runs upon collision where we can check things to make sure we want to go ahead… This is perfect for our ladder situation. We can set the player and ladder collide but run a check beforehand to see if the player is on top of the ladder, if not, cancel all collisions.

We add the new collider like this:

this.physics.add.collider(player, ladders, null, checkLadderTop, this);

The main thing happening here is the function checkLadderTop will be called upon collision:

//called when player collides with ladder
function checkLadderTop(player, ladder) {
    /* We check here if our player is higher up than the ladder i.e. if the player is on top of the ladder
    the sprites are positioned from their centres, so we have to add or subtract half their height to find the heroes feet and the top of the ladder. 
    With the player we add half the height so we are checking the positon of their feet. With the ladder we add half the height so we are checking the top of the ladder. We also round the two values differently, floor for the player to give us the smallest number possible and ceil for the ladder height to give us the highest number possible. This deals with any subpixel values.
    */
    if (Math.floor(player.y + (player.height / 2)) <= Math.ceil(ladder.y - (ladder.height / 2))) {
        //if pressing the down key, or touch down return false and cancel collision
        if (cursors.down.isDown || (Math.floor(prevPos) < Math.floor(yPos))) return false;
        //return true making our collision happen i.e. the player can walk on top of the ladder
        else return true;
    }
    //otherwise return false which cancels the collision
    else {
        return false;
    }
}

If our player’s y pos is less than the ladder, that means they are on top of or above the ladder. If the player is below the ladder we return false which cancels the collision. If the player is higher than the ladder we do an extra check for either a down press or a downward movement with the thumb. If we find a downpress or downward movement it means our player is on top of the ladder but trying to descend, so we can just return false and cancel the collision. This means our player can still use the climbing functionality of the ladder even when directly on top of it. Canceling the collision means our overlap function does its thing and set flags that change the physics and movement of the player to act as if they are climbing the ladder and the player isn’t blocked from going downwards by a collision. If we detect the player is on top of the ladder and NOT pressing down then we return true, allowing the collision to go ahead. At that point the built-in arcade physics kicks in, and walking on top of the ladder is just like walking on top of any other platform.

And that’s it. By adding the extra collision, we have a ladder that acts as a platform if the player is above it, and a ladder if the player is below it OR above it and trying to go down.

Onto the mobile side of things. We want ascending and descending the ladder to be automatic, so when once the hero starts climbing the ladder they don’t stop till they either reach the top or turn around. This is a different feel to the desktop version where the player can choose to wait halfway up the ladder if they want, but I think it feels much nicer this way on mobile. All we need to do is change the reset when the player is standing on the ladder when no controls are pressed. Previously we always reset the velocity of the player, but now we only want to do it if they are at the top of the ladder:

if (onLadder) {
    if (Math.floor(player.y + (player.height / 2)) <= Math.ceil(ladder.y - (ladder.height / 2))) {
        player.setVelocityY(0);
    }
}

So that means if they are halfway up or down the ladder, we leave them alone so they can keep on climbing or descending.

This feels much tighter now, and much more playable. Next up Shells.

chromeless mobile version
view all the code on codepen
download the source on github.