Climbing ladders mechanic in Unity 2D platformer games

  • 07th May 2021
  • by Pav
  • Reading Time: 10 minutes

Introduction

Climbing ladders is quite popular in 2D platformers. This mechanic allows players to easily move their characters above and below different parts of the levels without the need to jump or fall. It introduces another layer of complexity to game environments. In this tutorial we are going to look into how we can create ladders and give our characters the ability to climb them.

climbing stairs pav creations

Table of contents:

  1. The purpose of having ladders in a 2D platformer
  2. Preparing sprites and animations in Aseprite
  3. Implementation of the climbing ladders mechanic

1. The purpose of having ladders in a 2D platformer

Ladders not only provide us with another way of interacting with levels but also offer developers another element that they can use while designing them. Instead of overusing platforms or stairs in strategic locations we can always place a ladder that will fulfill the requirement of moving character up or down. However, while working on the layout of our levels we have to take into consideration the fact that they operate a bit different. From the technical point of view once on a ladder our character shouldn’t be subject to gravitational forces. We want our character to “hold” the ladder at the position the player climbed up to without immediate fall.

That is especially true when we want to place traps along the way. It is not so uncommon to see some kind of fire cannons shooting at characters while climbing a ladder. In order to prevent the damage, the players are forced to stop at certain points. In a way, it creates an additional mini gameplay experience that we cannot convey with standard platforming mechanics. As game designers we need to carefully think what impact on the entire level walkthrough a placement of a ladder in a given location will have. Should the ladder be short or tall? Should there be any traps along the climb? What parts of the level should player reach?

Ladders add another layer of gameplay mechanic to a game that can completely change how players will perceive it. It may look difficult to implement at first, but fortunately Unity provides us with everything we need to quickly manifest this concept within our platformer. In the subsequent chapters I’ll show you step by step how you can develop it. Let’s get right to it!

2. Preparing sprites and animations in Aseprite

As usual, I’m going to start with preparing few simple graphics. First, I’ll draw a ladder that we are going to place in our level. Secondly, I’ll add climbing animation to the list of character’s clips so that we can create a visual cue for the player.

Making a simple ladder

Normally when I work on a 2D game I usually take into consideration the size of a single cell. I’m not necessarily strictly sticking to it a I sometimes need my assets to be slightly bigger or smaller. The size depends on my design requirements. With ladders it’s a little bit different. Since I need to make it “tileable”, I have to add two additional pixels to its bottom. This will become more obvious in chapter 3, but basically I do this to be able to make ladders of different heights using the same asset.

simple ladder sprite for unity 2d game pav creations

Note the spaces between rungs of the ladder. They will play an important role when we place them inside the level. Always try to think about how it would look like when the middle part is continuously being repeated in a tall ladder.

Creating climbing animation

Our character’s animation needs to give a visual cue to the player that he’s climbing a ladder. It will depend on the integral structure of your level. In our case, the ladders will be placed literally in front of us. This means that we will observe how characters climb them from behind their backs.

character's climbing animation sprites pav creations

Although the individual frames may look funny they will make sense once we put our character on a ladder. When you prepare your climbing animations always keep in mind the player’s perspective on the scene. The end animation now look like this:

character climbing animation pav creations
Character “Climbing” Animation

Now we have everything we need to implement our climbing ladders mechanic. Let’s jump into it!

3. Implementation of the climbing ladders mechanic

Before we start writing our scripts we are going to set up a ladder prefab object. That way we’ll be able to reuse it across many game levels, increasing the productivity later on! It’s going to be composed out of 3 child GameObjects that will maintain the information we need to ensure proper climbing controls.

Setting up the ladder GameObject prefab

Back in Unity, after importing and setting up the ladder asset create a new empty GameObject inside your project. Call it “Ladder”. After that add in a sequence the following components: SpriteRenderer, BoxCollider2D and RigidBody2D. In the Inspector panel we now need to configure our ladder’s parameters. This will depend on how tall we want our ladder to be. Let’s say that we want to set the height dimension to 18, which is quite high.

  • In the SpriteRenderer panel set “Draw Mode” to “Tiled”, “Height” to “18” and “Tile Mode” to “Continuous”
  • Depending on the layer structure of your level set the “Order in Layer” so that your ladder appears in front of everything but behind the player and foreground. For instance, if your “Player” is on layer 4, foreground elements on layers 5-6 and the background elements in layers 0-2 then your ladder should be on layer 3.
  • In the BoxCollider2D panel make sure that the “Size” matches the dimensions we set for SpriteRenderer component earlier. In our case it’s 1×18.
  • Inside the RigidBody2D panel set “Body Type” to “Static” and make sure that “Simulated” checkbox is ticked.

Your components should now look like this in the Inspector window:

the ladder prefab settings pav creation
The “LadderHandlers” script

We still need to do few more things before we move on. When players climb ladders we have to know when they reach their top or bottom. Of course there are many ways of approaching this. I’ll simply add two empty child GameObjects and get this information from their Transform data.

In order to save time on positioning the ladder’s top / bottom “tip handlers” manually, I’ll write a script that deals with it. To calculate each tip’s location I’ll use the height value we set up earlier. Add a new script to the parent element (Ladder) and call it LadderHandlers. Inside we have to write a code that will position both objects at the game start. To that end, we’ll use the built-in Awake function.

public class LadderHandlers : MonoBehaviour
{
    void Awake()
    {
        float width = GetComponent<SpriteRenderer>().size.x;
        float height = GetComponent<SpriteRenderer>().size.y;
        Transform topHandler = transform.GetChild(0).transform;
        Transform bottomHandler = transform.GetChild(1).transform;
        topHandler.position    = new Vector3(transform.position.x, transform.position.y + (height / 2), 0);
        bottomHandler.position = new Vector3(transform.position.x, transform.position.y - (height / 2), 0);
        GetComponent<BoxCollider2D>().offset = Vector2.zero;
        GetComponent<BoxCollider2D>().size = new Vector2(width, height);
    }
}

The logic is self-explanatory and straightforward. I use half of the sprite’s height to create an offset and position handlers in their respective locations. To ensure proper sizing of the box collider boundaries I take the dimensions values from the sprite renderer component (lines 11-12). The ladder is almost ready to be placed in the game! The only thing we now have to take care of is the actual solid tile that will allow our characters to step on the upper platforms when they reach the top.

The ladder’s climbing step tile

We have to create a tile that will allow characters to step on the upper platform when they climb a ladder. To do this I’ll add one more child GameObject and call it “LadderTile“. I could position this object with a script but I figured it is best to adjust it manually. The reason for that is the fact that you may have many different areas where you want to add a ladder. The good rule of thumb is to have this mindset that you “fit-ladder-inside-level-platforms” and not vice-versa.

  • Add “RigidBody2D“, “PlatformEffector2D” and “EdgeCollider2D” to the “LadderTileGameObject.
  • In the RigidBody2D component set Body Type to Kinematic and make sure that Simulated checkbox is ticked. The Collision Detection should be set to Discrete.
  • Assuming that we only want our character possess the ability climb ladders, tick Use Collider Mask checkbox inside the PlatformEffector2D configuration panel and set Collider Mask to “Player”. Since we want our character to “step on” the tile once he climbs the ladder, tick Use One Way checkbox and set Surface Arc to 180 degrees.
  • Inside the EdgeCollider2D panel, tick Used By Effector checkbox and set it’s Offset to half of the tile’s height (X: 0, Y: 0.5). This will ensure the proper positioning of the arc. In the Points section make sure that the beginning and end are set to two horizontal edge corners of the tile. To do that, add two elements here with the normalized offsets set at horizontal axes (Size: 2 ; Element 0: X:-0.5, Y: 0 ; Element 1: X:0.5, Y:0).

The final ladder setup should now look like this:

the ladder ready for climbing mechanic in Unity pav creations

This concludes our ladders setup! Let’s move on to granting our character the ability to climb them!

Locking character to a climbed ladder

In this section I will expand the PlayerController script from my previous article. Of course we already did some expansions of that script in the past, for instance, by adding the jumping and crouching skill. In the final version of the game you may wish to do a little organization and spread the player’s controllers across multiple scripts. However, for the sake of this article I’ll focus solely on PlayerController file and only highlighting the code responsible for climbing.

Let’s start by defining few boolean fields that are going to be used to determine the state of the character.

public class PlayerController : MonoBehaviour
{
    ...

    private isFacingRight = true,
            jump = false, 
            jumpHeld = false;
            crouchHeld = false, 
            isUnderPlatform = false,
            isCloseToLadder = false,
            climbHeld = false, 
            hasStartedClimb = false;

    ...
}
                 

As you can see I left in the boolean values controlling the jumping and crouching. The new fields are going to be used to set the correct state of the character:

  • The isCloseToLadder field is set to true when player is near the ladder but has not started climbing yet. It will determine whether character can even start interacting with the ladder at the current position.
  • The field climbHeld is going to be set to true when the player pressed and holds the “Climb” button. You can define a new input button by going to Edit -> Project Settings… -> Input Manager.
  • The hasStartedClimb is the control boolean value that will be used to prevent player’s other actions while climbing (e.g. if you don’t want your character to fall when he’s on the ladder and the player released the button).

Now let’s use these fields in the Update loop to trigger correct climbing animations while preventing clash with the already established ones. I’ll also add a Transform field that will hold reference to a nearby ladder and vertical for capturing player’s input on Y-axis.

public class PlayerController : MonoBehaviour
{
    ...

    private isFacingRight = true,
            jump = false, 
            jumpHeld = false;
            crouchHeld = false, 
            isUnderPlatform = false,
            isCloseToLadder = false,
            climbHeld = false, 
            hasStartedClimb = false;

    private Transform ladder;
    private float vertical = 0f;
    private float climbSpeed = 0.2f;
    ...

    void Start()
    {
        ...
    }
 
    void Update()
    {
        ...

        vertical = Input.GetAxisRaw("Vertical") * climbSpeed;
        
        ...
        
        if (isOnGround() && horizontal.Equals(0) && !isCloseToLadder && (crouchHeld || isUnderPlatform))
            GetComponent<Animator>().Play("CharacterCrouchIdle");
        else if (isOnGround() && !isCloseToLadder && (horizontal > 0 || horizontal < 0) && (crouchHeld || isUnderPlatform))
            GetComponent<Animator>().Play("CharacterCrouch");
        else if(isOnGround() && !hasStartedClimb && horizontal.Equals(0))
            GetComponent<Animator>().Play("CharacterIdle");
        else if(isOnGround() && !hasStartedClimb && (horizontal > 0 || horizontal < 0))
            GetComponent<Animator>().Play("CharacterWalk");
         
        crouchHeld = (isOnGround() && !isCloseToLadder && Input.GetButton("Crouch")) ? true : false;
        climbHeld = (isCloseToLadder && Input.GetButton("Climb")) ? true : false;

        ...

        if (climbHeld)
        {
            if (!hasStartedClimb) hasStartedClimb = true;
        }
        else 
        {
            if (hasStartedClimb)
            {
                GetComponent<Animator>().Play("CharacterClimbIdle");
            }
            ...
        }
    }

    ...

    private void OnTriggerStay2D(Collider2D collision)
    {
        ...

        if (collision.gameObject.tag.Equals("Ladder"))
        {
            isCloseToLadder = true;
            this.ladder = collision.transform;
        }
    }
}

Note how on the highlighted lines I’m interchangeably using isCloseToLadder and hasStartedClimb to prevent clashes with existing animations triggers. On lines 62-71 I’m setting a ladder that is being detected with OnTriggerStay2D method. This gives me access to two child “handler” objects. On line 48 I set climbHeld so that we can use it next.

Setting the climbing state

At this point we need to implement the logic behind climbing mechanic. When the player starts climbing a ladder we don’t want his character to be subject to gravity. This may sound a bit irrational when we think about it in terms of real physical world. However, in a game world the character should be “locked” to a ladder. Indeed, we want to prevent our player from falling whenever he moves up and down while climbing. In Unity we can easily achieve that with just a single line of code – by changing the body type of RigidBody2D component to Kinematic.

...

void Update()
{
    ...

    if (climbHeld)
    {
        if (!hasStartedClimb) hasStartedClimb = true;
    }
    else 
    {
       if (hasStartedClimb)
       {
           GetComponent<Animator>().Play("CharacterClimbIdle");
       }
       ...
    }

    ...
}

void FixedUpdate()
{
    ...
    // Climbing
    if(hasStartedClimb && !climbHeld)
    {
        if(horizontal > 0 || horizontal < 0) ResetClimbing();
    }
    else if(hasStartedClimb && climbHeld)
    {
        float height         = GetComponent<SpriteRenderer>().size.y;
        float topHandlerY    = Half(ladder.transform.GetChild(0).transform.position.y + height);
        float bottomHandlerY = Half(ladder.transform.GetChild(1).transform.position.y + height);
        float transformY     = Half(transform.position.y);
        float transformVY    = transformY + vertical;

        if (transformVY > topHandlerY || transformVY < bottomHandlerY)
        {
            ResetClimbing();
        }
        else if (transformY <= topHandlerY && transformY >= bottomHandlerY)
        {
            rigidBody2D.bodyType = RigidbodyType2D.Kinematic;
            if (!transform.position.x.Equals(ladder.transform.position.x))
                 transform.position = new Vector3(ladder.transform.position.x,transform.position.y,transform.position.z);

            GetComponent<Animator>().Play("CharacterClimb");
            Vector3 forwardDirection = new Vector3(0, transformVY, 0); 
            Vector3 newPos = Vector3.zero;
            if (vertical > 0)
                newPos = transform.position + forwardDirection * Time.deltaTime * climbSpeed;
            else if(vertical < 0)
                newPos = transform.position - forwardDirection * Time.deltaTime * climbSpeed;
            if (newPos != Vector3.zero) rigidBody2D.MovePosition(newPos);
        }
    }
}

...

In the listing above I’m using the locations of both “ladder handlers” to define a range within which I want my character to be able to climb. I do this to prevent the player from missing the either tip of the ladder and climb to infinity (lines 39-43). The Half function is rounding the floating values to the nearest full number taking into account halves. The method ensures crisp positioning of sprites while climbing.

public static float Half(float value)
{
    return Mathf.Floor(value) + 0.5f;
}

On lines 46-47 I’m locking the character to the ladder. The code block on lines 50-56 is the core of the mechanic. First, I’m creating a new forward direction (y-axis) so that I can then calculate the new position of the character. Secondly, I’m subtracting or adding the new value to character’s transform position depending on player’s input for the vertical axis. Note that despite this block being in FixedUpdate method I’m using the Time.deltaTime built-in variable. On line 56 I’m moving the character to new position by using MovePosition member function of RigidBody2D.

Invalidating the climbing state

When the player reaches either tip of the ladder we want to bring him back again under the influence of the gravity. In previous sub chapter I have used ResetClimbing method that is responsible for just that.

private void ResetClimbing()
{
    if(hasStartedClimb)
    {
        hasStartedClimb = false;
        rigidBody2D.bodyType = RigidbodyType2D.Dynamic;
    }
}

If you notice that after reaching the top tip of the ladder your character jitters a bit when moving to adjacent platform cells, you can always round his position on y axis. To that end use the Half method like so:

private void ResetClimbing()
{
    if(hasStartedClimb)
    {
        hasStartedClimb = false;
        rigidBody2D.bodyType = RigidbodyType2D.Dynamic;
        transform.position = new Vector3(transform.position.x, Half(transform.position.y),transform.position.z);
    }
}

Of course you may wish to define some other conditions to add more smooth feeling to climbing controls. For instance, on lines 27-30 in the previous sub chapter listing I’m “releasing” the player if he moves left or right while climbing. That way the character becomes immediately subject to physical forces making the whole climbing experience less stiff.

Congratulations! You have now finished the implementation of the climbing mechanic! You can witness the end result in my last development video.

Conclusion

In this article I have shown you an example implementation of climbing mechanic in a 2D platformer game. I started by briefly stating the purpose of ladders and why they can be useful in enhancing the overall gameplay. I then prepared sprites in Aseprite for the ladder as well as character’s climbing animation. In the last chapter I focused entirely on the implementation inside Unity. I created the ladder prefab and then used it as a reference point for the PlayerController script. That way I was able to lock my character to it whenever the player was nearby in order to climb.