🎮 UnityDocs

Proyecto: Flappy Bird paso a paso

Clona Flappy Bird de cero: un pájaro que aletea con la gravedad, tuberías que aparecen sin parar por un spawner, puntuación al pasarlas y game over al chocar. Monta física, prefabs, spawning y UI en un juego completo.

🎮 ProyectoPrincipiante✅ RevisadoUnity 6.0 LTSActualizado 2026-07-15
DificultadConceptual Código Matemáticas
Tiempo📖 Lectura 11 min · ⌨️ Práctica 1 h 30 min · 🏗️ Proyecto 2 h

Qué vas a construir#

Flappy Bird: un pájaro que cae por la gravedad y sube un impulso cada vez que pulsas, tuberías con un hueco que aparecen sin parar y se mueven hacia ti, un punto por cada tubería superada, y game over al chocar o caer. Un clásico que enseña prefabs, spawning y un bucle de juego completo. Si vienes de Pong, este sube un peldaño.

Paso 1 — Proyecto 2D#

Crea un proyecto 2D. Fondo a tu gusto en la Main Camera. Como en Pong, no hacen falta gráficos: usaremos cuadrados (GameObject → 2D Object → Sprites → Square), aunque aquí un sprite de pájaro queda mejor si tienes uno.

Paso 2 — El pájaro#

  1. Crea un Square, renómbralo Pajaro, escálalo pequeño, colócalo a la izquierda del centro (Position X = -3).
  2. Añádele Box Collider 2D (o Circle Collider 2D) y Rigidbody2D.
  3. En el Rigidbody2D: Gravity Scale ≈ 3 (que caiga con ganas), Freeze Rotation Z si no quieres que gire.

El script: cae solo por la gravedad, y al pulsar da un impulso hacia arriba fijando la velocidad vertical.

C#
using UnityEngine;

[RequireComponent(typeof(Rigidbody2D))]
public class Pajaro : MonoBehaviour
{
    [SerializeField] private float fuerzaAleteo = 7f;
    private Rigidbody2D rb;
    private bool vivo = true;

    void Awake() => rb = GetComponent<Rigidbody2D>();

    void Update()
    {
        if (!vivo) return;

        if (Input.GetKeyDown(KeyCode.Space) || Input.GetMouseButtonDown(0))
            rb.linearVelocity = new Vector2(0f, fuerzaAleteo);   // impulso arriba
    }

    void OnCollisionEnter2D(Collision2D col) => Morir();
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Limite")) Morir();   // caerse fuera de pantalla
    }

    void Morir()
    {
        if (!vivo) return;
        vivo = false;
        GestorFlappy.Instance.GameOver();
    }
}
Aletear = fijar la velocidad, no sumarla

Poner linearVelocity.y = fuerzaAleteo (en vez de AddForce) hace que cada aleteo sea idéntico y predecible, sin importar a qué velocidad caía el pájaro. Es lo que da el control característico de Flappy: cada toque te sube lo mismo.

Paso 3 — La tubería (prefab)#

Una "tubería" es en realidad dos obstáculos (arriba y abajo) con un hueco en medio.

  1. Crea un objeto vacío Tuberia (GameObject → Create Empty).
  2. Dentro, crea dos Square alargados: Arriba y Abajo, separados dejando un hueco por el que quepa el pájaro. Dales a cada uno un Box Collider 2D.
  3. En el vacío padre Tuberia, añade un collider ancho y fino en el hueco con Is Trigger activado, llamado por ejemplo ZonaPunto (para detectar cuándo el pájaro pasa). O usa un objeto hijo aparte para eso.
  4. Arrastra Tuberia desde la Hierarchy a una carpeta del Project: ya es un Prefab. Bórralo de la escena (lo generará el spawner).

El script que la mueve hacia la izquierda y se autodestruye al salir:

C#
using UnityEngine;

public class Tuberia : MonoBehaviour
{
    [SerializeField] private float velocidad = 3f;

    void Update()
    {
        transform.position += Vector3.left * velocidad * Time.deltaTime;
        if (transform.position.x < -12f) Destroy(gameObject);   // fuera de pantalla
    }

    // El hijo "ZonaPunto" (trigger) detecta el paso del pájaro
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Pajaro"))
            GestorFlappy.Instance.SumarPunto();
    }
}

Ponle la Tag "Pajaro" al pájaro.

Mueve las tuberías, no el pájaro

En Flappy, el pájaro se queda fijo en X y son las tuberías las que vienen hacia él. Es más simple que mover el mundo o una cámara, y el efecto es idéntico. Truco de diseño clásico.

Paso 4 — El spawner de tuberías#

Genera tuberías cada cierto tiempo, con el hueco a una altura aleatoria. Usa una corrutina:

C#
using System.Collections;
using UnityEngine;

public class SpawnerTuberias : MonoBehaviour
{
    [SerializeField] private GameObject tuberiaPrefab;
    [SerializeField] private float cada = 1.6f;        // segundos entre tuberías
    [SerializeField] private float alturaMax = 2.5f;   // variación vertical del hueco
    [SerializeField] private float xInicial = 10f;     // aparecen a la derecha

    void Start() => StartCoroutine(Generar());

    IEnumerator Generar()
    {
        while (true)
        {
            float y = Random.Range(-alturaMax, alturaMax);
            Instantiate(tuberiaPrefab, new Vector3(xInicial, y, 0f), Quaternion.identity);
            yield return new WaitForSeconds(cada);
        }
    }
}

Pon este script en un objeto vacío. Asigna el prefab de la tubería. Al darle a Play, las tuberías aparecen y avanzan.

Object pooling para el pulido

Aquí instanciamos y destruimos tuberías sin parar. Para un juego real conviene object pooling y reciclarlas, evitando basura para el recolector. Para aprender, Instantiate/Destroy está bien; recuérdalo como mejora.

Paso 5 — Límites de pantalla#

Para que caerse o subir demasiado sea game over, crea un collider ancho abajo (y opcionalmente arriba) con Is Trigger y Tag "Limite" (el pájaro ya lo comprueba en su OnTriggerEnter2D). El suelo también puede ser un collider sólido normal contra el que chocar.

Paso 6 — El gestor: puntuación y game over#

Un Singleton sencillo lleva la puntuación, el récord y el estado:

C#
using UnityEngine;
using TMPro;

public class GestorFlappy : MonoBehaviour
{
    public static GestorFlappy Instance { get; private set; }

    [SerializeField] private TextMeshProUGUI textoPuntos;
    [SerializeField] private GameObject panelGameOver;

    private int puntos;

    void Awake()
    {
        if (Instance != null) { Destroy(gameObject); return; }
        Instance = this;
        Time.timeScale = 1f;    // por si venimos de un game over
    }

    void Start() => Actualizar();

    public void SumarPunto()
    {
        puntos++;
        Actualizar();
    }

    public void GameOver()
    {
        panelGameOver.SetActive(true);
        // Guardar récord (ver PlayerPrefs)
        int record = PlayerPrefs.GetInt("RecordFlappy", 0);
        if (puntos > record) PlayerPrefs.SetInt("RecordFlappy", puntos);
        Time.timeScale = 0f;    // congela el juego
    }

    void Actualizar() => textoPuntos.text = puntos.ToString();
}
Restaura Time.timeScale al reiniciar

El game over congela el juego con Time.timeScale = 0. Cuando reinicies (recargar la escena), vuelve a ponerlo a 1 (lo hacemos en Awake), o el juego nuevo arrancará congelado. Es el bug clásico del que avisa la receta del menú de pausa.

Paso 7 — UI y reinicio#

  1. Crea un TextMeshPro para los puntos, arriba y centrado (anclas). Arrástralo al gestor.
  2. Crea un panel Game Over con un botón "Reintentar". Desactívalo al inicio (lo activa el gestor al morir).
  3. El botón reinicia recargando la escena (botones):
C#
using UnityEngine;
using UnityEngine.SceneManagement;

public class BotonReiniciar : MonoBehaviour
{
    public void Reiniciar()
    {
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
}

Recuerda que la escena debe estar en Build Profiles (ver Escenas).

Paso 8 — Pulido#

  • Rotación del pájaro según su velocidad vertical (mira arriba al subir, cae de morro): interpola la rotación Z con rb.linearVelocity.y.
  • Sonidos de aleteo, punto y choque (Audio).
  • Récord en la pantalla de game over (ya lo guardas con PlayerPrefs).
  • Dificultad progresiva: reduce cada o sube la velocidad con los puntos.
  • Object pooling de tuberías (ver pooling).

Lo que has aprendido#

Flappy Bird te ha hecho usar: Rigidbody2D con gravedad, control por impulso, Prefabs, Instantiate con un spawner por corrutina, triggers para puntuar y morir, un Singleton gestor, Time.timeScale, PlayerPrefs para el récord y UI con reinicio de escena. Un bucle de juego completo de principio a fin.

Resumen#

Flappy = un pájaro con gravedad que aletea fijando linearVelocity.y, un prefab de tubería que se mueve a la izquierda y se autodestruye, un spawner que las genera cada X segundos a altura aleatoria, triggers para sumar punto (al pasar) y para el game over (al chocar/caer), y un gestor Singleton con puntuación, récord (PlayerPrefs) y Time.timeScale. Termínalo y pásate a mejorarlo con pooling y dificultad progresiva.

Fuentes y para profundizar