Событийная шина на базе Observable

Babylon.js предоставляет мощный инструмент для работы с событиями через класс Observable. Observable — это шаблон проектирования, который позволяет объектам уведомлять подписчиков о произошедших событиях. В контексте 3D-сцен это особенно удобно для реагирования на взаимодействие с объектами, изменение состояния сцены или управление анимацией.

Ключевые свойства Observable:

  • observers — массив подписчиков события.
  • add(callback) — метод для добавления подписчика.
  • remove(callback) — удаление подписчика.
  • notifyObservers(eventData) — уведомление всех подписчиков с передачей данных события.
  • clear() — удаление всех подписчиков.

Пример создания Observable:

const onCubeClic ked = new BABYLON.Observable();

Подписка на событие:

onCubeClicked.add((mesh) => {
    console.log("Клик по объекту:", mesh.name);
});

Вызов уведомления:

onCubeClicked.notifyObservers(cubeMesh);

Связывание Observable с объектами сцены

Каждый объект сцены в Babylon.js, например Mesh, Camera, Light, может иметь свои события на базе Observable. Основные встроенные Observables:

  • onDisposeObservable — вызывается при удалении объекта.
  • onPointerObservable — реагирует на действия мыши и касания.
  • onBeforeRenderObservable и onAfterRenderObservable — события перед и после рендера сцены.
  • onReadyObservable — для готовых к использованию объектов или ассетов.

Пример использования onPointerObservable:

scene.onPointerObservable.add((pointerInfo) => {
    switch (pointerInfo.type) {
        case BABYLON.PointerEventTypes.POINTERDOWN:
            console.log("Нажатие на сцену");
            break;
        case BABYLON.PointerEventTypes.POINTERUP:
            console.log("Отпускание кнопки мыши");
            break;
    }
});

Тип события определяется константами PointerEventTypes, что упрощает фильтрацию действий.


Создание событийной шины

Для крупномасштабных приложений удобно создать централизованную событийную шину, которая объединяет все Observables. Такой подход облегчает управление взаимодействием между различными модулями сцены.

Пример структуры событийной шины:

class EventBus {
    constructor() {
        this.events = {};
    }

    createEvent(eventName) {
        if (!this.events[eventName]) {
            this.events[eventName] = new BABYLON.Observable();
        }
        return this.events[eventName];
    }

    subscribe(eventName, callback) {
        if (!this.events[eventName]) {
            this.createEvent(eventName);
        }
        this.events[eventName].add(callback);
    }

    emit(eventName, data) {
        if (this.events[eventName]) {
            this.events[eventName].notifyObservers(data);
        }
    }

    unsubscribe(eventName, callback) {
        if (this.events[eventName]) {
            this.events[eventName].remove(callback);
        }
    }

    clearEvent(eventName) {
        if (this.events[eventName]) {
            this.events[eventName].clear();
        }
    }
}

Использование шины:

const bus = new EventBus();

// Подписка на событие
bus.subscribe("cubeClicked", (mesh) => {
    mesh.material.diffuseColor = new BABYLON.Color3(1, 0, 0);
});

// Генерация события
cubeMesh.actionManager = new BABYLON.ActionManager(scene);
cubeMesh.actionManager.registerAction(
    new BABYLON.ExecuteCodeAction(BABYLON.ActionManager.OnPickTrigger, () => {
        bus.emit("cubeClicked", cubeMesh);
    })
);

Интеграция с анимациями и UI

Observable позволяет легко интегрировать события с анимациями и пользовательским интерфейсом. Например, можно реагировать на клики и запускать анимацию движения камеры:

bus.subscribe("cameraMove", (targetPosition) => {
    BABYLON.Animation.CreateAndStartAnimation(
        "camMove",
        camera,
        "position",
        60,
        120,
        camera.position,
        targetPosition,
        BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT
    );
});

// Генерация события для перемещения камеры
bus.emit("cameraMove", new BABYLON.Vector3(5, 10, -5));

Такой подход позволяет отделить логику событий от самой сцены и упрощает расширение функционала.


Продвинутые возможности Observable

  1. Удаление подписчиков по условию — можно добавлять подписчиков с фильтрацией по типу данных.
  2. Групповые уведомления — notifyObservers может передавать сложные структуры, которые обрабатываются несколькими подписчиками.
  3. Цепочка событий — событие одного Observable может инициировать событие другого, создавая гибкую архитектуру реактивного взаимодействия.
  4. Переиспользуемые события между сценами — EventBus может существовать как глобальный объект, обеспечивая коммуникацию между различными сценами или модулями приложения.

Практические рекомендации

  • Всегда очищать Observables при уничтожении объектов, чтобы избежать утечек памяти.
  • Для часто обновляемых событий (onBeforeRenderObservable) использовать минимальное количество подписчиков и фильтровать действия по необходимости.
  • При сложных интеракциях лучше создавать отдельные EventBus для каждого модуля, чтобы не перегружать глобальную шину.
  • Observable идеально подходит для системы сигнализации внутри игрового движка: столкновения, сбор предметов, изменение состояний UI.

Использование Observable и событийной шины делает архитектуру Babylon.js-приложений модульной, управляемой и легко масштабируемой.