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

Создание визуальных эффектов — ключевая часть разработки игр. Phaser предлагает мощную систему Particle Emitter, которая оживляет сцену, но управлять запуском и остановкой частиц вручную не всегда удобно. В этой статье мы разберем, как использовать встроенные события эмиттера (`start`, `stop`, `complete`) для создания отзывчивых и контролируемых эффектов, которые срабатывают автоматически при взаимодействии игрока с объектами. Это особенно полезно для создания подсветки предметов, магических аур или следов при наведении курсора.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('bg', 'assets/skies/darkstone.png');
        this.load.image('flare', 'assets/particles/white-flare.png');
        this.load.image('fox', 'assets/pics/card3.png');
    }

    create ()
    {
        this.add.image(400, 300, 'bg');

        const card = this.add.image(400, 300, 'fox').setInteractive();

        const emitter = this.add.particles(0, 0, 'flare', {
            speed: 24,
            lifespan: 1500,
            quantity: 10,
            scale: { start: 0.4, end: 0 },
            emitting: false,
            emitZone: { type: 'edge', source: card.getBounds(), quantity: 42 },
            duration: 500
        });

        this.add.text(10, 10, 'Mouse over the card');

        const list = this.add.text(10, 60);

        card.on('pointerover', () => {

            list.text = '';

            emitter.start(2000);

        });

        emitter.on('start', () => {
            list.text = list.text.concat('START\n');
        });

        emitter.on('stop', () => {
            list.text = list.text.concat('STOP\n');
        });

        emitter.on('complete', () => {
            list.text = list.text.concat('COMPLETE\n');
        });
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и объектов

В методе preload загружаются необходимые ресурсы: фон, текстура частицы (белая вспышка flare) и изображение карточки (fox).

В create мы размещаем фон и карточку в центре экрана. Ключевой момент — карточка делается интерактивной с помощью метода setInteractive(). Это позволяет ей реагировать на события мыши.

const card = this.add.image(400, 300, 'fox').setInteractive();

Сразу создается текстовый элемент для вывода инструкций и второй (list) — в который будут записываться события эмиттера.

Настройка и конфигурация эмиттера частиц

Эмиттер создается с помощью this.add.particles(). Изначально он не активен (emitting: false). Частицы будут появляться из зоны испускания (emitZone), которая привязана к границам карточки (card.getBounds()). Это значит, что частицы будут рождаться по краям (type: 'edge') изображения, а не из одной точки.

const emitter = this.add.particles(0, 0, 'flare', {
    speed: 24,
    lifespan: 1500,
    quantity: 10,
    scale: { start: 0.4, end: 0 },
    emitting: false,
    emitZone: { type: 'edge', source: card.getBounds(), quantity: 42 },
    duration: 500
});

Важные параметры: - duration: 500 — эмиттер будет испускать частицы 500 мс после старта, а затем автоматически остановится. - lifespan: 1500 — каждая отдельная частица живет 1500 мс, постепенно исчезая ( scale: { start: 0.4, end: 0 } ).

Запуск эмиттера по событию мыши

Основная логика реакции на игрока строится на событии pointerover интерактивного объекта card. Когда курсор наводится на карточку, мы очищаем лог событий (list.text = '') и запускаем эмиттер.

card.on('pointerover', () => {
    list.text = '';
    emitter.start(2000);
});

Метод emitter.start() принимает необязательный параметр — задержку до повтора эффекта в миллисекундах. В нашем случае 2000 означает, что если курсор продолжает находиться над карточкой, эффект запустится повторно через 2 секунды после завершения предыдущего цикла.

Отслеживание событий жизненного цикла эмиттера

Мощь подхода — в подписке на события самого эмиттера. Это позволяет точно знать, в каком состоянии он находится, и синхронизировать с ним другую логику игры.

emitter.on('start', () => {
    list.text = list.text.concat('START\n');
});

emitter.on('stop', () => {
    list.text = list.text.concat('STOP\n');
});

emitter.on('complete', () => {
    list.text = list.text.concat('COMPLETE\n');
});

- Событие start срабатывает в момент вызова emitter.start(). - Событие stop генерируется, когда эмиттер прекращает испускать новые частицы (после истечения duration). - Событие complete вызывается, когда все созданные частицы завершили свой жизненный цикл (их lifespan истек).

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

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

Использование событий эмиттера (start, stop, complete) превращает его из простого генератора частиц в полноценный управляемый объект с предсказуемым жизненным циклом. Для экспериментов попробуйте изменить параметры duration и lifespan, чтобы увидеть, как меняется последовательность событий. Свяжите событие complete не с текстом, а, например, с исчезновением объекта, над которым был курсор, создавая эффект "растворения". Или используйте разные текстуры частиц в зависимости от типа объекта, над которым произошло наведение.