Шина событий — это конвейер событий, в котором издатель инициирует событие, а подписчики получают это событие и действуют соответствующим образом. Мы можем представить себе это как внезапную вспышку радиосигнала, который могут обнаружить только те, у кого есть антенны, настроенные на определенную частоту.
Например, в игре очень важно знать, когда игра началась, когда закончилась, когда и где умирает игрок и т. д. Не только игра — это один из ключевых механизмов в любом ПО.

Что мы будем создавать. Здесь я попытаюсь создать надежный механизм шины событий с низким уровнем взаимозависимости для запуска и обнаружения событий со значениями или без них. Основное внимание будет уделено тому, чтобы сделать его очень простым в использовании.

  1. Во-первых, мы создаем класс enum для хранения всех данных событийного типа.
  2. Затем мы создадим мозг нашего механизма, в котором будут существовать все наши функции прослушивания и срабатывания.
  3. Наконец, мы будем просто вызывать функции регистрации и прослушивания всякий раз, когда они нам понадобятся.

Создайте перечисление —
Это простой класс, который содержит некоторые значения перечисления данных типа события.

public class EventBusEnum 
    {
        public enum EventName
        {
            START_GAME,
            SCORE_UPDATE,
            THEME_CHANGE
        }
    }

Создайте мозг —
класс EBM является мозгом этого механизма. Это одноэлементный класс, поэтому мы можем вызывать его, не создавая никаких объектов этого класса.

      Hashtable eventHash = new Hashtable();

        private static EBM ebm;

        public static EBM instance
        {
            get
            {
                if (!ebm)
                {
                    ebm = FindObjectOfType(typeof(EBM)) as EBM;

                    if (!ebm)
                    {
                        Debug.LogError("There needs to be one active EBM script on a GameObject in your scene.");
                    }
                    else
                    {
                        ebm.Init();
                    }
                }

                return ebm;
            }
        }

        void Init()
        {
            if (ebm.eventHash == null)
            {
                ebm.eventHash = new Hashtable();
            }
        }

Сначала мы создаем экземпляр EBM. Этот блок кода говорит сам за себя и очень часто встречается в одноэлементных шаблонах. Затем мы используем HashTable для хранения всех наших данных о событиях. Как Dictionary hashTable — это пара ключ-значение, но в hashTable вам не нужно объявлять тип значения. Поскольку мы хотим вернуть значение из события, и это значение может быть любого типа, мы делаем его универсальным и используем hashTable.

Начать прослушивание

public static void StartListening<T>(EventBusEnum.EventName eventName, UnityAction<T> listener)
        {
            UnityEvent<T> thisEvent = null;

            string b = GetKey<T>(eventName);
          
            if (instance.eventHash.ContainsKey(b))
            {
                thisEvent = (UnityEvent<T>)instance.eventHash[b];
                thisEvent.AddListener(listener);
                instance.eventHash[eventName] = thisEvent;
            }
            else
            {
                thisEvent = new UnityEvent<T>();
                thisEvent.AddListener(listener);
                instance.eventHash.Add(b, thisEvent);
              
            }
        }

Это общая функция, которая будет хранить действие события в нашей хеш-таблице. Поскольку мы хотим вернуть значение из события, а тип возвращаемого значения может быть любым, мы используем общий ‹T›. Это означает, что T может быть любого типа, а тип UnityAction будет T. На следующем шаге я создаю ключ хеш-таблицы, добавляя тип и имя события. Я делаю это, потому что хочу разделить разные типы действий с одним и тем же именем события. Вы получите лучшее представление о наших вариантах использования.

private static string GetKey<T>(EventBusEnum.EventName eventName)
        {
            Type type = typeof(T);
            string key = type.ToString() + eventName.ToString();
            return key;
        }

Если вы не хотите ничего возвращать, вы можете просто написать следующий код.

public static void StartListening(EventBusEnum.EventName eventName, UnityAction listener)
        {
            UnityEvent thisEvent = null;

          //  string b = GetKey<T>(eventName);

            if (instance.eventHash.ContainsKey(eventName))
            {
                thisEvent = (UnityEvent)instance.eventHash[eventName];
                thisEvent.AddListener(listener);
                instance.eventHash[eventName] = thisEvent;
            }
            else
            {
                thisEvent = new UnityEvent();
                thisEvent.AddListener(listener);
                instance.eventHash.Add(eventName, thisEvent);

            }
        }

Хватит слушать —

public static void StopListening<T>(EventBusEnum.EventName eventName, UnityAction<T> listener)
        {
            if (ebm == null) return;
            UnityEvent<T> thisEvent = null;
            string key = GetKey<T>(eventName);
            if (instance.eventHash.ContainsKey(key))
            {
                thisEvent = (UnityEvent<T>)instance.eventHash[key];
                thisEvent.RemoveListener(listener);
                instance.eventHash[eventName] = thisEvent;
            }
        }

Приведенный выше код говорит сам за себя. Если вы хотите перестать слушать какой-либо скрипт, вам нужно вызвать вышеуказанную функцию. Здесь мы удаляем слушателя из этого события.

Инициирующее событие —

public static void TriggerEvent<T>(EventBusEnum.EventName eventName,T val)
        {
            UnityEvent<T> thisEvent = null;
            string key = GetKey<T>(eventName);
            if (instance.eventHash.ContainsKey(key))
            {
                thisEvent = (UnityEvent<T>)instance.eventHash[key];
                thisEvent.Invoke(val);
            }
        }

Случаи использования —

//Trigger 
EBM.TriggerEvent<THEME>(EventBusEnum.EventName.THEME_CHANGE, THEME.WHITE);
//Start Listening 
EBM.StartListening<THEME>(EventBusEnum.EventName.THEME_CHANGE, OnThemeChanged);
//Stop Listening 
EBM.StopListening<THEME>(EventBusEnum.EventName.THEME_CHANGE, OnThemeChanged);

void OnThemeChanged(THEME theme)
        {
            if (text == null)
            {
                return;
            }

            switch (theme)
            {
                case THEME.WHITE:
                    text.color = Color.black;
                    break;
                case THEME.DARK:
                    text.color = Color.white;
                    break;
                case THEME.BLUE:
                    text.color = Color.red;
                    break;
            }
        }

Просто напишите приведенные выше коды в нужных классах. Настоятельно рекомендуется поместить вызов начала прослушивания в OnEnable, а остановить прослушивание в функцию OnDisable.

Я надеюсь, что это поможет вам сделать ваш следующий проект более организованным и чистым.

Ознакомьтесь с полным проектом на GitHub
Пожалуйста, поддержите меня, если можете, и подумайте, что это ценно для вас.