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

Частицы — это мощный инструмент для создания атмосферы и визуальных эффектов в играх. Они оживляют сцену, будь то дым, огонь, магические следы или погодные явления. В Phaser 3 система частиц гибкая и производительная, но её настройка требует понимания основных параметров. На примере кода из баг-трекера разберём, как создать массивную сетку из эмиттеров частиц для формирования сложного визуального паттерна, и как тонко управлять их поведением — от движения и масштабирования до цвета и прозрачности.

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

Живой запуск

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

Исходный код


class ParticlesSquare extends Phaser.Scene
{
    preload()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image("smokeTest", "assets/particles/smoke0.png");
    }

    create()
    {
        const mainCam = this.cameras.main;
        const width = 1600;
        const height = 1200;

        mainCam.setBackgroundColor("#FFFFFF");
        mainCam.setZoom(0.50);
        mainCam.centerOn(width/2,height/2)

        for (let x = 0; x < width; x +=100)
        {
            for (let y = 0; y < height; y += 200)
            {
                this.add.particles(0, 0, "smokeTest", {
                    x,
                    y,
                    lifespan: { min: 3000, max: 5000 },
                    speed: { min: 5, max: 25 },
                    angle: { min: 270 - 10, max: 270 + 10 },
                    timeScale: 0.35,
                    gravityX: -2,
                    gravityY: -5,
                    scale: {
                        start: 0.33,
                        end: 1.2,
                        ease: Phaser.Math.Easing.Cubic.In
                    },
                    rotate: { min: 0, max: 360 },
                    alpha: { start: 0.6, end: 0 },
                    quantity: 1,
                    tint: [0x000000, 0x141414, 0x292929, 0x2e2e2e],
                    frequency: 25,
                    // blendMode: Phaser.BlendModes.NORMAL,
                    // particleBringToTop: true,
                });

            }
        }

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

const game = new Phaser.Game(config);

Подготовка сцены и камеры

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

preload()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image("smokeTest", "assets/particles/smoke0.png");
}
create()
{
    const mainCam = this.cameras.main;
    const width = 1600;
    const height = 1200;

    mainCam.setBackgroundColor("#FFFFFF");
    mainCam.setZoom(0.50);
    mainCam.centerOn(width/2,height/2)
    // ... создание частиц
}

Ключевые моменты: setZoom(0.50) уменьшает вид камеры, позволяя уместить большую рабочую область (1600x1200) в окно игры (800x600). centerOn позиционирует камеру по центру этой области. Без этого частицы могли бы оказаться за пределами видимости.

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

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

for (let x = 0; x < width; x +=100)
{
    for (let y = 0; y < height; y += 200)
    {
        this.add.particles(0, 0, "smokeTest", { /* конфиг */ });
    }
}

Циклы создают эмиттер каждые 100 пикселей по X и 200 пикселей по Y, покрывая всю область. Первые два аргумента this.add.particles(0, 0, ...) — это координаты *эмиттера*. Однако в конфиге отдельно задаются `xиy` для *испускания* частиц. Это позволяет иметь один управляемый эмиттер (например, для паузы), но испускать частицы в разных точках сетки.

Конфигурация поведения частиц: движение и жизнь

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

{
    x, // Позиция испускания берется из переменных цикла
    y,
    lifespan: { min: 3000, max: 5000 },
    speed: { min: 5, max: 25 },
    angle: { min: 260, max: 280 },
    gravityX: -2,
    gravityY: -5,
    frequency: 25,
}

* lifespan: Время жизни в миллисекундах. Случайное значение делает исчезновение менее однородным. * speed и angle: Задают начальный вектор движения. Угол ~270 градусов означает движение вверх (в системе координат Phaser, где 0 — направо). * gravityX и gravityY: Постоянное ускорение, применямого к частицам каждое обновление. Отрицательные значения по Y (-5) тянут частицы вверх сильнее, а небольшой отрицательный gravityX (-2) создаёт лёгкий снос влево, добавляя динамики. * frequency: Интервал в миллисекундах между испусканием новых частиц. Значение 25 означает примерно 40 частиц в секунду от каждого эмиттера.

Конфигурация внешнего вида: трансформация и цвет

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

{
    scale: {
        start: 0.33,
        end: 1.2,
        ease: Phaser.Math.Easing.Cubic.In
    },
    rotate: { min: 0, max: 360 },
    alpha: { start: 0.6, end: 0 },
    tint: [0x000000, 0x141414, 0x292929, 0x2e2e2e],
    // blendMode: Phaser.BlendModes.NORMAL,
}

* scale: Частицы увеличиваются от 33% до 120% размера текстуры. ease: Cubic.In означает, что рост начинается медленно, а к концу ускоряется. * rotate: Каждая частица получает случайный начальный угол вращения. * alpha: Плавное исчезновение от полупрозрачности (0.6) до полной прозрачности (0). * tint: Массив цветов в HEX формате (0xRRGGBB). Частицам случайным образом назначается один из этих оттенков серого, создавая неоднородный, "грязный" дым. * timeScale: Глобальный множитель скорости анимации для всех частиц в этом эмиттере (0.35 замедляет их).

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

Пример демонстрирует, как комбинация множества простых эмиттеров и детальной конфигурации рождает сложный и живой визуальный эффект. Для экспериментов попробуйте: изменить gravityY на положительное значение, чтобы дым опускался; заменить текстуру smoke0.png на искру или звезду; использовать blendMode: Phaser.BlendModes.ADD для создания светящихся эффектов; или динамически менять frequency или позицию (`x,y`) эмиттеров в реальном времени, привязав их к курсору мыши.