Шина событий — это конвейер событий, в котором издатель инициирует событие, а подписчики получают это событие и действуют соответствующим образом. Мы можем представить себе это как внезапную вспышку радиосигнала, который могут обнаружить только те, у кого есть антенны, настроенные на определенную частоту.
Например, в игре очень важно знать, когда игра началась, когда закончилась, когда и где умирает игрок и т. д. Не только игра — это один из ключевых механизмов в любом ПО.
Что мы будем создавать. Здесь я попытаюсь создать надежный механизм шины событий с низким уровнем взаимозависимости для запуска и обнаружения событий со значениями или без них. Основное внимание будет уделено тому, чтобы сделать его очень простым в использовании.
- Во-первых, мы создаем класс enum для хранения всех данных событийного типа.
- Затем мы создадим мозг нашего механизма, в котором будут существовать все наши функции прослушивания и срабатывания.
- Наконец, мы будем просто вызывать функции регистрации и прослушивания всякий раз, когда они нам понадобятся.
Создайте перечисление —
Это простой класс, который содержит некоторые значения перечисления данных типа события.
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
Пожалуйста, поддержите меня, если можете, и подумайте, что это ценно для вас.