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#
- Crea un Square, renómbralo Pajaro, escálalo pequeño, colócalo a la izquierda del centro (
Position X = -3). - Añádele Box Collider 2D (o Circle Collider 2D) y Rigidbody2D.
- 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.
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();
}
}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.
- Crea un objeto vacío Tuberia (
GameObject → Create Empty). - 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.
- 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.
- 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:
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.
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:
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.
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:
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();
}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#
- Crea un TextMeshPro para los puntos, arriba y centrado (anclas). Arrástralo al gestor.
- Crea un panel Game Over con un botón "Reintentar". Desactívalo al inicio (lo activa el gestor al morir).
- El botón reinicia recargando la escena (botones):
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
cadao 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.