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

В игровом движке Phaser 3 работа с системами частиц обычно ограничивается их визуализацией. Однако иногда требуется интерактивность: например, чтобы частицы гаснули при касании игрока или активировали ловушки. В этой статье мы разберем, как использовать метод `overlap()` эмиттера частиц для детектирования их пересечения с геометрической фигурой и реагирования на это событие. Этот подход открывает двери для создания нестандартной игровой механики, основанной на взаимодействии с визуальными эффектами.

Версия 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.atlas('bubbles', 'assets/particles/bubbles.png', 'assets/particles/bubbles.json');
    }

    create ()
    {
        this.emitter = this.add.particles(100, 30, 'bubbles', {
            frame: [ 'bluebubble', 'redbubble', 'greenbubble', 'silverbubble' ],
            scale: { min: 0.25, max: 1 },
            rotate: { start: 0, end: 360 },
            speed: { min: 50, max: 100 },
            lifespan: 6000,
            frequency: 50,
            gravityY: 90
        });

        this.tweens.add({
            targets: this.emitter,
            particleX: 700,
            yoyo: true,
            repeat: -1,
            ease: 'sine.inout',
            duration: 1500
        });

        this.rect = new Phaser.Geom.Rectangle(400, 500, 64, 64);

        this.input.on('pointermove', pointer => {

            Phaser.Geom.Rectangle.CenterOn(this.rect, pointer.worldX, pointer.worldY);

        });

        this.graphics = this.add.graphics();
    }

    update ()
    {
        this.graphics.clear();
        this.graphics.lineStyle(2, 0x00ff00);
        this.graphics.strokeRectShape(this.rect);

        const particles = this.emitter.overlap(this.rect);

        particles.forEach(particle => {
            particle.kill();
        });
    }
}

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

const game = new Phaser.Game(config);

Подготовка эмиттера частиц

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

this.emitter = this.add.particles(100, 30, 'bubbles', {
    frame: [ 'bluebubble', 'redbubble', 'greenbubble', 'silverbubble' ],
    scale: { min: 0.25, max: 1 },
    rotate: { start: 0, end: 360 },
    speed: { min: 50, max: 100 },
    lifespan: 6000,
    frequency: 50,
    gravityY: 90
});

Ключевые параметры: * frame: Массив названий кадров из атласа, определяющих внешний вид частиц. * scale, rotate, speed: Параметры разброса для размера, вращения и скорости, делающие частицы разнообразными. * lifespan: Время жизни частицы в миллисекундах (6000 мс = 6 секунд). * frequency: Интервал между испусканием частиц (одна каждые 50 мс). * gravityY: Сила гравитации, притягивающая частицы вниз.

Для добавления динамики эмиттеру применяется твин, который плавно перемещает точку испускания по горизонтали.

this.tweens.add({
    targets: this.emitter,
    particleX: 700,
    yoyo: true,
    repeat: -1,
    ease: 'sine.inout',
    duration: 1500
});

Параметр particleX твина управляет свойством particleX самого эмиттера, перемещая источник частиц по оси X.

Создание зоны взаимодействия

Для детектирования столкновений нам нужен объект, с которым будут сталкиваться частицы. В примере используется прямоугольник Phaser.Geom.Rectangle. Его позиция привязана к курсору мыши с помощью слушателя события pointermove.

this.rect = new Phaser.Geom.Rectangle(400, 500, 64, 64);

this.input.on('pointermove', pointer => {
    Phaser.Geom.Rectangle.CenterOn(this.rect, pointer.worldX, pointer.worldY);
});

Статический метод Phaser.Geom.Rectangle.CenterOn() перемещает центр прямоугольника в координаты указателя мыши (pointer.worldX, pointer.worldY). Это создает интерактивную зону, которую игрок может перемещать.

Чтобы визуализировать эту зону на экране, создается объект Graphics, который в методе update() будет отрисовывать контур прямоугольника.

this.graphics = this.add.graphics();

Детектирование пересечений и реакция

Вся логика обработки столкновений происходит в методе update(), который выполняется каждый кадр.

Сначала очищается предыдущая отрисовка и заново рисуется контур перемещаемого прямоугольника.

this.graphics.clear();
this.graphics.lineStyle(2, 0x00ff00);
this.graphics.strokeRectShape(this.rect);

Затем вызывается ключевой метод эмиттера — overlap(). В него передается геометрический объект (в нашем случае прямоугольник this.rect). Метод проверяет, пересекается ли ограничивающая область (bounds) любой из *активных* частиц эмиттера с заданной фигурой.

const particles = this.emitter.overlap(this.rect);

Метод возвращает массив всех частиц, которые в текущий кадр пересекаются с прямоугольником. Далее по этому массиву можно итерировать и выполнять необходимые действия. В примере для каждой такой частицы вызывается метод kill().

particles.forEach(particle => {
    particle.kill();
});

Метод particle.kill() немедленно деактивирует частицу и возвращает ее в пул для повторного использования эмиттером. Визуально это выглядит как исчезновение пузырьков при наведении на них курсором.

Конфигурация игры и запуск

Пример завершается стандартной конфигурацией игры Phaser и ее инициализацией.

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

const game = new Phaser.Game(config);

Эта конфигурация создает игровое поле размером 800x600 пикселей с темно-синим фоном и назначает наш класс Example в качестве основной сцены.

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

Метод overlap() эмиттера частиц — это мощный инструмент для добавления физического взаимодействия в чисто визуальные эффекты. В рассмотренном примере мы реализовали базовый детектор столкновений частиц с курсором. Для экспериментов попробуйте изменить форму зоны детектирования на круг (Phaser.Geom.Circle), реагировать на столкновения не удалением, а, например, изменением цвета или скорости частицы, или использовать несколько зон с разными эффектами. Это может стать основой для игровых механик, таких как сбор частиц-бонусов, активация порталов или создание областей, замедляющих время.