El problema que resuelven#
Cuando el jugador muere, muchas cosas deben reaccionar: la UI muestra "Game Over", el audio para la música, el marcador se guarda, los enemigos se detienen. La forma ingenua es que el script del jugador llame a todos:
// ❌ El jugador conoce a TODOS: acoplamiento máximo
uiManager.MostrarGameOver();
audioManager.PararMusica();
scoreManager.Guardar();Si mañana añades un sistema nuevo, tienes que volver a tocar el jugador. Los eventos invierten esto: el jugador solo anuncia "he muerto", y cada sistema decide si escucha. El jugador no conoce a nadie.
Delegados: la base#
Un delegado es una variable que guarda una función. Puedes pasarla, almacenarla y llamarla. C# trae dos genéricos que cubren casi todo:
Action saludar = () => Debug.Log("Hola"); // sin parámetros, no devuelve nada
Action<int> daño = (n) => Debug.Log("Daño: " + n); // recibe un int
Func<int, int, int> sumar = (a, b) => a + b; // recibe 2 int, devuelve int
saludar(); // Hola
daño(10); // Daño: 10
int r = sumar(2, 3); // 5Eventos con event#
Un event es un delegado con reglas de seguridad: desde fuera solo puedes suscribirte (+=) o darte de baja (-=), pero no invocarlo ni borrar los demás suscriptores. Es el patrón observer del lenguaje.
using System;
using UnityEngine;
public class Salud : MonoBehaviour
{
// El evento que otros pueden escuchar
public event Action<int> AlCambiarVida;
public event Action AlMorir;
private int vida = 100;
public void RecibirDaño(int cantidad)
{
vida = Mathf.Max(0, vida - cantidad);
AlCambiarVida?.Invoke(vida); // avisa a los suscritos (si hay)
if (vida == 0)
AlMorir?.Invoke();
}
}Y quien quiera reaccionar, se suscribe:
public class BarraDeVidaUI : MonoBehaviour
{
[SerializeField] private Salud salud;
void OnEnable() => salud.AlCambiarVida += Actualizar;
void OnDisable() => salud.AlCambiarVida -= Actualizar; // ¡imprescindible!
void Actualizar(int vida) => Debug.Log("Vida en UI: " + vida);
}Si te suscribes y no te das de baja, cuando el objeto se destruya el evento seguirá teniendo una referencia a un método de un objeto muerto: fuga de memoria y posibles NullReferenceException. La pareja OnEnable/OnDisable es la forma segura y simétrica.
El operador ?.Invoke()#
AlMorir?.Invoke() significa "si hay alguien suscrito, avísale; si no, no hagas nada". Sin el ?., invocar un evento sin suscriptores lanza NullReferenceException. Es el patrón estándar para disparar eventos.
UnityEvent: eventos desde el Inspector#
UnityEvent es la versión de Unity que puedes conectar arrastrando en el Inspector, sin código. Es lo que usan los botones de UI (onClick).
using UnityEngine;
using UnityEngine.Events;
public class Interruptor : MonoBehaviour
{
public UnityEvent alActivar; // aparece en el Inspector
public void Activar() => alActivar.Invoke();
}event Action | UnityEvent | |
|---|---|---|
| Se configura en | Código | Inspector (o código) |
| Rendimiento | Mejor | Algo mayor coste |
| Lo usan diseñadores sin programar | No | Sí |
| Ideal para | Comunicación entre sistemas de código | Enganchar acciones desde el editor |
Cuándo NO usar eventos#
- Para flujo lineal simple dentro de una misma clase: una llamada directa es más clara.
- Si abusas, el flujo se vuelve difícil de seguir ("¿quién ha llamado a esto?"). Los eventos desacoplan, pero demasiados hacen el código "mágico".
Errores frecuentes#
- No darse de baja (
-=) y provocar fugas o llamadas a objetos destruidos. - Invocar sin
?.y petar cuando no hay suscriptores. - Usar
UnityEventen bucles calientes por comodidad, cuando uneventsería más eficiente.
Ponte a prueba#
Resumen#
Los eventos permiten que unos sistemas avisen a otros sin conocerlos: el emisor anuncia, los interesados se suscriben. Usa Action/event para comunicación en código (rápido) y UnityEvent para conectar acciones desde el Inspector. Suscríbete en OnEnable, da de baja en OnDisable e invoca siempre con ?.Invoke().