Home

Platformer Tutorial — Part 2: A Small Game About Jumps, Boxes, and Clouds

This is the second part of our Unity platformer tutorial. The first part taught you how to move. This one — lets you build a world worth moving through.

We won’t add enemies with behavior trees. We won’t build menus or transitions. What we’ll do is craft a space with coins, walls, pitfalls, one slightly grumpy mushroom… and something that quietly stays with you all the way to the end.

You’ll place things. You’ll fix a wall that lets rats stick to it. You’ll ride a platform. And when you finally reach the exit — you’ll know it’s not the real reason this level was made.

So open Unity. Take a breath. And begin — not because it’s required, but because **something here is quietly waiting** to be placed.

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.

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: Since we’re using PlayClipAtPoint, the sound plays in world space. If it feels quiet, that’s because the Audio Listener is on the camera — we’ll fix this later.

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:

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 CoinCounterText and make the following adjustments:

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 C# script and name it DeadEndController. Paste in the following code:

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

public class DeadEndController : MonoBehaviour {
    void OnTriggerEnter2D(Collider2D other) {
        if (other.tag == "Player") {
            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.

Tip: 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
{
    // How far the mushroom moves from its start point (in units)
    public float distanceToRun = 2f;

    // Movement speed
    public float speed = 2f;

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

    // Initial position
    Vector3 startPos;

    // Reference to Rigidbody2D
    Rigidbody2D rb;

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

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

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

        if (isLookingLeft)
        {
            // If moved too far to the left, turn
            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 to the right, turn
            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 mushroom direction visually and logically
    void TurnTheMushroom()
    {
        isLookingLeft = !isLookingLeft;
        transform.localScale = new Vector3(
            transform.localScale.x * -1,
            transform.localScale.y,
            transform.localScale.z
        );
    }

    // If rat collides with the mushroom (not from above)
    void OnCollisionEnter2D(Collision2D other)
    {
        if (other.collider.gameObject.tag == "Player")
        {
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
    }

    // If rat touches the trigger on top — destroy mushroom
    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 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 wall sprite and add a Box Collider 2D to it.

Import the Exit sprite (or use the red sign from the existing asset sheet) and drag it into the scene near the top wall. It should be high enough that the rat can’t reach it by jumping alone — the Box will be needed.

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 C# script and name it Parallax. Paste this code:


using UnityEngine;

public class Parallax : MonoBehaviour
{
    // Reference to the player
    private GameObject player;

    // Previous frame position
    private Vector3 lastPosition;

    // Parallax scroll speed
    public float speed = 60f;

    void Start()
    {
        // Find the player using tag
        player = GameObject.FindWithTag("Player");
        if (player != null)
        {
            lastPosition = player.transform.position;
        }
    }

    void Update()
    {
        if (player == null) return;

        // Calculate offset and apply it
        Vector3 offset = player.transform.position - lastPosition;
        transform.Translate(offset * speed * Time.deltaTime);

        // Save current 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, doesn’t even move on its own. 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 C# script called CloudFollower and attach it to the Cloud. Use this code:


using UnityEngine;

public class CloudFollower : MonoBehaviour
{
    public Vector3 offset = new Vector3(-2f, 3f, 0f);
    public float followSpeed = 1.5f;
    private Transform target;

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

    void Update()
    {
        if (target == null) return;

        // Gently follow the player with offset
        Vector3 desiredPos = target.position + offset;
        transform.position = Vector3.Lerp(transform.position, desiredPos, followSpeed * Time.deltaTime);
    }
}

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.