Управление событиями и подписками

Phaser — это игровая библиотека для JavaScript, построенная на событийно-ориентированной архитектуре. Управление событиями в Phaser позволяет реагировать на действия пользователя, изменения состояния игры и внутренние события движка. События могут исходить от игровых объектов, сцен и систем Phaser, таких как таймеры, физические коллизии или ввод с клавиатуры.

Все события обрабатываются с помощью подписок, реализованных через методы on, once и off. Они позволяют:

  • on(event, callback, context) — подписка на событие. Вызывается каждый раз, когда событие происходит.
  • once(event, callback, context) — подписка на одноразовое событие. После первого вызова подписка автоматически снимается.
  • off(event, callback, context) — отписка от события, предотвращающая дальнейшие вызовы.

Контекст (context) используется для привязки значения this внутри функции-обработчика.


События сцены

Сцена (Phaser.Scene) является основным контейнером для логики игры и поддерживает собственный событийный эмиттер. Основные события сцены включают:

  • preload — происходит до загрузки всех ассетов.
  • create — вызывается после загрузки ресурсов и подготовки сцены.
  • update — вызывается на каждом кадре.
  • shutdown — при завершении сцены, для очистки ресурсов.
  • destroy — окончательная очистка сцены из памяти.

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

class MyScene extends Phaser.Scene {
    constructor() {
        super({ key: 'MyScene' });
    }

    create() {
        this.events.on('shutdown', this.onShutdown, this);
    }

    onShutdown() {
        console.log('Сцена завершена');
    }
}

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


События игровых объектов

Любой объект, созданный через this.add или физический объект (this.physics.add), может генерировать события. Наиболее часто используемые события:

  • pointerdown / pointerup / pointermove — для обработки ввода с мыши или сенсорного экрана.
  • drag / dragstart / dragend — для интерактивного перетаскивания объектов.
  • over / out — события наведения курсора.

Пример обработки клика по спрайту:

const sprite = this.add.sprite(400, 300, 'player');
sprite.setInteractive();

sprite.on('pointerdown', () => {
    console.log('Спрайт кликнут');
});

Использование метода setInteractive() обязательно для того, чтобы объект начал реагировать на указательные события.


События физики и коллизий

Phaser предоставляет две физические системы: Arcade Physics и Matter.js, каждая из которых имеет собственные события.

Arcade Physics поддерживает события коллизий:

  • collide — когда два объекта сталкиваются.
  • overlap — когда объекты перекрываются, без физического воздействия.

Пример обработки коллизии:

this.physics.add.collider(player, walls, () => {
    console.log('Игрок столкнулся со стеной');
});

Matter.js поддерживает более сложные события, такие как collisionstart, collisionactive, collisionend:

this.matter.world.on('collisionstart', (event) => {
    event.pairs.forEach(pair => {
        console.log('Началась коллизия между', pair.bodyA.label, 'и', pair.bodyB.label);
    });
});

Пользовательский EventEmitter

Phaser использует встроенный класс Phaser.Events.EventEmitter, который позволяет создавать собственные события и подписки. Это удобно для организации модульного кода и уведомления разных систем игры о происходящих изменениях.

Пример создания собственного эмиттера:

const emitter = new Phaser.Events.EventEmitter();

emitter.on('scoreChanged', (newScore) => {
    console.log('Новый счет:', newScore);
});

emitter.emit('scoreChanged', 100);

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


Управление подписками и очистка

Правильное управление подписками критически важно для предотвращения утечек памяти. Для игровых объектов и сцен рекомендуется:

  • Использовать once для одноразовых событий.
  • Отписываться от событий при уничтожении объекта (off) или завершении сцены (shutdown).
  • Привязывать обработчики к контексту объекта, чтобы сохранить корректное значение this.

Пример безопасной отписки:

this.events.off('update', this.onUpdate, this);

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


События ввода с клавиатуры и контроллеров

Phaser предоставляет отдельные системы для клавиатуры, мыши и геймпадов. Методы this.input.keyboard.on или this.input.gamepad.on позволяют подписываться на события нажатий клавиш и кнопок контроллера.

Пример обработки клавиши:

this.input.keyboard.on('keydown-SPACE', () => {
    console.log('Пробел нажат');
});

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


Дебаунс и частота срабатывания событий

Для событий, которые могут срабатывать очень часто (например, pointermove или update), полезно использовать дебаунс или throttle. Это предотвращает перегрузку логики игры и снижает нагрузку на CPU.

Пример простого throttling:

let lastCall = 0;
sprite.on('pointermove', (pointer) => {
    const now = Date.now();
    if (now - lastCall > 50) { 
        lastCall = now;
        console.log('Движение указателя обработано');
    }
});

Итоговая структура событийной архитектуры

  • Сцена — события жизненного цикла и глобальные уведомления.
  • Игровые объекты — пользовательский ввод и интерактивные реакции.
  • Физика — коллизии и взаимодействия объектов.
  • Системы ввода — клавиатура, мышь, геймпад.
  • Пользовательские эмиттеры — модульное уведомление о событиях.

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