Unity Beginner Tutorial

Build Flappy Bird in Unity

A complete, step-by-step guide to building your first Unity game from scratch. No experience needed — just follow along.

Difficulty: Beginner | 10 Steps | Unity 2022+ | C#

What You'll Build

By the end of this tutorial you'll have a fully working Flappy Bird clone: a bird that flaps when you press Space, pipes that scroll across the screen, a score counter, and a game-over screen with restart. Everything is done in Unity 2D with C# scripts.

Unity EditorHow to navigate, create objects, and use the Inspector
GameObjectsSprites, colliders, rigidbodies, and prefabs
C# ScriptingVariables, functions, Input, and physics
Game LogicSpawning, scoring, game states, and UI
Prerequisites: A computer (Windows or Mac), an internet connection to download Unity, and about 2 hours of time. That's it — no coding or game dev experience needed.

1

Install Unity

Unity uses a launcher called Unity Hub to manage your editor versions and projects.

  1. Go to unity.com/download and download Unity Hub.
  2. Install and open Unity Hub.
  3. Create a free Unity account (or sign in with Google).
  4. In Unity Hub, click Installs in the left sidebar.
  5. Click Install Editor and choose the latest LTS (Long Term Support) version — this is the most stable.
  6. On the modules screen, you only need the defaults. Click Install.
Tip: The download is around 2–4 GB. While it installs, keep reading ahead so you're ready to go.
Which version? Any Unity 2022.3 or newer will work. The screenshots in this tutorial use 2022.3 LTS but the steps are the same across versions.

2

Create the Project

  1. Open Unity Hub and click New Project (top-right).
  2. Select the 2D (Built-in Render Pipeline) template.
  3. Name it FlappyBird.
  4. Choose a folder to save it in (your Desktop or Documents folder is fine).
  5. Click Create project.

Unity will open the editor. It takes a minute the first time. When it loads, you'll see:

Scene View
where you see your game world

Game View
what the player actually sees

Hierarchy
list of all objects in the scene

Inspector
properties of the selected object

Project
your files (scripts, sprites, etc.)
Tip: If any panel is missing, go to Window in the top menu bar and re-open it.

3

Set Up the Scene

3a. Set the background color

  1. In the Hierarchy, click on Main Camera.
  2. In the Inspector, find the Camera component.
  3. Click the Background color swatch.
  4. Set it to a sky blue color — try #4EC0CA (R: 78, G: 192, B: 202).

3b. Set the camera size

  1. Still on the Main Camera, find Size in the Camera component.
  2. Set it to 5. This controls how much of the world is visible.

3c. Create a ground

  1. In the Hierarchy, right-click and choose 2D Object > Sprites > Square.
  2. Rename it to Ground (click on it in the Hierarchy and press F2).
  3. In the Inspector, set its Transform values:
    • Position: X: 0, Y: -4.5, Z: 0
    • Scale: X: 20, Y: 1, Z: 1
  4. Click the Color swatch on the Sprite Renderer and set it to a ground color like #DED895 (sandy yellow).
  5. Click Add Component in the Inspector, search for Box Collider 2D, and add it.
What's a Collider? A collider is an invisible shape that lets Unity detect when objects touch. The ground needs one so the bird can crash into it.

4

Create the Bird

  1. In the Hierarchy, right-click and choose 2D Object > Sprites > Circle.
  2. Rename it to Bird.
  3. In the Inspector, set its Transform:
    • Position: X: -2, Y: 0, Z: 0
    • Scale: X: 0.5, Y: 0.5, Z: 1
  4. Change the Sprite Renderer color to yellow: #F2D831.

4a. Add physics to the bird

  1. With the Bird selected, click Add Component.
  2. Search for Rigidbody 2D and add it.
  3. In the Rigidbody 2D component, set Gravity Scale to 1.5 (makes the bird fall faster and feel snappier).
  4. Set Collision Detection to Continuous.

4b. Add a collider to the bird

  1. Click Add Component again.
  2. Search for Circle Collider 2D and add it.
  3. The collider auto-fits the circle sprite. No changes needed.
What's a Rigidbody? A Rigidbody tells Unity "this object is affected by physics." Once added, the bird will fall due to gravity. Hit Play (the triangle at the top) to test it — the bird should fall and land on the ground.

Press the Play button at the top of the editor to test. The bird should fall and hit the ground. Press Play again to stop.


5

Bird Flap Script

Now let's make the bird flap when you press Space. This is your first C# script.

5a. Create the script

  1. In the Project panel (bottom of the screen), right-click in the Assets folder.
  2. Choose Create > Folder and name it Scripts.
  3. Open the Scripts folder, right-click inside, and choose Create > C# Script.
  4. Name it exactly BirdController (spelling and capitalization matter!).
  5. Double-click the script to open it in your code editor.
Code editor: Unity comes with Visual Studio. If it asks you to install it, go ahead. You can also use VS Code — either works fine.

5b. Write the code

Replace everything in the file with this:

BirdController.cs
using UnityEngine;

public class BirdController : MonoBehaviour
{
    // How hard the bird flaps upward
    public float flapForce = 6f;

    // Reference to the bird's physics body
    private Rigidbody2D rb;

    void Start()
    {
        // Get the Rigidbody2D component attached to this object
        rb = GetComponent<Rigidbody2D>();
    }

    void Update()
    {
        // When the player presses Space (or clicks), flap!
        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0))
        {
            Flap();
        }
    }

    void Flap()
    {
        // Reset the bird's vertical speed, then push it up
        rb.linearVelocity = Vector2.zero;
        rb.AddForce(Vector2.up * flapForce, ForceMode2D.Impulse);
    }
}

5c. Attach the script to the bird

  1. Save the script (Ctrl+S / Cmd+S) and switch back to Unity.
  2. Click on the Bird in the Hierarchy.
  3. Drag the BirdController script from the Project panel onto the Inspector (or click Add Component and search for it).

Press Play and hit Space. The bird should flap upward each time you press it!

How this works:
Start() runs once when the game begins — we use it to grab the Rigidbody.
Update() runs every single frame — we check for Space here.
Flap() resets the velocity (so flaps feel consistent) and pushes the bird up.

5d. Add rotation (optional but looks nice)

To make the bird tilt up when flapping and tilt down when falling, update the script — add this inside Update(), after the if statement:

BirdController.cs — add to Update()
        // Tilt the bird based on its vertical speed
        float tilt = Mathf.Clamp(rb.linearVelocity.y * 5f, -90f, 30f);
        transform.rotation = Quaternion.Euler(0, 0, tilt);

6

Create the Pipes

The pipes are pairs of tall rectangles with a gap in the middle. We'll build one pair, then turn it into a prefab so we can spawn copies.

6a. Create an empty parent

  1. In the Hierarchy, right-click and choose Create Empty.
  2. Rename it to Pipe.
  3. Set its Position to X: 5, Y: 0, Z: 0.

6b. Create the top pipe

  1. Right-click on the Pipe object in the Hierarchy and choose 2D Object > Sprites > Square.
  2. Rename it to TopPipe.
  3. Set its Transform:
    • Position: X: 0, Y: 5, Z: 0
    • Scale: X: 1, Y: 8, Z: 1
  4. Set its color to green: #73BF2E.
  5. Add a Box Collider 2D component to it.

6c. Create the bottom pipe

  1. Right-click on Pipe again > 2D Object > Sprites > Square.
  2. Rename it to BottomPipe.
  3. Set its Transform:
    • Position: X: 0, Y: -5, Z: 0
    • Scale: X: 1, Y: 8, Z: 1
  4. Set its color to the same green: #73BF2E.
  5. Add a Box Collider 2D.
The gap: The top pipe is at Y: 5, the bottom is at Y: -5. Each is 8 units tall. Since they're centered on their positions, the top pipe covers Y: 1 to Y: 9, and the bottom covers Y: -9 to Y: -1. That leaves a 2-unit gap in the middle (Y: -1 to Y: 1). You can adjust this later.

6d. Add the pipe movement script

Create a new C# script in your Scripts folder called PipeMove:

PipeMove.cs
using UnityEngine;

public class PipeMove : MonoBehaviour
{
    // How fast the pipes scroll to the left
    public float speed = 3f;

    // How far off-screen before we delete this pipe
    public float destroyX = -10f;

    void Update()
    {
        // Move the pipe to the left every frame
        transform.position += Vector3.left * speed * Time.deltaTime;

        // Destroy the pipe once it's off-screen
        if (transform.position.x < destroyX)
        {
            Destroy(gameObject);
        }
    }
}
  1. Attach PipeMove to the Pipe parent object (not the individual top/bottom pipes).

6e. Make it a prefab

  1. In the Project panel, create a new folder called Prefabs.
  2. Drag the Pipe object from the Hierarchy into the Prefabs folder. It will turn blue in the Hierarchy — that means it's now a prefab.
  3. Delete the Pipe from the Hierarchy (right-click > Delete). We'll spawn copies from the prefab instead.
What's a Prefab? A prefab is a saved template of a GameObject. Think of it like a cookie cutter — you design one pipe pair, save it as a prefab, and then stamp out as many copies as you need during the game.

7

Pipe Spawner

We need something to keep creating new pipe pairs at regular intervals with random heights.

7a. Create the spawner object

  1. In the Hierarchy, right-click > Create Empty.
  2. Rename it to PipeSpawner.
  3. Position doesn't matter — it won't be visible.

7b. Create the spawner script

PipeSpawner.cs
using UnityEngine;

public class PipeSpawner : MonoBehaviour
{
    // Drag your Pipe prefab here in the Inspector
    public GameObject pipePrefab;

    // Where pipes spawn (off-screen right)
    public float spawnX = 8f;

    // How far up or down the pipes can shift
    public float heightRange = 1.5f;

    // Seconds between each new pipe pair
    public float spawnInterval = 2f;

    private float timer;

    void Update()
    {
        timer += Time.deltaTime;

        if (timer >= spawnInterval)
        {
            SpawnPipe();
            timer = 0f;
        }
    }

    void SpawnPipe()
    {
        // Pick a random height offset
        float randomY = Random.Range(-heightRange, heightRange);

        // Create a new pipe pair at the spawn position
        Vector3 spawnPos = new Vector3(spawnX, randomY, 0);
        Instantiate(pipePrefab, spawnPos, Quaternion.identity);
    }
}

7c. Connect the prefab

  1. Attach the PipeSpawner script to the PipeSpawner object.
  2. In the Inspector, you'll see a field called Pipe Prefab with "None."
  3. Drag your Pipe prefab from the Prefabs folder into that field.

Press Play. Pipes should now appear from the right side and scroll left. The bird should be able to flap through the gaps!

Pipes not appearing? Double-check that you dragged the prefab (from the Project panel), not a scene object. The Pipe Prefab field should show "Pipe" with a small blue cube icon.

8

Scoring System

The score goes up by 1 each time the bird passes through a pipe gap. We do this with an invisible trigger collider between the pipes.

8a. Add a score zone to the pipe prefab

  1. In the Project panel, double-click the Pipe prefab to open it.
  2. Right-click the Pipe root > Create Empty. Rename it to ScoreZone.
  3. Set its Position to X: 0, Y: 0, Z: 0.
  4. Add a Box Collider 2D to ScoreZone.
  5. Check the Is Trigger box on the collider.
  6. Set the collider Size to X: 0.5, Y: 3 (covers the gap).
  7. Click the < back arrow in the Hierarchy header to exit prefab editing mode.
Trigger vs Collider: A regular collider is solid — things bounce off it. A trigger collider isn't solid — things pass through it, but it sends a signal when something enters. Perfect for detecting "the bird passed through."

8b. Add the score tag

  1. Open the Pipe prefab again.
  2. Select the ScoreZone child object.
  3. At the top of the Inspector, click the Tag dropdown.
  4. Click Add Tag, then click the + button.
  5. Type ScoreZone and save.
  6. Go back to ScoreZone and set its Tag to ScoreZone.

8c. Create the UI text

  1. Back in the main scene, go to the Hierarchy.
  2. Right-click > UI > Text - TextMeshPro.
  3. If prompted to import TMP Essentials, click Import.
  4. Rename the text object to ScoreText.
  5. In the Inspector, set the text to 0.
  6. Set Font Size to 72.
  7. Set Alignment to center (both horizontal and vertical).
  8. Set the Color to white.
  9. On the Rect Transform, set anchors to top-center and position to X: 0, Y: -80.

8d. Create the GameManager script

This script tracks the score and controls game state:

GameManager.cs
using UnityEngine;
using TMPro;

public class GameManager : MonoBehaviour
{
    // A static reference so other scripts can access this easily
    public static GameManager Instance;

    public TextMeshProUGUI scoreText;

    private int score;

    void Awake()
    {
        // Set up the singleton so we can call GameManager.Instance
        Instance = this;
    }

    void Start()
    {
        score = 0;
        UpdateScoreDisplay();
    }

    public void AddScore()
    {
        score++;
        UpdateScoreDisplay();
    }

    void UpdateScoreDisplay()
    {
        scoreText.text = score.ToString();
    }

    public int GetScore()
    {
        return score;
    }
}
  1. Create an empty GameObject called GameManager.
  2. Attach the GameManager script to it.
  3. Drag the ScoreText object into the Score Text field in the Inspector.

8e. Detect scoring on the bird

Add this method to your BirdController.cs script, right below the Flap() method:

BirdController.cs — add this method
    void OnTriggerEnter2D(Collider2D other)
    {
        // When the bird passes through the score zone
        if (other.CompareTag("ScoreZone"))
        {
            GameManager.Instance.AddScore();
        }
    }

Press Play. Fly through pipes and the score should count up!


9

Game Over & Restart

When the bird hits a pipe or the ground, the game should stop and show a game-over screen.

9a. Create the Game Over UI

  1. In the Hierarchy, right-click the Canvas > UI > Panel.
  2. Rename it to GameOverPanel.
  3. Set the Image color to semi-transparent black: RGBA(0, 0, 0, 180).
  4. Right-click GameOverPanel > UI > Text - TextMeshPro. Set the text to Game Over, font size 48, white, centered.
  5. Right-click GameOverPanel > UI > Button - TextMeshPro. Rename it RestartButton.
  6. Set the button text to Play Again.
  7. Turn off the GameOverPanel by unchecking the checkbox at the top of the Inspector. We'll show it when the player dies.

9b. Update GameManager for game over

Replace your GameManager.cs with this updated version:

GameManager.cs — full updated version
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    public TextMeshProUGUI scoreText;
    public GameObject gameOverPanel;

    private int score;
    private bool isGameOver;

    void Awake()
    {
        Instance = this;
    }

    void Start()
    {
        score = 0;
        isGameOver = false;
        UpdateScoreDisplay();

        // Make sure the game over panel is hidden
        gameOverPanel.SetActive(false);
    }

    public void AddScore()
    {
        if (isGameOver) return;

        score++;
        UpdateScoreDisplay();
    }

    void UpdateScoreDisplay()
    {
        scoreText.text = score.ToString();
    }

    public void GameOver()
    {
        if (isGameOver) return;

        isGameOver = true;

        // Show the game over panel
        gameOverPanel.SetActive(true);

        // Freeze the game
        Time.timeScale = 0f;
    }

    public void RestartGame()
    {
        // Unfreeze time
        Time.timeScale = 1f;

        // Reload the current scene
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }

    public bool IsGameOver()
    {
        return isGameOver;
    }

    public int GetScore()
    {
        return score;
    }
}
  1. Select the GameManager object. Drag the GameOverPanel into the new Game Over Panel field.

9c. Detect collisions on the bird

Add this method to BirdController.cs:

BirdController.cs — add this method
    void OnCollisionEnter2D(Collision2D collision)
    {
        // Bird hit something solid (pipe or ground) = game over
        GameManager.Instance.GameOver();
    }

9d. Wire up the restart button

  1. Select the RestartButton in the Hierarchy.
  2. In the Inspector, scroll to the On Click () section.
  3. Click the + button.
  4. Drag the GameManager object into the "None (Object)" field.
  5. Click the dropdown that says "No Function" and choose GameManager > RestartGame.

9e. Stop the bird from flapping after death

Update the Update() method in BirdController.cs to check for game over:

BirdController.cs — updated Update()
    void Update()
    {
        // Don't allow input if game is over
        if (GameManager.Instance != null && GameManager.Instance.IsGameOver())
            return;

        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0))
        {
            Flap();
        }

        // Tilt the bird based on its vertical speed
        float tilt = Mathf.Clamp(rb.linearVelocity.y * 5f, -90f, 30f);
        transform.rotation = Quaternion.Euler(0, 0, tilt);
    }

9f. Stop spawning pipes after death

Update the Update() method in PipeSpawner.cs:

PipeSpawner.cs — updated Update()
    void Update()
    {
        // Don't spawn if the game is over
        if (GameManager.Instance != null && GameManager.Instance.IsGameOver())
            return;

        timer += Time.deltaTime;

        if (timer >= spawnInterval)
        {
            SpawnPipe();
            timer = 0f;
        }
    }

Press Play. The full game loop should work: flap, dodge pipes, score points, die, see game over, and restart!

Button not working? Make sure the Canvas has an EventSystem in the scene (Unity usually creates one automatically when you add UI). Check the Hierarchy for it.

10

Polish & Build

The game works. Now let's make it feel better and build a real executable.

10a. Add boundaries

Stop the bird from flying off the top of the screen. Add this to the end of Flap() in BirdController:

BirdController.cs — add to Flap() or Update()
    void LateUpdate()
    {
        // Clamp the bird's position so it can't fly above the screen
        Vector3 pos = transform.position;
        pos.y = Mathf.Clamp(pos.y, -4.5f, 4.5f);
        transform.position = pos;
    }

10b. Add sound effects (optional)

  1. Find free sound effects online (OpenGameArt.org has free ones).
  2. Drag the audio files into your Assets folder.
  3. On the Bird, click Add Component > Audio Source.
  4. Uncheck Play On Awake.

Then add this to BirdController.cs:

BirdController.cs — sound additions
    // Add these fields at the top of the class
    public AudioClip flapSound;
    public AudioClip hitSound;
    private AudioSource audioSource;

    // Add this line inside Start()
    // audioSource = GetComponent<AudioSource>();

    // Play flap sound — add inside Flap()
    // audioSource.PlayOneShot(flapSound);

    // Play hit sound — add inside OnCollisionEnter2D()
    // audioSource.PlayOneShot(hitSound);

Then drag your audio clips into the Inspector fields.

10c. Add a start screen (optional)

A simple approach: freeze the game at the start and wait for the first tap.

In GameManager.cs, add a bool gameStarted = false; field. Set Time.timeScale = 0f; in Start(). Then in a new method called StartGame(), set Time.timeScale = 1f;. Call it from BirdController on the first flap.

10d. Build the game

  1. Go to File > Build Settings.
  2. Click Add Open Scenes to add your current scene.
  3. Choose your Target Platform (Windows, Mac, or WebGL for browser).
  4. Click Build and choose a folder.
  5. Unity will compile your game into a standalone application!
WebGL builds can be uploaded to itch.io for free so anyone can play your game in a browser. Ask your Hackingtons instructor about publishing!

ref

Complete Scripts

Here are the final, complete versions of every script in the project.

BirdController.cs — COMPLETE
using UnityEngine;

public class BirdController : MonoBehaviour
{
    public float flapForce = 6f;
    public AudioClip flapSound;
    public AudioClip hitSound;

    private Rigidbody2D rb;
    private AudioSource audioSource;

    void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        audioSource = GetComponent<AudioSource>();
    }

    void Update()
    {
        // Don't allow input if game is over
        if (GameManager.Instance != null && GameManager.Instance.IsGameOver())
            return;

        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0))
        {
            Flap();
        }

        // Tilt the bird based on its vertical speed
        float tilt = Mathf.Clamp(rb.linearVelocity.y * 5f, -90f, 30f);
        transform.rotation = Quaternion.Euler(0, 0, tilt);
    }

    void LateUpdate()
    {
        // Clamp the bird so it can't fly off screen
        Vector3 pos = transform.position;
        pos.y = Mathf.Clamp(pos.y, -4.5f, 4.5f);
        transform.position = pos;
    }

    void Flap()
    {
        rb.linearVelocity = Vector2.zero;
        rb.AddForce(Vector2.up * flapForce, ForceMode2D.Impulse);

        if (audioSource != null && flapSound != null)
            audioSource.PlayOneShot(flapSound);
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("ScoreZone"))
        {
            GameManager.Instance.AddScore();
        }
    }

    void OnCollisionEnter2D(Collision2D collision)
    {
        if (audioSource != null && hitSound != null)
            audioSource.PlayOneShot(hitSound);

        GameManager.Instance.GameOver();
    }
}
PipeMove.cs — COMPLETE
using UnityEngine;

public class PipeMove : MonoBehaviour
{
    public float speed = 3f;
    public float destroyX = -10f;

    void Update()
    {
        transform.position += Vector3.left * speed * Time.deltaTime;

        if (transform.position.x < destroyX)
        {
            Destroy(gameObject);
        }
    }
}
PipeSpawner.cs — COMPLETE
using UnityEngine;

public class PipeSpawner : MonoBehaviour
{
    public GameObject pipePrefab;
    public float spawnX = 8f;
    public float heightRange = 1.5f;
    public float spawnInterval = 2f;

    private float timer;

    void Update()
    {
        if (GameManager.Instance != null && GameManager.Instance.IsGameOver())
            return;

        timer += Time.deltaTime;

        if (timer >= spawnInterval)
        {
            SpawnPipe();
            timer = 0f;
        }
    }

    void SpawnPipe()
    {
        float randomY = Random.Range(-heightRange, heightRange);
        Vector3 spawnPos = new Vector3(spawnX, randomY, 0);
        Instantiate(pipePrefab, spawnPos, Quaternion.identity);
    }
}
GameManager.cs — COMPLETE
using UnityEngine;
using UnityEngine.SceneManagement;
using TMPro;

public class GameManager : MonoBehaviour
{
    public static GameManager Instance;

    public TextMeshProUGUI scoreText;
    public GameObject gameOverPanel;

    private int score;
    private bool isGameOver;

    void Awake()
    {
        Instance = this;
    }

    void Start()
    {
        score = 0;
        isGameOver = false;
        UpdateScoreDisplay();
        gameOverPanel.SetActive(false);
    }

    public void AddScore()
    {
        if (isGameOver) return;

        score++;
        UpdateScoreDisplay();
    }

    void UpdateScoreDisplay()
    {
        scoreText.text = score.ToString();
    }

    public void GameOver()
    {
        if (isGameOver) return;

        isGameOver = true;
        gameOverPanel.SetActive(true);
        Time.timeScale = 0f;
    }

    public void RestartGame()
    {
        Time.timeScale = 1f;
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }

    public bool IsGameOver()
    {
        return isGameOver;
    }

    public int GetScore()
    {
        return score;
    }
}
Final Hierarchy
Main Camera
Bird (BirdController, Rigidbody2D, CircleCollider2D)
Ground (BoxCollider2D)
GameManager (GameManager script)
PipeSpawner (PipeSpawner script)
Canvas
ScoreText (TextMeshPro)
GameOverPanel (disabled by default)
EventSystem

You built a game!

Seriously — that's a real game with physics, scoring, and UI. Take what you learned and try adding new features: parallax backgrounds, difficulty scaling, high scores, or new obstacles.

Try a Free Hackingtons Class