О чем этот пример

Система событий Phaser — мощный инструмент для организации взаимодействия между частями игры. Однако без правильного управления подписками можно столкнуться с утечками памяти и неожиданным поведением, когда обработчики срабатывают тогда, когда уже не должны. Этот пример показывает, как явно отключать обработчик события изнутри самого себя — ключевая техника для создания чистого и контролируемого кода. Умение вовремя "выключать" слушатели особенно полезно при создании одноразовых анимаций, диалоговых окон, временных бонусов или при уничтожении игровых объектов, чтобы избежать накопления неиспользуемых функций в памяти.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


let i = 0;
class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('plush', 'assets/pics/profil-sad-plush.png');
    }

    create ()
    {
        //  Call this handler.
        //  Within the handler it will disable itself after 5 calls.
        this.events.on('addImage', this.handler, this);

        //  Emit the event 10 times
        for (var i = 0; i < 10; i++)
        {
            this.events.emit('addImage');
        }
    }

    handler ()
    {
        const x = Phaser.Math.Between(100, 700);
        const y = Phaser.Math.Between(100, 500);

        this.add.image(x, y, 'plush');

        i++;

        if (i === 5)
        {
            this.events.off('addImage', this.handler);
        }
    }

}

const config = {
    type: Phaser.AUTO,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};

const game = new Phaser.Game(config);

Суть примера: событие, которое само себя отключает

В этом примере создаётся пользовательское событие addImage. Обработчик этого события (handler) не только выполняет свою основную функцию (добавляет изображение на сцену), но и содержит логику для собственного отключения после пятого вызова.

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

this.events.on('addImage', this.handler, this);

Разбор метода-обработчика

Ключевая логика заключена в методе handler. Давайте разберём его по шагам.

1.  **Добавление спрайта:** Сначала обработчик размещает изображение 'plush' в случайной позиции на сцене, используя `Phaser.Math.Between`.
2.  **Счётчик вызовов:** Глобальная переменная `i` увеличивается с каждым вызовом. Использование глобальной переменной здесь — упрощение для примера; в реальном проекте счётчик лучше хранить в свойствах сцены или объекта.
3.  **Условие отписки:** Когда счётчик достигает 5, срабатывает условие. Здесь происходит самое важное: вызов `this.events.off`.
handler ()
{
    const x = Phaser.Math.Between(100, 700);
    const y = Phaser.Math.Between(100, 500);

    this.add.image(x, y, 'plush');

    i++;

    if (i === 5)
    {
        this.events.off('addImage', this.handler);
    }
}

Метод off принимает имя события и ссылку на функцию-обработчик, которую нужно удалить из списка слушателей. После этого вызова событие addImage больше не будет вызывать этот конкретный handler.

Что происходит в create()?

Сцена создаёт подписку на событие и сразу же инициирует его вызов 10 раз подряд в цикле.

Важный момент: хотя событие испускается 10 раз (emit вызывается 10 раз), обработчик сработает только 5 раз. После пятого срабатывания он сам себя отключает, и последующие 5 вызовов события addImage просто игнорируются системой событий, так как для него больше не зарегистрировано слушателей.

create ()
{
    this.events.on('addImage', this.handler, this);

    for (var i = 0; i < 10; i++)
    {
        this.events.emit('addImage');
    }
}

Это наглядно показывает, что emit — это просто сигнал. Будет ли на него реакция, зависит от наличия активных слушателей (on).

Практические сценарии применения

Техника отписки внутри обработчика полезна во многих игровых ситуациях:

* **Лимитированные действия:** Игрок может использовать особую способность только 3 раза за уровень. После третьего использования обработчик события нажатия на кнопку способности отключается. * **Временные модификаторы:** Бонус скорости действует 10 секунд. При его получении запускается таймер, и по его окончании обработчик, применяющий множитель скорости к персонажу, отключается через events.off. * **Одноразовые анимации и диалоги:** Отображение кат-сцены или всплывающей подсказки. После завершения анимации или закрытия окна слушатель связанных событий (например, клика для продолжения) должен быть удалён.

Главное правило: если вы добавляете слушатель с помощью on, всегда продумывайте момент, когда его нужно убрать с помощью off. Это предотвратит "зомби"-обработчики, которые продолжают реагировать, хотя их объект-владелец уже уничтожен.

Что попробовать дальше

Управление подписками на события — признак зрелого кода на Phaser. Использование events.off для удаления обработчиков предотвращает утечки памяти и логические ошибки. Пример с самоотключающимся обработчиком — отличная база для более сложной логики жизненного цикла. Поэкспериментируйте: попробуйте создать обработчик, который отписывается не по счётчику, а по случайному шансу или в зависимости от состояния игры. Или реализуйте систему «одноразовых» событий, где объект может прослушать событие только один раз, после чего подписка автоматически сбрасывается.