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

Работа с событиями — фундамент динамических игр в Phaser. Однако неконтролируемые подписки могут привести к утечкам памяти и неожиданному поведению. На примере простого события мы разберем, как корректно удалять обработчики, когда они больше не нужны, используя метод `off()` и понимая его важные нюансы. Этот навык критически важен для создания стабильных и эффективных игр.

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

Живой запуск

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

Исходный код


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 ()
    {
        let i = 0;

        this.events.on('addImage', function () {

            var x = Phaser.Math.Between(100, 700);
            var y = Phaser.Math.Between(100, 500);

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

            i++;

            if (i === 5)
            {
                //  Remove the event after 5 calls.
                //  By not providing a handler it will clear ALL 'addImage' listeners.
                this.events.off('addImage');
            }

        }, this);

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

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

const game = new Phaser.Game(config);

Сценарий: условное удаление обработчика

В примере создается событие 'addImage', которое должно сработать 10 раз. Однако задача — остановить обработку после пятого вызова. Для этого используется счетчик `i`. Ключевой момент: удаление подписки происходит не снаружи, а внутри самого обработчика, на основе внутренней логики сцены.

let i = 0;

this.events.on('addImage', function () {
    // ... логика обработки ...
    i++;
    if (i === 5)
    {
        this.events.off('addImage');
    }
}, this);

Сила и опасность метода `off('eventName')`

Метод this.events.off('addImage') в данном контексте делает больше, чем кажется. Поскольку при вызове не передан конкретный callback-обработчик, он удаляет **все** функции, подписанные на событие 'addImage'. Это мощный, но потенциально опасный инструмент.

// Удаляет ВСЕ обработчики для события 'addImage'
this.events.off('addImage');

В нашем примере это безопасно, так как обработчик только один. Но в проектах, где на одно событие могут реагировать несколько систем (например, звук, анимация, логика), такой вызов «сломает» их все. Для точечного удаления нужно сохранять ссылку на функцию и передавать ее в off().

Почему событие все равно emit'ится 10 раз?

Важно понимать последовательность выполнения. Цикл for инициирует 10 последовательных выбросов события 'addImage'.

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

Обработчик удаляется только при **пятом** вызове, в момент, когда `iстановится равен 5. Однако вызовыemit` не ставятся в очередь — они выполняются мгновенно. Поэтому первые пять вызовов успешно обработаются и создадут изображения, а последующие пять просто не найдут активных подписчиков и будут проигнорированы. В консоли ошибок не будет.

Лучшие практики: именованные функции и точный контроль

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

create () {
    const onAddImageHandler = function () {
        var x = Phaser.Math.Between(100, 700);
        var y = Phaser.Math.Between(100, 500);
        this.add.image(x, y, 'plush');
    };

    // Подписываемся, передавая именованную функцию
    this.events.on('addImage', onAddImageHandler, this);

    // ... позже, в другом месте кода ...
    // Удаляем только этот конкретный обработчик
    this.events.off('addImage', onAddImageHandler);
}

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

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

Умение управлять жизненным циклом обработчиков событий — признак качественного кода на Phaser. Всегда очищайте подписки, когда они становятся ненужными, чтобы избежать утечек памяти. Для экспериментов попробуйте: создать несколько разных обработчиков на одно событие и удалять их выборочно; или отписываться от события в другом сценарии, например, по таймеру или коллизии. Помните, что off() без callback — это «выключение всего», используйте его осознанно.