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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.atlas('flares', 'assets/particles/flares.png', 'assets/particles/flares.json');
    }

    create ()
    {
        const tree = new Phaser.Geom.Triangle.BuildEquilateral(0, -250, 400);
        const trunk = new Phaser.Geom.Rectangle(0, 0, 80, 140);
        const baubles = new Phaser.Geom.Line(0, 0, 170, 60);
        const baubles2 = new Phaser.Geom.Line(0, 0, 310, 70);

        const particles = this.add.particles('flares');

        particles.createEmitter({
            frame: 'green',
            x: 400, y: 300,
            speed: 0,
            lifespan: 2000,
            delay: 500,
            quantity: 48,
            frequency: 2000,
            scale: { start: 0.4, end: 0.1 },
            blendMode: 'ADD',
            emitZone: { type: 'edge', source: tree, quantity: 48 }
        });

        particles.createEmitter({
            frame: 'blue',
            x: 360, y: 420,
            speed: 0,
            lifespan: 500,
            delay: 500,
            frequency: 0,
            quantity: 1,
            scale: 0.2,
            blendMode: 'ADD',
            emitZone: { type: 'edge', source: trunk, quantity: 48 }
        });

        particles.createEmitter({
            frame: 'red',
            x: 400, y: 300,
            lifespan: 500,
            quantity: 1,
            frequency: 200,
            scale: 0.6,
            blendMode: 'ADD',
            emitZone: { type: 'edge', source: tree, quantity: 12 }
        });

        particles.createEmitter({
            frame: { frames: [ 'red', 'yellow', 'blue' ], cycle: true },
            x: 340, y: 200,
            lifespan: 200,
            quantity: 1,
            frequency: 50,
            scale: 0.4,
            blendMode: 'ADD',
            emitZone: { type: 'edge', source: baubles, quantity: 10 }
        });

        particles.createEmitter({
            frame: { frames: [ 'red', 'yellow', 'blue' ], cycle: true },
            x: 280, y: 300,
            lifespan: 200,
            quantity: 1,
            frequency: 50,
            scale: 0.4,
            blendMode: 'ADD',
            emitZone: { type: 'edge', source: baubles2, quantity: 16 }
        });
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и загрузка атласа

Всё начинается в методе preload. Здесь мы загружаем единственный необходимый ресурс — атлас частиц flares. Атлас содержит несколько текстур (фреймов) для частиц разных цветов в одном изображении.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.atlas('flares', 'assets/particles/flares.png', 'assets/particles/flares.json');

Метод setBaseURL задаёт базовый путь для загрузчиков, что позволяет указывать только относительные пути к файлам атласа: изображению (flares.png) и JSON-файлу с данными о фреймах (flares.json).

Геометрия как основа визуализации

В методе create мы определяем форму нашей будущей ёлки с помощью встроенных геометрических объектов Phaser. Это ключевой момент: мы не рисуем частицы вручную, а задаём для них области появления.

const tree = new Phaser.Geom.Triangle.BuildEquilateral(0, -250, 400);
const trunk = new Phaser.Geom.Rectangle(0, 0, 80, 140);
const baubles = new Phaser.Geom.Line(0, 0, 170, 60);
const baubles2 = new Phaser.Geom.Line(0, 0, 310, 70);

* tree: Равносторонний треугольник, который станет кроной ёлки. Он строится от точки (0, -250) с длиной стороны 400 пикселей. * trunk: Прямоугольник для ствола. * baubles и baubles2: Отрезки, вдоль которых будут появляться гирлянды-шарики.

Все фигуры создаются с началом в точке (0, 0), их позиция будет задана позже через свойства эмиттера `xиy`.

Создание менеджера частиц и эмиттеров

Сначала создаётся менеджер частиц (ParticleEmitterManager), который будет управлять всеми эмиттерами и их визуализацией.

const particles = this.add.particles('flares');

Затем для этого менеджера создаётся несколько эмиттеров методом createEmitter. Каждый эмиттер отвечает за свой визуальный элемент: крону, ствол, вспышки на кроне и две гирлянды. Рассмотрим первый, самый сложный эмиттер для кроны.

particles.createEmitter({
    frame: 'green',
    x: 400, y: 300,
    speed: 0,
    lifespan: 2000,
    delay: 500,
    quantity: 48,
    frequency: 2000,
    scale: { start: 0.4, end: 0.1 },
    blendMode: 'ADD',
    emitZone: { type: 'edge', source: tree, quantity: 48 }
});

* frame: Указывает, какой фрейм из атласа ('green') использовать для частиц. * x, y: Центральная точка, к которой будет привязана зона эмиссии (emitZone). * speed: 0: Частицы не движутся, они остаются на месте появления. * lifespan: Время жизни частицы в миллисекундах (2000 мс = 2 секунды). * delay и frequency: Эмиттер ждёт 500 мс, а затем испускает частицы каждые 2000 мс. * quantity: Количество частиц, испускаемых за один цикл (48 штук). * scale: Частицы плавно уменьшаются с 0.4 до 0.1 за время своей жизни. * blendMode: 'ADD': Режим наложения «сложение» делает яркие частицы ещё ярче при наложении, создавая эффект свечения. * emitZone: Самая важная настройка. type: 'edge' означает, что частицы будут появляться вдоль рёбер (контура) геометрической фигуры source: tree. Параметр quantity здесь определяет, сколько точек равномерно распределить по контуру фигуры для появления частиц. Для треугольника это 48 точек.

Эмиттеры для деталей: ствол, вспышки и гирлянды

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

Эмиттер ствола использует прямоугольник (trunk) в качестве emitZone и испускает 48 синих статичных частиц один раз (частота frequency: 0).

particles.createEmitter({
    frame: 'blue',
    x: 360, y: 420,
    speed: 0,
    // ... другие параметры ...
    emitZone: { type: 'edge', source: trunk, quantity: 48 }
});

Эмиттер красных вспышек на кроне (tree) испускает по одной крупной частице каждые 200 мс с коротким временем жизни (500 мс), создавая эффект мерцания.

Эмиттеры гирлянд (baubles, baubles2) демонстрируют продвинутую работу с кадрами. Вместо строки они принимают объект конфигурации, который циклически перебирает массив цветов.

frame: { frames: [ 'red', 'yellow', 'blue' ], cycle: true }

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

Настройка игры и запуск

Финальный шаг — конфигурация и инстанцирование объекта игры Phaser.Game. Мы используем WEBGL-рендерер для корректной работы режима наложения ADD.

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

const game = new Phaser.Game(config);

Конфиг передаётся в конструктор Phaser.Game, который запускает игровой цикл и инициализирует нашу сцену Example.

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

Этот пример наглядно показывает, как, комбинируя простые геометрические фигуры и настраивая параметры эмиттеров, можно создавать сложные и управляемые визуальные эффекты без использования готовых изображений. Для экспериментов попробуйте: изменить type в emitZone на 'random', чтобы частицы появлялись не по контуру, а случайно внутри фигуры; анимировать свойства `xиyсамого эмиттера, чтобы заставить гирлянды двигаться; или использовать более сложную пользовательскую кривую дляscaleиalpha`, чтобы получить нелинейное затухание частиц.