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

В разработке игр часто возникают ситуации, когда реакция на событие должна произойти лишь один раз, независимо от того, сколько раз оно было вызвано. Например, вы хотите воспроизвести уникальную анимацию при первом открытии сундука или добавить бонус при первом посещении локации. Повторные срабатывания могут сломать логику игры. Встроенная система событий Phaser предоставляет для этого простой и элегантный инструмент — метод `once`. Эта статья на практическом примере покажет, как он работает, чем отличается от обычного `on` и почему это важно для чистоты кода.

Версия 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 ()
    {
        //  This handler will only be called once, no matter how many times the event fires
        this.events.once('addImage', this.handler, this);

        //  We emit the event 3 times, but the handler is only called once
        this.events.emit('addImage');
        this.events.emit('addImage');
        this.events.emit('addImage');
    }

    handler ()
    {
        const x = Phaser.Math.Between(200, 600);
        const y = Phaser.Math.Between(200, 400);

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

}

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

const game = new Phaser.Game(config);

Разбираем исходный код

В примере создается простая сцена, которая загружает изображение и в методе create() подписывается на событие. Ключевое отличие — используется не this.events.on, а this.events.once.

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

Этот метод регистрирует обработчик handler, который будет вызван только при первом срабатывании события addImage. Контекст this передан третьим аргументом, чтобы внутри handler можно было работать с методами сцены, например, this.add.image.

Механика вызова: один раз и навсегда

Сразу после подписки код трижды испускает (эмитит) одно и то же событие.

this.events.emit('addImage');
this.events.emit('addImage');
this.events.emit('addImage');

Несмотря на три вызова emit, функция handler выполнится лишь один раз. Метод once автоматически отписывает обработчик после его первого выполнения. Это внутренняя гарантия Phaser, которая избавляет разработчика от необходимости вручную удалять подписку через off.

Что происходит внутри обработчика

Единственный вызов обработчика приводит к добавлению на сцену одного изображения в случайной позиции.

handler ()
{
    const x = Phaser.Math.Between(200, 600);
    const y = Phaser.Math.Between(200, 400);

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

Phaser.Math.Between генерирует случайные координаты в заданных пределах. Метод this.add.image создает игровой объект Image в этих координатах, используя загруженный ключ текстуры 'plush'. Если бы использовался обычный on, на сцене появилось бы три изображения.

Практические сценарии использования

Метод once незаменим для инициализации и уникальных игровых моментов: * **Одноразовая инициализация:** Загрузка критичных ресурсов или настройка менеджера после старта сцены. * **Уникальные события:** Воспроизведение кат-сцены, показ обучающей подсказки или выдача достижения, которые должны случиться только один раз за сессию. * **Гарантия от утечек памяти:** Использование once вместо комбинации on и последующего off делает код чище и безопаснее, так как исключает человеческую ошибку — вы можете забыть отписаться, а once сделает это автоматически.

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

Метод events.once — это мощный паттерн для реакций, которые не должны повторяться. Он делает код более декларативным, безопасным и простым для понимания. **Идеи для экспериментов:** Попробуйте заменить once на on в примере и увидите три изображения. Создайте кнопку, которая эмитит событие по клику, и убедитесь, что once срабатывает лишь на первый клик. Или скомбинируйте once и on для разных обработчиков одного события, чтобы разделить разовую и постоянную логику.