Patrón Observer

El patrón Observer es un patrón de comportamiento que permite que un objeto (observador) se suscriba a otro objeto (observado) para recibir notificaciones de eventos. En el contexto de los videojuegos, este patrón es muy útil para implementar sistemas de eventos y notificaciones, lo que permite una comunicación asincrónica entre diferentes partes del juego.

📝 Ventajas

  • Comunicación asincrónica: Permite que objetos se comuniquen sin depender directamente el uno del otro, lo que mejora la flexibilidad del código.
  • Modularidad: Facilita la creación de sistemas modulares y decoupled, lo que hace que el código sea más fácil de mantener y expandir.
  • Reutilización: Permite reutilizar comportamientos y acciones en diferentes partes del juego sin duplicar el código.
  • Desacoplamiento: Reduce la dependencia entre componentes del juego, lo que mejora la escalabilidad y la legibilidad del código.

🚧 Desventajas

  • Mantener referencias: Puede ser complejo mantener referencias y evitar fugas de memoria, especialmente en entornos de larga duración como videojuegos.
  • Dependencia de la plataforma: El patrón Observer puede depender de la implementación de la plataforma, lo que puede dificultar la portabilidad del código.
  • Dificultad en debugear: Los sistemas complejos basados en eventos pueden ser difíciles de depurar y entender debido a la naturaleza dispersa de las comunicaciones.

🎮 Uso en videojuegos

  • Eventos del juego: Notificar eventos como muertes de enemigos, recolección de objetos, cambios en el estado del jugador, etc.
  • Sistema de UI: Actualizar la interfaz de usuario automáticamente en respuesta a eventos del juego.
  • Gestión de niveles: Notificar al sistema de niveles cuando un nivel se completa para cargar el siguiente nivel.
  • Sistema de sonido y efectos visuales: Reproducir sonidos y efectos visuales en respuesta a eventos del juego.

🤔 Preguntas para evaluar si es el correcto

  • ¿Es necesario un sistema de comunicación asincrónico entre diferentes partes del juego?
  • ¿Los componentes del juego pueden separarse de manera que no tengan dependencias directas entre sí?
  • ¿Hay eventos que necesiten ser notificados de manera genérica y sin dependencias específicas?
  • ¿La lógica del juego se beneficia de la modularidad y la reutilización de código?
  • ¿Es posible que la complejidad de los eventos y sus suscriptores sea manejable?

💡 Sugerencias adicionales

  • Desacoplamiento: Asegúrate de que los objetos no estén demasiado acoplados, permitiendo una comunicación más limpia y modular.
  • Optimización: Ten en cuenta la eficiencia y la optimización del sistema de eventos, especialmente en juegos con muchos eventos y suscriptores.
  • Extensibilidad: Asegúrate de que el sistema de eventos sea lo suficientemente flexible para agregar nuevos eventos y suscriptores en el futuro sin requerir cambios significativos en el código existente.
Patrón Observer en Unity
Patrón Observer en Unity

🖥️ Código fuente

La interfaz IObserver define el método OnNotify que los suscriptores deben implementar para recibir notificaciones.

public interface IObserver
{
    void OnNotify(object data);
}

La clase Subject será la clase base para cualquier objeto que pueda notificar a otros objetos (Observers) sobre cambios o eventos.

using System.Collections.Generic;
using UnityEngine;

public class Subject
{
    private readonly List<IObserver> _observers = new List<IObserver>();

    public void Attach(IObserver observer)
    {
        if (!_observers.Contains(observer))
        {
            _observers.Add(observer);
        }
    }

    public void Detach(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void Notify(object data)
    {
        foreach (var observer in _observers)
        {
            observer.OnNotify(data);
        }
    }
}

🕹️ Ejemplo

  1. Interfaz IObserver: Define el método OnNotify que los observadores deben implementar para recibir notificaciones.
  2. Clase Subject: Gestiona la lista de observadores y proporciona métodos para agregar, quitar y notificar a los observadores.
  3. Clase GameEventManager: Implementa el patrón Observer y gestiona los eventos del juego.
  4. Clase Player: Se suscribe a eventos y reacciona cuando se notifica la muerte de un enemigo.
  5. Clase Enemy: Lanza un evento cuando muere.

Clase GameEventManager

Esta clase implementa el patrón Observer y gestiona los eventos del juego.

using UnityEngine;

public class GameEventManager : Subject
{
    // Método para notificar la muerte de un enemigo
    public void NotifyEnemyDeath(string enemyName)
    {
        Notify(new EnemyDeathEvent(enemyName));
    }
}

public class EnemyDeathEvent
{
    public string enemyName;

    public EnemyDeathEvent(string name)
    {
        enemyName = name;
    }
}

Clase Player

Esta clase se suscribe a los eventos del GameEventManager y reacciona cuando se notifica la muerte de un enemigo.

using UnityEngine;

public class Player : MonoBehaviour, IObserver
{
    private GameEventManager _gameEventManager;

    void Start()
    {
        _gameEventManager = FindAnyObjectByType <GameEventManager>();
        _gameEventManager.Attach(this);
    }

    void OnNotify(object data)
    {
        var eventInfo = data as EnemyDeathEvent;
        if (eventInfo != null)
        {
            Debug.Log($"Enemy {eventInfo.enemyName} has died!");
        }
    }

    void OnDestroy()
    {
        _gameEventManager.Detach(this);
    }
}

Clase Enemy

using UnityEngine;

public class Enemy : MonoBehaviour
{
    private GameEventManager _gameEventManager;

    void Start()
    {
        _gameEventManager = FindAnyObjectByType <GameEventManager>();
    }

    void Die()
    {
        _gameEventManager.NotifyEnemyDeath(name);
        Destroy(gameObject);
    }

    void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Bullet"))
        {
            Die();
        }
    }
}