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

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

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

    create ()
    {
        const pop = 1024 * 8;
        const layer = this.add.spriteGPULayer('sparkle1', pop);

        console.log(layer);

        layer.setBlendMode(Phaser.BlendModes.ADD);

        const template = {
            x: {
                base: 400,
                ease: 'Linear',
                yoyo: false
            },
            y: {
                base: 500,
                ease: 'Gravity',
                yoyo: false
            },
            alpha: {
                base: 1,
                amplitude: -1,
                ease: 'Quad.easeInOut',
                yoyo: false
            },
            rotation: {
                ease: 'Linear',
                yoyo: false
            },
            tintTopLeft: 0x5080ff,
            tintTopRight: 0x5080ff,
            tintBottomLeft: 0x5080ff,
            tintBottomRight: 0x5080ff
        };

        const drift = 1000000 / pop;
        const bursts = 16;
        for (let j = 0; j < bursts; j++)
        {
            let a = -Math.random() * Math.PI;
            let b = Math.random() * Math.PI;
            let delayBase = Math.random() * 1000;

            for (let i = 0; i < pop / bursts; i++)
            {
                const duration = Math.random() * 1000 + 1000;
                const delay = i * 1000 / (pop / bursts) + delayBase;

                a += (Math.random() * 2 - 1) / drift;
                b += (Math.random() * 2 - 1) / drift;

                let x = Math.cos(a) * Math.cos(b);
                let y = Math.sin(a);

                template.x.amplitude = x * 500 - Math.random() * 100;
                template.x.duration = duration;
                template.x.delay = delay;

                template.y.velocity = y * 800 - Math.random() * 100;
                template.y.duration = duration;
                template.y.delay = delay;

                template.alpha.duration = duration;
                template.alpha.delay = delay;

                template.rotation.base = Math.random() * Math.PI * 2;
                template.rotation.amplitude = Math.random() * 6 - 3;
                template.rotation.duration = duration;
                template.rotation.delay = delay;

                template.scaleX = 0.1 * Math.random() + 0.02;
                template.scaleY = template.scaleX;

                layer.addMember(template);
            }
        }
    }
}

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

const game = new Phaser.Game(config);

Инициализация SpriteGPULayer

Ключевой объект для работы с массой частиц — SpriteGPULayer. Он создаётся как слой, который может содержать множество спрайтов, использующих одну текстуру, и оптимизирован для рендеринга на GPU.

Сначала загружаем текстуру частицы в preload. Затем в create вычисляем общее количество частиц (pop) и создаём сам слой. Метод setBlendMode устанавливает режим наложения ADD, который идеально подходит для светящихся частиц — цвета складываются, создавая эффект свечения.

const pop = 1024 * 8;
const layer = this.add.spriteGPULayer('sparkle1', pop);
layer.setBlendMode(Phaser.BlendModes.ADD);

Шаблон для настройки частиц

Каждая частица в слое настраивается через объект-шаблон (template). В нём определяются свойства анимации: положение (`x,y), прозрачность (alpha), вращение (rotation), цвет (tint) и масштаб (scaleX,scaleY`).

Свойства используют паттерн с base (базовое значение), amplitude (амплитуда изменения), ease (функция плавности), duration (длительность) и delay (задержка старта). Например, для оси Y используется ease 'Gravity', что имитирует движение под действием гравитации.

const template = {
    x: { base: 400, ease: 'Linear', yoyo: false },
    y: { base: 500, ease: 'Gravity', yoyo: false },
    alpha: { base: 1, amplitude: -1, ease: 'Quad.easeInOut', yoyo: false },
    rotation: { ease: 'Linear', yoyo: false },
    tintTopLeft: 0x5080ff,
    tintTopRight: 0x5080ff,
    tintBottomLeft: 0x5080ff,
    tintBottomRight: 0x5080ff
};

Генерация и распределение частиц

Чтобы создать эффект фонтана, частицы распределяются порциями (bursts). Для каждой порции вычисляются начальные углы `aиb, которые задают направление разлёта в сферических координатах. ЗадержкаdelayBase` добавляет случайность во времени появления.

Внутри цикла для каждой частицы задаются длительность анимации и индивидуальная задержка. Углы слегка изменяются на каждом шаге (через drift), что создаёт естественное расхождение траекторий. Затем вычисляются компоненты скорости `xиy, которые присваиваются какamplitudeдля оси X иvelocity` для оси Y в шаблоне.

template.x.amplitude = x * 500 - Math.random() * 100;
template.x.duration = duration;
template.x.delay = delay;

template.y.velocity = y * 800 - Math.random() * 100;
template.y.duration = duration;
template.y.delay = delay;

Настройка визуальных параметров

Помимо движения, важно анимировать визуальные свойства. Прозрачность (alpha) уменьшается от 1 до 0 за время duration, создавая эффект исчезновения. Вращение (rotation) задаётся случайным начальным углом (base) и амплитудой, которая определяет скорость вращения.

Масштаб (scaleX, scaleY) устанавливается небольшим и случайным, чтобы частицы выглядели как мелкие искры. Все эти параметры копируются из шаблона для каждой частицы при добавлении её в слой через layer.addMember(template).

template.alpha.duration = duration;
template.alpha.delay = delay;

template.rotation.base = Math.random() * Math.PI * 2;
template.rotation.amplitude = Math.random() * 6 - 3;
template.rotation.duration = duration;
template.rotation.delay = delay;

template.scaleX = 0.1 * Math.random() + 0.02;
template.scaleY = template.scaleX;

layer.addMember(template);

Запуск анимации и настройка сцены

После добавления всех частиц в слой анимация запускается автоматически, согласно заданным задержкам и длительностям. Конфигурация игры (config) должна использовать type: Phaser.WEBGL, так как SpriteGPULayer работает только с WebGL рендерером.

Важно указать корректный parent (контейнер в HTML) и размеры холста. Экземпляр игры создаётся с этой конфигурацией и переданным классом сцены.

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};
const game = new Phaser.Game(config);

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

SpriteGPULayer позволяет создавать сложные частичные системы с тысячами объектов, сохраняя высокую производительность за счёт GPU. Вы можете экспериментировать: измените ease-функции для движения (например, на 'Sine.easeOut'), добавьте изменение цвета частиц со временем через tint, или создайте несколько слоёв с разными текстурами для составных эффектов (огонь и дым). Попробуйте варьировать параметры drift и bursts для контроля над формой фонтана.