El objetivo#
Generar enemigos en oleadas: cada oleada trae más enemigos que la anterior, con un pequeño retardo entre cada uno y una pausa entre oleadas. Base de un tower defense, un survival o un modo horda.
La receta#
using System.Collections;
using UnityEngine;
public class SpawnerOleadas : MonoBehaviour
{
[Header("Qué y dónde")]
[SerializeField] private GameObject enemigoPrefab;
[SerializeField] private Transform[] puntosSpawn;
[Header("Ritmo")]
[SerializeField] private int enemigosOleada1 = 5;
[SerializeField] private int aumentoPorOleada = 3;
[SerializeField] private float tiempoEntreEnemigos = 0.5f;
[SerializeField] private float tiempoEntreOleadas = 5f;
private int oleadaActual = 0;
void Start() => StartCoroutine(GestionarOleadas());
IEnumerator GestionarOleadas()
{
while (true) // oleadas sin fin (o pon una condición de victoria)
{
oleadaActual++;
int cantidad = enemigosOleada1 + (oleadaActual - 1) * aumentoPorOleada;
Debug.Log($"Oleada {oleadaActual}: {cantidad} enemigos");
yield return StartCoroutine(GenerarOleada(cantidad));
// Pausa antes de la siguiente oleada
yield return new WaitForSeconds(tiempoEntreOleadas);
}
}
IEnumerator GenerarOleada(int cantidad)
{
for (int i = 0; i < cantidad; i++)
{
Spawnear();
yield return new WaitForSeconds(tiempoEntreEnemigos);
}
}
void Spawnear()
{
Transform punto = puntosSpawn[Random.Range(0, puntosSpawn.Length)];
Instantiate(enemigoPrefab, punto.position, punto.rotation);
}
}Por qué así#
- Una corrutina orquesta las oleadas y llama a otra que genera los enemigos con retardo: separar "gestión de oleadas" de "generar enemigos" mantiene el código claro.
cantidadcrece con la oleada (enemigosOleada1 + (oleada-1) * aumento).- Puntos de spawn aleatorios (
Random.Range) reparten a los enemigos por el mapa.
Esperar a que se limpie la oleada#
A menudo quieres que la siguiente oleada no empiece hasta eliminar todos los enemigos de la actual. Lleva un contador y espera:
private int enemigosVivos = 0;
void Spawnear()
{
Transform punto = puntosSpawn[Random.Range(0, puntosSpawn.Length)];
Instantiate(enemigoPrefab, punto.position, punto.rotation);
enemigosVivos++;
}
// El enemigo llama a esto al morir (con un evento o referencia al spawner)
public void EnemigoMuerto() => enemigosVivos--;
IEnumerator GestionarOleadas()
{
while (true)
{
oleadaActual++;
int cantidad = enemigosOleada1 + (oleadaActual - 1) * aumentoPorOleada;
yield return StartCoroutine(GenerarOleada(cantidad));
// Espera a limpiar la oleada antes de la pausa
yield return new WaitUntil(() => enemigosVivos == 0);
yield return new WaitForSeconds(tiempoEntreOleadas);
}
}Que cada enemigo conozca al spawner para avisarle acopla las clases. Mejor: un evento "enemigo murió" al que el spawner (y el marcador, y el audio...) se suscriben. El enemigo solo anuncia; no sabe quién escucha.
Rendimiento#
Si generas y destruyes cientos de enemigos, Instantiate/Destroy constante genera tirones. Sustitúyelo por object pooling: reutiliza enemigos activándolos/desactivándolos. Es el patrón estándar para hordas.
Mejoras habituales#
- Datos por oleada con ScriptableObjects: define cada oleada (tipos y cantidades) como un asset editable sin tocar código (ver ScriptableObject).
- Varios tipos de enemigo por oleada.
- UI: mostrar "Oleada 3" y el número de enemigos restantes.
Errores frecuentes#
- Bucle
while (true)sin ninguna condición de fin ni forma de parar (para el spawner al ganar/perder). Instantiatede golpe en un frame en vez de con retardo (aparecen todos amontonados).- No reutilizar enemigos con pooling y provocar tirones en hordas grandes.
- Acoplar cada enemigo al spawner en vez de usar eventos.
Resumen#
Una corrutina gestiona las oleadas (cantidad creciente, pausa entre ellas) y otra genera los enemigos con retardo en puntos aleatorios. Para encadenar oleadas al limpiar la anterior, lleva un contador y usa WaitUntil. Avisa de las muertes con eventos, usa pooling para hordas grandes y define las oleadas con ScriptableObjects para ajustarlas sin código.