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.
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:
0
.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:
noFriction
.0
in the Inspector.
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.
noFriction
material assigned to
the rat.
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:
0
.-10
(so it
appears behind the rat).
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:
CoinController
.
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.
Let’s show how many coins the rat has collected using a UI text element.
In the Hierarchy, go to:
Canvas
,
CoinCounterText
, and EventSystem
.
Select the Canvas GameObject. In the Inspector:
Scale With Screen Size
.
Expand
.
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:
CoinCounterText
.0
.
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.
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:
0
.
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.
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:
0
.Interpolate
.
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.
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:
0
.Ground
.
Kinematic
.Interpolate
.
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.
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:
Ground
.
0
.Scale X = 2
, Y = 1.5
).
Interpolate
.
You can now push the box around in the scene — try riding it on the moving platform or using it as a step!
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:
0
.DeadEndController
script (we use it to reload
the scene).
Now when the rat touches the Exit, the scene restarts — but only if you managed to climb up to it.
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:
X: 7
,
Y: 5
(adjust as needed).
-100
so it
renders behind everything else.
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.
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.