Home

Unity 2D Platformer Tutorial — Part 2: Jumping, Boxes & More

Welcome to Part 2 of our Unity 2D platformer tutorial. In Part 1, you learned how to move your character. Now it's time to build a real level — one that feels fun to explore.

Instead of complex enemies or scene transitions, we’ll focus on the environment: coins to collect, walls to climb, pits to fall into, a slightly grumpy mushroom… and something unexpected that stays with you until the end.

You'll place objects in the scene, fix a sticky wall, ride a moving platform, and create basic logic for enemies and level restarts. And when you reach the exit — you might realize it wasn’t really about the exit at all.

So open Unity, take a breath, and begin — not because you have to, but because something quiet is already waiting for you to bring it into the scene.

Step 1. Wall Setup and the Sticky Wall Glitch

We’ll begin by adding a vertical wall to the scene. Use the same largeGround sprite we used for the floor — just rotate it and place it upright. Unity will automatically create a new GameObject. Rename it to Wall.

In the Inspector for the new Wall:

Now press Play and try this: run toward the wall, jump onto it, and hold the movement key. The rat will cling to the wall like a tiny superhero. That’s not magic — it’s friction.

This happens because Unity’s default colliders have built-in friction. To fix it, we’ll assign a Physics Material 2D with zero friction.

In the Assets folder:

Now select the Rat GameObject. In its Capsule Collider 2D component, set the Material to noFriction.

Play the scene again — the rat should no longer get stuck on walls or platforms.

Wall added and noFriction material applied to the rat
Vertical wall added and noFriction material assigned to the rat.

Step 2. Collecting Coins and Playing a Sound

Let’s give our rat something to collect — coins!

Import the coin image into your project: Download the image here (we’ll use the coin from this sprite sheet). Then drag it into the scene — Unity will create a new GameObject.

Select the coin in the Hierarchy, then in the Inspector:

Create a new tag named Coin and assign it to the coin object.

Rename the coin to CoinPrefab, then drag it into the Assets folder to create a prefab.

Unity coin object setup with Circle Collider and Coin tag
The coin object is set up: Circle Collider 2D is added, Is Trigger is enabled, and the Coin tag is assigned.

Now let’s create a script to collect coins. In the Assets folder:

Paste the following code into the script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoinController : MonoBehaviour {

    // Sound to play when collecting a coin
    public AudioClip CoinSound;

    // Coin counter
    public int coin;

    void OnTriggerEnter2D (Collider2D other) {
        // If the object we hit has the Coin tag
        if (other.tag == "Coin") {
            // Increase the coin counter
            coin = coin + 1;

            // Play the coin pickup sound
            AudioSource.PlayClipAtPoint(CoinSound, transform.position);

            // Destroy the collected coin
            Destroy(other.gameObject);
        }
    }
}

Attach the CoinController script to the Rat GameObject. You can download the coin sound here: Download CoinSound.wav. Import the CoinSound clip into the project and assign it to the Coin Sound field of the script in the Inspector.

💡 Tip: We’re using PlayClipAtPoint, so the sound plays in world space. If it sounds too quiet, it’s likely because the Audio Listener is still on the camera. To fix this, either move the camera closer to the player or move the Audio Listener from the camera to the Rat object.

Scene with coin object and CoinController script setup on the rat
Coin added to the scene. CoinController script assigned to the rat with the coin sound connected.

Step 3. Displaying the Coin Counter with TextMeshPro

Let’s show how many coins the rat has collected using a UI text element.

In the Hierarchy, go to:

Select the Canvas GameObject. In the Inspector:

Unity Canvas settings for responsive UI text
Canvas settings: set to Scale With Screen Size with Expand for flexible UI layout.

Double-click the Canvas in the Hierarchy to frame the UI view. You’ll see a white rectangle that represents the visible area of the screen.

Select Text(TMP) and make the following adjustments:

TextMeshPro settings for CoinCounterText in Unity
CoinCounterText settings: font size, alignment, and auto-size enabled.

Now let’s update the CoinController script to connect this text.

using UnityEngine;
using TMPro;

public class CoinController : MonoBehaviour
{
    // coins pick up sound
    public AudioClip CoinSound;

    // coin counter
    public int coin;

    // link to TextMeshPro counter
    public TMP_Text CoinCounterText;

    void OnTriggerEnter2D(Collider2D other)
    {
        // check, if the object has tag Coin
        if (other.tag == "Coin")
        {
            coin = coin + 1;

            // play the pickup sound
            AudioSource.PlayClipAtPoint(CoinSound, transform.position);

            // update the text
            CoinCounterText.text = coin.ToString();

            // remove coin from scene
            Destroy(other.gameObject);
        }
    }
}

Select the Rat in the Hierarchy. In the CoinController component, drag the CoinCounterText GameObject into the Coin Counter Text field.

Now press Play — as you collect coins, the UI will update in real-time.

TextMeshPro UI element displaying coin count
CoinCounterText set up with TextMeshPro and connected to the rat’s script.

Step 4. Creating a Dead End and Restarting the Level

Let’s add a simple challenge: a hole the player can fall into.

Move one of the platforms to create a gap in the floor — this will become a fall zone.

Now right-click in the Hierarchy and choose: Create Empty. Rename the new object to DeadEnd.

In the Inspector, do the following:

Now create a new MonoBehaviour script and name it DeadEndController. Paste in the following code:

using UnityEngine;
using UnityEngine.SceneManagement; // allows scene reloading

// This script is attached to a "dead zone" object (like a pit)
// It restarts the level if the player falls in
public class DeadEndController : MonoBehaviour
{
    // Called when something enters the trigger collider
    void OnTriggerEnter2D(Collider2D other)
    {
        // Check if the object has the "Player" tag
        if (other.tag == "Player")
        {
            // Reload the current scene
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
    }
}

Select the DeadEnd object and attach the DeadEndController script to it. Then create a prefab by dragging DeadEnd from the Hierarchy into the Assets folder.

When the rat falls into the trigger area, the scene reloads — it's a simple way to reset the level without extra setup.

Scene with DeadEnd trigger and controller script
DeadEnd trigger set up beneath the hole. When entered, it restarts the scene.

Step 5. Adding a Simple Enemy (the Mushroom)

Time to add a moving obstacle: a mushroom enemy that walks back and forth. If the rat jumps on its head — it disappears. If the rat touches it from the side — the level resets.

Place a Ground prefab and a DeadEnd below it. Then drag the mushroom sprite from the assets into the scene, onto the last platform. Rename the new object to Enemy.

In the Inspector for the Enemy:

Now create a new script and name it MushroomController. Paste in this code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class MushroomController : MonoBehaviour
{
    // Max distance the mushroom moves from its start position (in Unity units)
    public float distanceToRun = 2f;

    // Movement speed
    public float speed = 2f;

    // Is the mushroom currently looking left?
    public bool isLookingLeft = true;

    // Starting position
    Vector3 startPos;

    // Reference to the Rigidbody2D component
    Rigidbody2D rb;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        startPos = transform.position;

        // Optimize distance check by using squared values
        distanceToRun = distanceToRun * distanceToRun;
    }

    void FixedUpdate()
    {
        // Calculate distance from the start position
        Vector2 dist = transform.position - startPos;

        if (isLookingLeft)
        {
            // If moved too far left — turn around
            if (transform.position.x < startPos.x && dist.sqrMagnitude > distanceToRun)
            {
                TurnTheMushroom();
                rb.linearVelocity = new Vector2(speed, rb.linearVelocity.y);
            }
            else
            {
                rb.linearVelocity = new Vector2(-speed, rb.linearVelocity.y);
            }
        }
        else
        {
            // If moved too far right — turn around
            if (transform.position.x > startPos.x && dist.sqrMagnitude > distanceToRun)
            {
                TurnTheMushroom();
                rb.linearVelocity = new Vector2(-speed, rb.linearVelocity.y);
            }
            else
            {
                rb.linearVelocity = new Vector2(speed, rb.linearVelocity.y);
            }
        }
    }

    // Flip the mushroom visually and logically
    void TurnTheMushroom()
    {
        isLookingLeft = !isLookingLeft;
        transform.localScale = new Vector3(
            transform.localScale.x * -1,
            transform.localScale.y,
            transform.localScale.z
        );
    }

    // Restart the level if the player hits the mushroom from the side
    void OnCollisionEnter2D(Collision2D other)
    {
        if (other.collider.gameObject.tag == "Player")
        {
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
    }

    // Destroy the mushroom if the player jumps on top (trigger zone)
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Player")
        {
            Destroy(gameObject);
        }
    }
}

Attach the MushroomController script to the Enemy object. Adjust Speed and Distance To Run in the Inspector (e.g., both set to 2).

Finally, drag Enemy into your Assets to create a reusable prefab.

Mushroom enemy with colliders, script and prefab setup
Enemy mushroom with two colliders, a Rigidbody2D and the patrol script attached.

Step 6. Creating a Moving Platform

Let's add a vertical moving platform that the rat can ride.

Drag the largeGround sprite into the scene and rename the new object to Platform.

In the Inspector for Platform:

Now we’ll give the platform a script that moves it up and down between two points.

Create a new MonoBehaviour script called PlatformMoving and paste this code:


using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlatformMoving : MonoBehaviour
{
    // Speed of the platform movement
    public float speed;

    // The upper point the platform moves toward
    public Transform endPoint;

    // The starting point (initial position)
    Vector3 startPoint;

    // Reference to Rigidbody2D
    Rigidbody2D rb;

    // Current speed value
    float currentSpeed;

    void Start()
    {
        // Save initial position
        startPoint = transform.position;

        // Get reference to Rigidbody2D
        rb = GetComponent<Rigidbody2D>();

        // Set initial movement direction
        currentSpeed = speed;
    }

    void FixedUpdate()
    {
        // If platform has passed the endpoint, reverse direction
        if (transform.position.y > endPoint.position.y)
            currentSpeed = -speed;

        // If platform has returned to start, reverse again
        if (transform.position.y < startPoint.y)
            currentSpeed = speed;

        // Apply velocity to the Rigidbody
        rb.linearVelocity = new Vector3(0, currentSpeed, 0);
    }
}

In the Hierarchy, create an empty GameObject and name it EndPoint. Optionally assign an icon to make it visible. Place it above the platform using the move tool.

Select the Platform again. Attach the PlatformMoving script. Set Speed to 1 and assign the EndPoint object to the corresponding field.

Play the scene and observe how the platform moves vertically between two positions.

Add a wall to the right of the platform and assign a Box Collider 2D to it. Then place a DeadEnd prefab below the moving platform and adjust its size.

Moving platform setup with Rigidbody, endPoint and script
Moving platform configured with Rigidbody2D, Box Collider, and PlatformMoving script.

Step 7. Adding a Physical Box

Let’s add a box that can interact with the rat and other objects using real physics.

Drag the imageGround sprite into the scene and rename the new object to Box.

In the Inspector for Box:

You can now push the box around in the scene — try riding it on the moving platform or using it as a step!

Box made from imageGround with Rigidbody and Collider
A simple physical box made from the imageGround sprite, placed on the platform.

Step 8. Creating the Exit

Let’s add an Exit to the level. It won’t load a new scene (yet), but it will act as a finish trigger — if you can reach it.

First, add one more Ground prefab where the moving platform will stop — near the top of its path. Then place a wall at the end of the level using the largeGround sprite and add a Box Collider 2D to it.

Drag the Exit sprite from the Assets folder into the scene — it's already included in the project. Place it near the top wall, high enough so the rat can’t reach it on its own. The player will need a Box as a step to get to the exit.

In the Inspector for the Exit object:

Now when the rat touches the Exit, the scene restarts — but only if you managed to climb up to it.

Exit object with collider and controller script
The Exit placed just out of reach — you'll need the box to get there.

Step 9 (Optional). Adding a Moving Background with Parallax

To add a sense of depth, let’s make the background move slightly when the player moves. This is called a parallax effect — it simulates depth by scrolling distant layers more slowly.

You can download the background image here: Download BackGround.png

Import the image into your project and drag it into the scene. In the Inspector:

Create a new MonoBehaviour script and name it Parallax. Paste this code:


using UnityEngine;

public class Parallax : MonoBehaviour
{
    // The player we want to follow
    private GameObject player;

    // Player's position from the previous frame
    private Vector3 lastPosition;

    // Parallax speed (lower = slower background movement)
    public float speed = 60f;

    void Start()
    {
        // Find the player by tag
        player = GameObject.FindWithTag("Player");

        // Store initial position
        if (player != null)
        {
            lastPosition = player.transform.position;
        }
    }

    void Update()
    {
        // Do nothing if player is missing
        if (player == null) return;

        // Calculate movement difference
        Vector3 offset = player.transform.position - lastPosition;

        // Move the background slower than the player for parallax effect
        transform.Translate(offset * speed * Time.deltaTime);

        // Update position for next frame
        lastPosition = player.transform.position;
    }
}

Attach the script to the background object. Set the Speed value — 60 is a good starting point, but you can experiment.

Background object with parallax script and setup
A parallax background moves softly based on the player’s motion, creating a sense of depth.

Step 10. The Cloud That Follows (The Most Important Step)

This is the step everything quietly led to. Not the coins, not the mushroom, not even the exit. This — is the reason.

In the assets, there’s a smiling cloud. It doesn’t attack, doesn’t score points. And yet, it’s the only thing that stays with you — always.

Drag the cloud sprite into the scene and name it Cloud. Set its Z Position to 0 and its Order in Layer to -50, so it stays behind the rat.

Place it somewhere above and behind the rat — like (x - 2, y + 3). It doesn’t need precision. It just needs to be there.

Create a new MonoBehaviour script called CloudFollower and attach it to the Cloud. Use this code:


using UnityEngine;

public class CloudFollower : MonoBehaviour
{
    // Offset of the cloud relative to the player (left and up)
    public Vector3 offset = new Vector3(-2f, 3f, 0f);

    // Speed at which the cloud follows the player
    public float followSpeed = 1.5f;

    // Reference to the target (the player)
    private Transform target;

    void Start()
    {
        // Find the object with the "Player" tag
        GameObject playerObj = GameObject.FindWithTag("Player");
        if (playerObj != null)
        {
            target = playerObj.transform;
        }
    }

    void Update()
    {
        // If target not found — do nothing
        if (target == null) return;

        // Target position: player's position + offset
        Vector3 desiredPos = target.position + offset;

        // Smoothly move the cloud toward the target position
        transform.position = Vector3.Lerp(
            transform.position,       // current position
            desiredPos,               // target position
            followSpeed * Time.deltaTime // interpolation over time
        );
    }
}

The cloud will now follow the rat — not perfectly, not precisely, but with a soft delay. It doesn’t help. It doesn’t talk. It simply stays.

If you slow down or fall — it waits. If you restart — it returns. You’re never alone on this level. Not really.

Cloud floating behind and above the rat
The cloud. The companion. The reason.

Step 11. Coins, mushrooms, and everything that comes next

Now you can add coins and mushrooms. Drop a box under the platform. And — maybe — stay in this level a bit longer than you planned.

It all feels simple. And that's the beauty of it. Don’t try to build the best game in the world right away. First, get comfortable with the basics: movement, collisions, interactions.

Then — you'll create your own game. The one that truly feels like yours.