Design a Complete 2D Platformer in Unity: A Practical Indie Developer’s Walkthrough
Ever wonder why some 2D platformers feel like a smooth ride while others feel like a clunky experiment? The difference is often not the art or the music, but the way the developer plans and builds the core systems. As an indie dev who has spent countless nights tweaking jump curves and polishing level flow, I want to share a step‑by‑step walk through that helped me turn a simple idea into a playable game. Grab a coffee, open Unity, and let’s get our hands dirty.
Planning Your Game
Define the Core Loop
Before you even open Unity, write down the core loop in one sentence. For my first platformer it was: “Run, jump over obstacles, collect a gem, repeat.” This sentence keeps you focused on what the player should do over and over. Anything that doesn’t serve that loop can be trimmed later.
Sketch the Level Flow
Grab a piece of paper or a digital note app and sketch a rough map of three to five rooms. Mark where enemies, platforms, and collectibles will sit. Keep the first room short and forgiving – it’s the tutorial. The later rooms can introduce new mechanics like moving platforms or double jumps. Seeing the flow on paper saves you from building a level that feels impossible to finish.
Choose Your Tools
Unity’s 2D package is perfect for this kind of game. You’ll need:
- Unity Hub (2022 LTS or newer)
- 2D Sprite Pack (free from the Asset Store works fine)
- A simple code editor – Visual Studio Community is already bundled
No need for fancy third‑party plugins at this stage. Stick to what’s built in; you’ll understand the engine better.
Setting Up the Project
Create a New 2D Project
Open Unity Hub, click “New”, select the 2D template, and name it “PixelForgePlatformer”. Unity will generate a basic scene with a Main Camera already set to orthographic view – perfect for side‑scroll games.
Organize Your Folders
A clean folder structure saves hours later. I use:
Assets/
Sprites/
Scripts/
Scenes/
Audio/
Prefabs/
Put every asset in its proper place right away. It feels like extra work, but it prevents a chaotic mess when the project grows.
Building the Player
The Player Sprite
Import a simple 32×32 pixel character sprite. Drag it into the scene, rename it “Player”, and add a Sprite Renderer component if it isn’t already there. Set the Sorting Layer to “Player” so it draws above the background.
Adding Physics
Add a Rigidbody2D component – this gives the player weight and lets Unity handle gravity. Set Body Type to Dynamic, Gravity Scale to 3 (you can tweak later), and uncheck Simulated for now while you set up the controller.
Add a BoxCollider2D that matches the character’s shape. This collider will detect collisions with the ground and obstacles.
Writing the Movement Script
Create a new C# script called PlayerController.cs in the Scripts folder. Here’s a stripped‑down version that covers walking and jumping:
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 5f;
public float jumpForce = 12f;
private Rigidbody2D rb;
private bool isGrounded;
void Awake()
{
rb = GetComponent<Rigidbody2D>();
}
void Update()
{
float move = Input.GetAxisRaw("Horizontal");
rb.velocity = new Vector2(move * moveSpeed, rb.velocity.y);
if (Input.GetButtonDown("Jump") && isGrounded)
{
rb.velocity = new Vector2(rb.velocity.x, jumpForce);
}
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.contacts[0].normal.y > 0.5f)
isGrounded = true;
}
void OnCollisionExit2D(Collision2D collision)
{
if (collision.contacts.Length > 0 && collision.contacts[0].normal.y > 0.5f)
isGrounded = false;
}
}
Attach the script to the Player object. Press Play – you should be able to move left/right with A/D or arrow keys and jump with Space. If the jump feels floaty, lower jumpForce or increase Gravity Scale.
Polish the Jump
A good platformer jump feels responsive. Add a small “coyote time” (a brief window after leaving a platform where jump still works) and “jump buffering” (registering a jump press just before landing). Both are just a few extra lines of code, but they make the controls feel professional.
Building the World
Tilemaps for Fast Level Design
Unity’s Tilemap system lets you paint levels quickly. Create a new Grid object (right‑click in Hierarchy → 2D Object → Tilemap → Rectangular). Inside the Grid, add a Tilemap and a Tilemap Renderer.
Import a tileset (the free “Kenney Platformer” pack works great). Drag the tiles into the Tile Palette window, then start painting your ground, platforms, and walls. Tilemaps automatically generate colliders if you add a Tilemap Collider 2D component to the Tilemap object.
Adding Collectibles
Create a prefab called “Gem”. It’s just a small sprite with a CircleCollider2D set to “Is Trigger”. Add a script Gem.cs:
using UnityEngine;
public class Gem : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
// You could add a sound or particle here
Destroy(gameObject);
}
}
}
Tag the Player object with “Player” (add a new tag in the inspector). Now place a few gems in the level. When the player touches one, it disappears – a simple reward loop.
Enemies and Simple AI
Basic Enemy Sprite
Duplicate the Player sprite, recolor it, and rename it “Enemy”. Add a Rigidbody2D set to Dynamic, a BoxCollider2D, and a new script PatrolEnemy.cs:
using UnityEngine;
public class PatrolEnemy : MonoBehaviour
{
public float speed = 2f;
public Transform leftPoint;
public Transform rightPoint;
private bool movingRight = true;
void Update()
{
if (movingRight)
{
transform.position += Vector3.right * speed * Time.deltaTime;
if (transform.position.x >= rightPoint.position.x) movingRight = false;
}
else
{
transform.position += Vector3.left * speed * Time.deltaTime;
if (transform.position.x <= leftPoint.position.x) movingRight = true;
}
}
void OnCollisionEnter2D(Collision2D collision)
{
if (collision.collider.CompareTag("Player"))
{
// Simple damage logic – just reload the scene for now
UnityEngine.SceneManagement.SceneManager.LoadScene(
UnityEngine.SceneManagement.SceneManager.GetActiveScene().name);
}
}
}
Create two empty child objects under the enemy called “LeftPoint” and “RightPoint”. Position them where you want the enemy to turn. This gives a basic back‑and‑forth patrol without any fancy pathfinding.
UI and Game Flow
Simple HUD
Add a Canvas (right‑click → UI → Canvas). Set the Render Mode to “Screen Space - Overlay”. Inside the Canvas, add a Text element called “ScoreText”. Create a script ScoreManager.cs:
using UnityEngine;
using UnityEngine.UI;
public class ScoreManager : MonoBehaviour
{
public static int score;
public Text scoreText;
void Update()
{
scoreText.text = "Gems: " + score;
}
public static void AddPoint()
{
score++;
}
}
Call ScoreManager.AddPoint() from the Gem script when a gem is collected. The HUD now updates in real time.
Level Transitions
When the player reaches the right edge of a level, load the next scene. Place an invisible trigger at the exit, tag it “LevelEnd”, and add this to a new script LevelEndTrigger.cs:
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelEndTrigger : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
int next = SceneManager.GetActiveScene().buildIndex + 1;
if (next < SceneManager.sceneCountInBuildSettings)
SceneManager.LoadScene(next);
}
}
}
Add each scene to the Build Settings (File → Build Settings → Add Open Scenes). Now the player can move from one room to the next without a pause.
Polish and Playtesting
Sound Effects
A jump sound, a gem collect chime, and a simple enemy “bump” noise add a lot of feedback. The free “SFXR” tool can generate retro‑style sounds in seconds. Drag the audio clips onto the appropriate objects and call AudioSource.Play() in the scripts.
Camera Follow
Add a simple script to the Main Camera that follows the player smoothly:
using UnityEngine;
public class CameraFollow : MonoBehaviour
{
public Transform target;
public float smoothSpeed = 0.125f;
public Vector3 offset;
void LateUpdate()
{
Vector3 desired = target.position + offset;
Vector3 smoothed = Vector3.Lerp(transform.position, desired, smoothSpeed);
transform.position = new Vector3(smoothed.x, smoothed.y, transform.position.z);
}
}
Set the offset to (0,0,-10) so the camera stays behind the scene. The result is a buttery smooth scroll that feels much more professional.
Playtest Early, Playtest Often
Invite a friend or two to try the first level. Watch where they get stuck, note if the jump feels too high or too low, and adjust the numbers. Small tweaks based on real players make the difference between “fun” and “frustrating”.
Publishing Your First Build
When you feel the game is solid, open File → Build Settings, choose your target platform (Windows, Mac, Linux), and click “Build”. The executable will be ready to share on itch.io or any indie portal you like.
That’s it – a full, working 2D platformer built from scratch in Unity. The steps above cover the basics, but the real magic comes when you layer your own ideas on top: new power‑ups, boss fights, or a story told through pixel art. Keep experimenting, keep iterating, and remember that every polished indie game started as a simple sketch on a napkin.
- → Streamlining Unity Build Processes: Tips to Cut Compile Time by Half @pixelforgestudio
- → From Prototype to Playable: A Step‑by‑Step Guide for Indie Developers to Launch Their First Game @pixelforgestudio
- → A Step-by-Step Guide to Reducing Unity Build Size Without Sacrificing Quality @pixelforgestudio
- → Designing a Core Gameplay Loop: A Practical Guide for Solo Developers @pixelforgestudio
- → Reducing Unity Build Size Without Sacrificing Quality @unityforge