Все заметки

Всплытие событий

Для чего вообще может понадобиться всплытие событий в движке?

Представим, что есть система, которая обрабатывает различные эффекты вроде “Нанести персонажу X урона”, “Вылечить игрока на Y хп” или “Отравить врага на Z секунд”. Событие эффекта может иметь единый тип, но разный набор параметров и тогда системе достаточно следить только за этим событием и подбирать правильную стратегию реагирования. Но как узнать о том, что к какому-то объекту применили эффект? Без всплытия придется перебрать вообще все объекты сцены и на каждый подписаться. При этом нужно не забывать, что объекты могут исчезать и появляться, поэтому нужно следить и за этим тоже. На выходе имеем больше boilerplate кода и кучу одинаковых обработчиков на каждом объекте. В то же время, если бы события умели всплывать наверх, то одной подписки на корневой объект (например сцену) было бы достаточно чтобы следить за всеми сразу.

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

const event = {
  type,
  target: this,
  currentTarget: this
};

let target = this;

while (target !== null) {
  event.currentTarget = target;

  const listeners = target.getListeners(event.type);
  listeners?.forEach((listener) => listener(event));

  target = target.parent;
}

Связь между объектами у меня уже есть, но для того чтобы событие поднималось аж до сцены, должна существовать родительская связь и со сценой тоже.

Все бы ничего, но апи сцены по добавлению объектов совсем не подходило под текущие требования. К примеру, все объекты лежат в сцене плоским списком в независимости от их связей между собой. Когда-то это было просто и удобно, но сейчас все хочется переделать и привести к одному виду.

Чтобы у сцены и объекта был единый интерфейс по работе с родителями и детьми я добавил немножечко наследования. В основе лежит EventTarget класс c возможностями подписки и генерации событий, затем идет Entity – базовый класс, поддерживающий методы вроде appendChild(), removeChild(), getEntityById() и так далее. Наконец, от него уже наследуются Scene и Actor (переименовал GameObject в Actor потому что надоело писать много букв да и приставка Game ни туда ни сюда).

Код движка стал немного аккуратнее и у меня даже получилось щегольнуть знаниями, полученными после нарешивая задач на литкоде. Для поиска объекта в дереве по идентификатору я использовал BFS метод с очередью на односвязном списке.