Saltar a contenido

Módulo 3 — Física de Unity: lluvia de meteoritos (20 min)

¿Qué vamos a lograr?

Hasta ahora, todo lo que se mueve en el juego (la nave, las balas) lo movimos "a mano": en cada frame, nosotros calculamos la nueva posición con código (transform.position, transform.Translate). Eso se llama movimiento cinemático: el código manda, la física no interviene para nada.

En este módulo vamos a ver la otra forma de mover objetos en Unity: dejar que el motor de físicas se encargue. Cada cierto tiempo va a caer un meteorito desde arriba, con gravedad real, y si dos meteoritos se chocan entre sí, van a rebotar y desviarse solos — sin que nosotros programemos ni una línea para el rebote. Eso es lo que hace un Rigidbody2D.

3.1 Conceptos: Rigidbody2D y Collider2D

Dos componentes de Unity que trabajan siempre juntos:

  • Rigidbody2D: le dice a Unity "este objeto debe obedecer las leyes de la física" (gravedad, fuerzas, choques). Tiene un Body Type:
  • Dynamic: el motor de físicas lo controla por completo (cae, choca, rebota). Es el que vamos a usar para los meteoritos.
  • Kinematic: se mueve solo si nosotros lo movemos por código, pero sí puede detectar colisiones. Lo vamos a usar más adelante para la nave y los enemigos.
  • Static: no se mueve nunca (paredes, límites del escenario).
  • Collider2D: define la "forma física" del objeto para detectar colisiones (no tiene por qué ser igual a la forma visual del sprite). Para los meteoritos, que son redondos, usamos un Circle Collider 2D.

Cuando dos objetos con Collider2D se tocan y ninguno tiene marcada la casilla Is Trigger, Unity resuelve la colisión como algo físico de verdad: los objetos se empujan y rebotan entre sí. Esa casilla va a ser clave más adelante (Módulo 5), cuando usemos colliders para detectar golpes por código en vez de dejar que reboten.

3.2 Crear el prefab del meteorito

  1. En la Hierarchy, clic derecho > 2D Object > Sprites > Square. Renómbralo a Meteor.
  2. En el Sprite Renderer, asígnale un sprite de la carpeta Meteors del pack de Kenney.
  3. Add Component > Rigidbody 2D. Dejalo en Body Type: Dynamic (el que viene por defecto). Bájale el Gravity Scale a 0.5 para que caiga a una velocidad más controlada (con 1 cae más rápido de lo que se ve bien en pantalla).
  4. Add Component > Circle Collider 2D. Dejalo con Is Trigger desactivado (sin marcar) — así los meteoritos van a rebotar físicamente entre ellos.
  5. Arrastra el script Meteor.cs (ya escrito en Meteor.cs) sobre el GameObject.
using UnityEngine;

// Este script va sobre el prefab del meteorito.
// A diferencia de la nave y la bala, el meteorito NO se mueve por código:
// se mueve solo porque tiene un Rigidbody2D con gravedad, y Unity lo hace caer.
public class Meteor : MonoBehaviour
{
    void Start()
    {
        // Le damos una rotación inicial al azar (grados por segundo).
        // Como es un Rigidbody2D, con solo fijar este valor una vez acá, Unity se encarga
        // de ir rotando el objeto solo en cada frame, sin que nosotros escribamos más código.
        Rigidbody2D rb = GetComponent<Rigidbody2D>();
        rb.angularVelocity = Random.Range(-90f, 90f);
    }

    // OnBecameInvisible() se llama automáticamente cuando el Sprite Renderer de este objeto
    // deja de estar dentro de la vista de cualquier cámara. Es otra forma de "limpiar"
    // objetos que ya no se necesitan, distinta a las que usamos en la nave y en la bala.
    void OnBecameInvisible()
    {
        Destroy(gameObject);
    }

    // El Collider2D del meteorito NO es un trigger (así los meteoritos chocan físicamente
    // entre ellos). Pero el Collider2D de la nave sí va a ser un trigger (sección 3.3) —
    // y como con que UNO de los dos sea trigger alcanza, este choque en particular se
    // avisa acá, con OnTriggerEnter2D, en vez de con OnCollisionEnter2D.
    void OnTriggerEnter2D(Collider2D other)
    {
        if (other.CompareTag("Player"))
        {
            // Todavía no armamos el sistema de vida (eso es el Módulo 6), así que por
            // ahora el impacto destruye la nave directamente: es una muerte instantánea,
            // sin importar cuánta vida tuviera. Más adelante vamos a cambiar esta línea
            // por una llamada al sistema de vida, para restarle toda la vida de una vez.
            Destroy(other.gameObject);
            Destroy(gameObject);
        }
    }
}
  1. Arrastra el GameObject Meteor hasta Assets/Prefabs para convertirlo en prefab, y borra el que quedó en la Hierarchy.

Inspector del prefab Meteor: Rigidbody 2D, Circle Collider 2D y script Meteor

3.3 Preparar la nave para recibir el impacto

Para que el meteorito pueda "darse cuenta" de que tocó a la nave, la nave también necesita un Collider2D. Y para que la detección funcione de forma confiable, también le agregamos un Rigidbody2D (la regla general en Unity es: al menos uno de los dos objetos que chocan necesita un Rigidbody2D — se lo damos a los dos para no tener que acordarnos de esa regla cada vez).

  1. Selecciona el GameObject Player.
  2. Arriba del Inspector, en el campo Tag, cambia Untagged por Player (es una etiqueta que Unity ya trae creada). Así es como el script del meteorito reconoce a la nave.
  3. Add Component > Circle Collider 2D. Esta vez sí marca la casilla Is Trigger — la nave no debe rebotar como un objeto físico, solo detectar el impacto por código.
  4. Add Component > Rigidbody 2D. Cambia el Body Type a Kinematic (así seguimos moviendo la nave nosotros mismos, por código, como ya hace PlayerController, pero ahora también participa en la detección de colisiones).

Inspector del Player: Tag Player, Circle Collider 2D y Rigidbody 2D Kinematic

Opcional

Si quieres que los meteoritos reboten de forma más "elástica" al chocar entre sí, crea un Physics Material 2D (clic derecho en Assets > Create > 2D > Physics Material 2D), súbele el valor Bounciness a algo como 0.4, y asígnalo en el campo Material del Circle Collider 2D del meteorito.

3.4 Crear el spawner de meteoritos

Un spawner es un GameObject que no se ve en pantalla, cuyo único trabajo es ir creando otros GameObjects con el tiempo. Vamos a usar este mismo patrón más adelante para los enemigos.

  1. En la Hierarchy, clic derecho > Create Empty. Renómbralo a MeteorSpawner.
  2. Arrastra el script MeteorSpawner.cs sobre él.
using UnityEngine;

// Crea meteoritos nuevos cada cierto tiempo, en una posición X al azar,
// justo por encima del borde superior de la pantalla.
public class MeteorSpawner : MonoBehaviour
{
    [Header("Meteoritos")]
    public GameObject meteorPrefab;

    // Cada cuántos segundos cae un meteorito nuevo.
    public float intervaloEntreMeteoritos = 3f;

    // Qué tan fuerte puede ser el empujón lateral inicial (hacia la izquierda o la
    // derecha). Con esto los meteoritos no caen todos derechito hacia abajo.
    public float velocidadLateralMaxima = 2f;

    private float minX, maxX, topY;

    void Start()
    {
        CalcularLimitesDePantalla();

        // InvokeRepeating llama a un método una y otra vez, cada cierto intervalo de tiempo,
        // sin que nosotros tengamos que llevar la cuenta del tiempo a mano en Update().
        InvokeRepeating(nameof(CrearMeteorito), intervaloEntreMeteoritos, intervaloEntreMeteoritos);
    }

    void CalcularLimitesDePantalla()
    {
        Camera camara = Camera.main;
        Vector3 esquinaInferior = camara.ViewportToWorldPoint(new Vector3(0f, 0f, 0f));
        Vector3 esquinaSuperior = camara.ViewportToWorldPoint(new Vector3(1f, 1f, 0f));

        minX = esquinaInferior.x;
        maxX = esquinaSuperior.x;
        topY = esquinaSuperior.y + 1f; // un poco arriba del borde, para que aparezca "cayendo"
    }

    void CrearMeteorito()
    {
        // Random.Range(min, max) devuelve un número al azar entre esos dos valores.
        float posicionX = Random.Range(minX, maxX);
        Vector3 posicionDeAparicion = new Vector3(posicionX, topY, 0f);

        GameObject meteorito = Instantiate(meteorPrefab, posicionDeAparicion, Quaternion.identity);

        // Le damos un empujón lateral al azar. La gravedad se encarga de acelerarlo hacia
        // abajo, así que el resultado es una caída en ángulo (una curva), no una línea recta.
        Rigidbody2D rb = meteorito.GetComponent<Rigidbody2D>();
        float impulsoLateral = Random.Range(-velocidadLateralMaxima, velocidadLateralMaxima);
        rb.linearVelocity = new Vector2(impulsoLateral, 0f);
    }
}
  1. Arrastra el prefab Meteor (desde Assets/Prefabs) al campo Meteor Prefab del componente Meteor Spawner.
  2. Presiona Play y espera unos segundos — deberían empezar a caer meteoritos en ángulos ligeramente distintos, y si dos coinciden en el mismo lugar, deberían rebotar entre sí solos. Si uno toca la nave, la debería destruir al instante.

Inspector de MeteorSpawner con el prefab asignado

Jugando en modo Play: meteoritos cayendo en ángulo

Checkpoint ✅

Al terminar este módulo deberían caer meteoritos solos, cada intervaloEntreMeteoritos segundos, en trayectorias ligeramente distintas. Si dos se cruzan en el aire, la física de Unity los debería desviar automáticamente, sin ningún código nuestro para ese rebote. Y si uno toca la nave, la nave debería destruirse al instante.