Всплытие событий
Для чего вообще может понадобиться всплытие событий в движке?
Представим, что есть система, которая обрабатывает различные эффекты вроде “Нанести персонажу 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 метод с очередью на односвязном списке.