Event Channels en Unity con Scriptable Objects: arquitectura limpia para proyectos indie

El siguiente paso natural después de Scriptable Objects: eventos desacoplados, menos dependencias y un proyecto más escalable.
Ayer vimos el primer artículo de la serie sobre Scriptable Objects en Unity, hoy tocaremos un tema que se siente como “la pieza que faltaba” cuando el proyecto empieza a crecer: cómo comunicar sistemas sin llenar la escena de referencias y sin terminar con un “GameManager” que lo controla todo.
En proyectos indie es común arrancar rápido: un UI llama al Player, el Player llama al AudioManager, el AudioManager llama al SaveSystem… Funciona… hasta que cambias una escena, desactivas un objeto, duplicas un prefab o empiezas a separar responsabilidades. En ese momento aparecen los clásicos síntomas: referencias rotas, dependencias circulares, scripts difíciles de reutilizar y un mantenimiento que te frena.
La idea de Event Channels (también llamados “Game Events” o “Event SO”) es simple y muy poderosa: convertir eventos en assets para que varios sistemas se comuniquen sin conocerse entre sí. Es un patrón especialmente útil en juegos indie porque te da orden sin obligarte a montar una arquitectura pesada.
Qué es un Event Channel y por qué te conviene
Un Event Channel es un Scriptable Object que actúa como intermediario: un sistema “emite” el evento y otros sistemas “escuchan” el evento. Ninguno necesita una referencia directa del otro. El canal existe como asset en tu proyecto, lo asignas en el Inspector y listo.
Esto te ayuda a:
- Reducir acoplamiento: UI no necesita conocer al Player, ni el Player al UI.
- Evitar referencias frágiles entre escenas y prefabs.
- Escalar: agregar un nuevo listener no rompe nada; solo se suscribe al canal.
- Mejorar testeo mental: “cuando pasa X, se dispara Y”, sin cadenas de dependencias.
Ejemplo real: cuando el jugador toma una moneda
Vamos a construir un ejemplo práctico típico: cuando el jugador recoge una moneda, queremos:
- Actualizar el UI de monedas.
- Reproducir un sonido.
- Guardar/registrar el progreso (opcional).
Con acoplamiento clásico, el script de la moneda termina con referencias al UI, al audio y al sistema de guardado. Con Event Channels, la moneda solo emite un evento y listo.
1) Crear el Event Channel (Scriptable Object)
Crea un script llamado IntEventChannelSO.cs. Este canal enviará un int (por ejemplo, +1 por moneda, o la cantidad total, tú decides).
using System;
using UnityEngine;
[CreateAssetMenu(menuName = "CodeAndPlay/Events/Int Event Channel")]
public class IntEventChannelSO : ScriptableObject
{
public event Action<int> OnEventRaised;
public void RaiseEvent(int value)
{
OnEventRaised?.Invoke(value);
}
}Luego, en Unity: Create > CodeAndPlay > Events > Int Event Channel y nómbralo: EC_CoinCollected.
2) Emisor: la moneda dispara el evento
Ahora, el script de la moneda no necesita saber nada del UI ni del audio. Solo requiere una referencia al canal y emite.
using UnityEngine;
public class CoinPickup : MonoBehaviour
{
[Header("Event Channels")]
[SerializeField] private IntEventChannelSO coinCollectedChannel;
[Header("Coin Settings")]
[SerializeField] private int coinValue = 1;
private void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Player")) return;
coinCollectedChannel.RaiseEvent(coinValue);
Destroy(gameObject);
}
}En el Inspector del prefab/objeto moneda, asigna: coinCollectedChannel = EC_CoinCollected.
3) Listener 1: UI actualiza el contador
En el UI (por ejemplo, un script en tu HUD), te suscribes al canal en OnEnable y te desuscribes en OnDisable. Esto es importante para evitar listeners “fantasma” cuando cambias de escena o desactivas objetos.
using TMPro;
using UnityEngine;
public class CoinsHUD : MonoBehaviour
{
[SerializeField] private IntEventChannelSO coinCollectedChannel;
[SerializeField] private TextMeshProUGUI coinsText;
private int coins;
private void OnEnable()
{
coinCollectedChannel.OnEventRaised += OnCoinCollected;
}
private void OnDisable()
{
coinCollectedChannel.OnEventRaised -= OnCoinCollected;
}
private void OnCoinCollected(int value)
{
coins += value;
coinsText.text = coins.ToString();
}
}4) Listener 2: Audio reproduce un SFX
El audio se vuelve un sistema independiente: solo escucha el mismo canal.
Nota: aquí ignoramos el valor del evento (por eso usamos int _), pero podrías usarlo para variar el volumen, pitch o sonidos según el valor.
using UnityEngine;
public class CoinSFXListener : MonoBehaviour
{
[SerializeField] private IntEventChannelSO coinCollectedChannel;
[SerializeField] private AudioSource audioSource;
[SerializeField] private AudioClip coinClip;
private void OnEnable()
{
coinCollectedChannel.OnEventRaised += PlaySFX;
}
private void OnDisable()
{
coinCollectedChannel.OnEventRaised -= PlaySFX;
}
private void PlaySFX(int _)
{
if (coinClip == null || audioSource == null) return;
audioSource.PlayOneShot(coinClip);
}
}Errores comunes y cómo evitarlos
1) Olvidar desuscribirse
Si te suscribes en OnEnable y no te desuscribes en OnDisable, puedes acumular listeners y disparar acciones duplicadas. Regla práctica: OnEnable += y OnDisable -= siempre.
2) Usar un canal “para todo”
Evita crear un mega-canal universal. Es mejor tener canales por intención: EC_CoinCollected, EC_PlayerDamaged, EC_OpenPauseMenu, etc. Te da trazabilidad y hace el proyecto más legible.
3) Depender de canales sin disciplina de nombres
Un estándar simple te salva: prefijo EC_ (Event Channel) + acción en PascalCase. Ejemplos: EC_LevelCompleted, EC_InventoryChanged.
Cuándo usar Event Channels en un indie
Úsalos cuando:
- Tu UI necesita reaccionar a gameplay sin “conocer” scripts del jugador.
- Tienes sistemas transversales: audio, logros, analytics, guardado, tutoriales.
- Estás separando responsabilidades y quieres evitar dependencias entre escenas.
No los uses (todavía) cuando:
- Estás prototipando algo muy pequeño y temporal (aunque puedes migrar después).
- Necesitas una secuencia estricta con dependencias fuertes (por ejemplo, un flujo de inicialización controlado). Ahí quizá te convenga un “Bootstrap” más explícito.
Curiosidades rápidas
- Este patrón es popular porque se adapta bien al flujo visual de Unity: assets + Inspector + prefabs.
- Si mañana agregas un sistema de “misiones”, no tocas la moneda: solo agregas un nuevo listener al canal.
- Los Event Channels combinan excelente con Scriptable Objects de datos (stats, balance, configuraciones) para crear una base sólida de proyecto indie.
En Conclusión
Si Scriptable Objects fue el primer paso para ordenar datos en tu proyecto, los Event Channels son el siguiente paso para ordenar la comunicación entre sistemas. Es una forma muy “Unity-friendly” de construir una arquitectura limpia sin convertir tu juego indie en un proyecto pesado.
Para la serie, este enfoque te va a facilitar muchísimo seguir creciendo: UI, audio, guardado, logros y más pueden reaccionar a eventos sin amarrarse entre sí. El resultado es un proyecto más estable, más modular y más fácil de mantener.
En el siguiente artículo hablaremos sobre un sistema de Variables Compartidas con Scriptable Objects (Int/Float/Bool) para UI, balance y debugging sin depender de singletons.


