Módulo 4 — Enemigos y dificultad progresiva (25 min)¶
¿Qué vamos a lograr?¶
Dos tipos de enemigo distintos, que van a ir apareciendo desde arriba cada vez con más frecuencia:
- Enemigo que se aproxima: avanza directo hacia la nave, en línea recta hacia donde esté en cada momento.
- Enemigo que dispara: baja hasta cierta altura, se detiene, y desde ahí dispara balas hacia abajo a intervalos regulares, sin acercarse más.
Info
En este módulo todavía no vamos a programar qué pasa cuando algo choca con un enemigo, ni qué pasa cuando una bala enemiga o un enemigo tocan a la nave — eso lo hacemos junto con el resto del sistema de vida y puntaje en los Módulos 5 y 6, todo de una vez. Acá nos concentramos solo en que los enemigos existan, se muevan y aparezcan con el tiempo.
4.1 Crear el enemigo que se aproxima¶
- En la Hierarchy, clic derecho > 2D Object > Sprites > Square. Renómbralo a
EnemyChaser. - En el Sprite Renderer, asígnale un sprite de nave enemiga (por ejemplo
ufoRed, o cualquiera de la carpetaEnemies). - En el campo Tag del Inspector, elige Add Tag..., crea una etiqueta nueva
llamada
Enemy, y luego asignásela a este GameObject. Vamos a usar este tag en el Módulo 5 para reconocer a cualquier enemigo, sin importar de qué tipo sea. - Add Component > Circle Collider 2D, con Is Trigger activado (mismo motivo que con la nave en el Módulo 3: queremos detectar el contacto, no que rebote físicamente).
- Add Component > Rigidbody 2D, con Body Type en
Kinematic(nosotros lo vamos a mover por código, igual que a la nave). - Arrastra el script
EnemyChaser.cssobre el GameObject.
using UnityEngine;
// Este enemigo avanza directo hacia la nave del jugador, sin importar en qué
// dirección tenga que moverse para lograrlo.
public class EnemyChaser : MonoBehaviour
{
public float moveSpeed = 3f;
// Guardamos una referencia al Transform de la nave, para saber hacia dónde movernos.
private Transform jugador;
void Start()
{
// GameObject.FindGameObjectWithTag busca en toda la escena el único objeto
// que tenga el tag indicado. Por eso en el Módulo 3 le pusimos el tag "Player" a la nave.
GameObject objetoJugador = GameObject.FindGameObjectWithTag("Player");
if (objetoJugador != null)
{
jugador = objetoJugador.transform;
}
}
void Update()
{
// Si la nave ya fue destruida (jugador quedó en null), no hay a quién perseguir.
if (jugador == null) return;
// Vector3.MoveTowards calcula, paso a paso, una posición que se va acercando
// desde transform.position hacia jugador.position, sin pasarse de largo.
transform.position = Vector3.MoveTowards(transform.position, jugador.position, moveSpeed * Time.deltaTime);
}
// Igual que con los meteoritos: si el enemigo sale de la pantalla sin haber chocado
// con nada, lo destruimos para no dejar objetos de más dando vueltas.
void OnBecameInvisible()
{
Destroy(gameObject);
}
}
- Arrastra el GameObject
EnemyChaserhastaAssets/Prefabs, y borra el que quedó en la Hierarchy.
4.2 Crear el enemigo que dispara desde la distancia¶
Primero necesitamos su bala. Es prácticamente igual a la del jugador (Módulo 2), pero viaja hacia abajo:
- En la Hierarchy, clic derecho > 2D Object > Sprites > Square. Renómbralo a
EnemyBullet. - En el Sprite Renderer, asígnale un sprite de la carpeta
Lasers(elige un color distinto al de la bala del jugador, para diferenciarlas a simple vista — por ejemplo un láser rojo). - Arrastra el script
EnemyBullet.cssobre el GameObject.
using UnityEngine;
// Es prácticamente igual a Bullet.cs, pero para los disparos enemigos:
// se mueve hacia abajo en vez de hacia arriba, y se destruye sola después de un tiempo.
public class EnemyBullet : MonoBehaviour
{
[Header("Movimiento")]
public float moveSpeed = 8f;
[Header("Duración")]
public float tiempoDeVida = 3f;
void Start()
{
Destroy(gameObject, tiempoDeVida);
}
void Update()
{
transform.Translate(Vector3.down * moveSpeed * Time.deltaTime);
}
}
- Arrastra
EnemyBullethastaAssets/Prefabs, y borra el que quedó en la Hierarchy.
Note
Por ahora, a propósito, EnemyBullet no tiene Collider2D: todavía no necesita
"detectar" nada, porque el daño a la nave lo programamos recién en el Módulo 6.
Ahora el enemigo en sí:
- En la Hierarchy, clic derecho > 2D Object > Sprites > Square. Renómbralo a
EnemyShooter. - Asígnale un sprite de nave enemiga distinto al de
EnemyChaser(para diferenciarlos a simple vista). - Ponle el Tag
Enemy(el mismo que creamos recién). - Add Component > Circle Collider 2D, con Is Trigger activado.
- Add Component > Rigidbody 2D, con Body Type en
Kinematic. - Arrastra el script
EnemyShooter.cssobre el GameObject.
using UnityEngine;
// Este enemigo baja hasta una altura fija y se queda ahí quieto, disparando hacia abajo
// cada cierto tiempo, sin acercarse más a la nave.
public class EnemyShooter : MonoBehaviour
{
[Header("Movimiento")]
public float moveSpeed = 3f;
// Hasta qué altura (posición Y) baja antes de detenerse a disparar.
public float alturaDeDetencion = 2f;
[Header("Disparo")]
public GameObject enemyBulletPrefab;
public float fireRate = 1.5f;
private bool detenido = false;
private float proximoDisparo;
void Update()
{
if (!detenido)
{
Mover();
}
else if (Time.time >= proximoDisparo)
{
Disparar();
proximoDisparo = Time.time + fireRate;
}
}
void Mover()
{
transform.Translate(Vector3.down * moveSpeed * Time.deltaTime);
// Apenas llega a la altura de detención, deja de moverse y empieza a disparar.
if (transform.position.y <= alturaDeDetencion)
{
detenido = true;
proximoDisparo = Time.time + fireRate;
}
}
void Disparar()
{
Instantiate(enemyBulletPrefab, transform.position, Quaternion.identity);
}
void OnBecameInvisible()
{
Destroy(gameObject);
}
}
- En el Inspector de
EnemyShooter, arrastra el prefabEnemyBullet(desdeAssets/Prefabs) al campo Enemy Bullet Prefab. - Arrastra el GameObject
EnemyShooterhastaAssets/Prefabs, y borra el que quedó en la Hierarchy.
4.3 Crear el spawner de enemigos, con dificultad progresiva¶
- En la Hierarchy, clic derecho > Create Empty. Renómbralo a
EnemySpawner. - Arrastra el script
EnemySpawner.cssobre él.
using UnityEngine;
// Crea enemigos con el tiempo, alternando al azar entre los dos tipos, y hace que
// aparezcan cada vez más seguido: así el juego se pone más difícil solo.
public class EnemySpawner : MonoBehaviour
{
[Header("Prefabs de enemigos")]
public GameObject enemigoQueSeAproximaPrefab;
public GameObject enemigoQueDisparaPrefab;
[Header("Dificultad progresiva")]
// Cada cuánto aparece un enemigo nuevo, al empezar el juego.
public float intervaloInicial = 2.5f;
// El intervalo nunca va a bajar de este valor, para que el juego siga siendo jugable.
public float intervaloMinimo = 0.6f;
// Cuánto se reduce el intervalo cada vez que aparece un enemigo nuevo.
public float reduccionPorAparicion = 0.05f;
private float intervaloActual;
private float proximaAparicion;
private float minX, maxX, topY;
void Start()
{
intervaloActual = intervaloInicial;
CalcularLimitesDePantalla();
proximaAparicion = Time.time + intervaloActual;
}
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;
}
void Update()
{
// El mismo patrón de "cooldown" con Time.time que usamos para el disparo de la
// nave en el Módulo 2. A diferencia del InvokeRepeating del Módulo 3, acá el
// intervalo va cambiando, así que necesitamos controlarlo nosotros mismos.
if (Time.time >= proximaAparicion)
{
CrearEnemigo();
// Reducimos el intervalo para que el próximo enemigo tarde menos en aparecer,
// sin bajar nunca del mínimo.
intervaloActual = Mathf.Max(intervaloMinimo, intervaloActual - reduccionPorAparicion);
proximaAparicion = Time.time + intervaloActual;
}
}
void CrearEnemigo()
{
// Random.value devuelve un número al azar entre 0 y 1: lo usamos como
// "moneda al aire" para elegir uno de los dos tipos de enemigo.
GameObject prefabElegido = Random.value < 0.5f ? enemigoQueSeAproximaPrefab : enemigoQueDisparaPrefab;
float posicionX = Random.Range(minX, maxX);
Vector3 posicionDeAparicion = new Vector3(posicionX, topY, 0f);
Instantiate(prefabElegido, posicionDeAparicion, Quaternion.identity);
}
}
- En el Inspector de
EnemySpawner, arrastra el prefabEnemyChaseral campo Enemigo Que Se Aproxima Prefab, y el prefabEnemyShooteral campo Enemigo Que Dispara Prefab. - Presiona Play y observa unos 20-30 segundos: deberían ir apareciendo ambos tipos de enemigo desde arriba, cada vez con menos tiempo entre uno y otro.
Checkpoint ✅¶
Al terminar este módulo deberían aparecer enemigos de los dos tipos desde arriba de la pantalla: unos avanzando directo hacia la nave, y otros bajando hasta cierta altura para quedarse ahí disparando. Con el paso del tiempo, deberían ir apareciendo más seguido.



