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

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

Версия 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/tweens/sky.png');
        this.load.atlas('match3', 'assets/atlas/match3.png', 'assets/atlas/match3.json');
    }

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

        let pointerX = 400;
        let pointerY = 300;

        this.input.on('pointermove', pointer => {
            pointerX = pointer.worldX;
            pointerY = pointer.worldY;
        });

        this.add.particles(0, 0, 'match3', {
            frame: 'Match3_Icon_23',
            x: {
                onEmit: (particle, key, t, value) => {
                    return pointerX;
                },
                onUpdate: (particle, key, t, value) => {
                    return value;
                }
            },
            y: {
                onEmit: (particle, key, t, value) => {
                    return pointerY;
                },
                onUpdate: (particle, key, t, value) => {
                    //  add to the y value based on particles remaining life
                    //  this creates the effect of gravity, without using gravity
                    return value + (t * 10);
                }
            },
            scale: { start: 0.5, end: 0 },
            speed: 200,
            lifespan: 2000
        });
    }
}

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

const game = new Phaser.Game(config);

Суть примера: кастомное движение частиц

Традиционно для движения частиц используется комбинация свойств speed, gravity и acceleration. Однако они ограничены линейной или параболической траекторией. В данном примере мы отказываемся от стандартных правил и берём управление координатами X и Y каждой частицы в свои руки.

Эмиттер создаётся в центре сцены, но координаты испускания каждой новой частицы (onEmit) и её обновление на каждом кадре (onUpdate) задаются через функции. Это позволяет привязать эмиссию к положению курсора и реализовать собственный алгоритм «падения».

Следим за курсором: коллбэк onEmit

Коллбэк onEmit вызывается в момент создания (эмиссии) каждой новой частицы. Его задача — определить начальные значения для заданного свойства (в нашем случае, `xиy`).

В примере мы отслеживаем движение мыши с помощью события pointermove и сохраняем мировые координаты в переменных pointerX и pointerY. Эти значения и возвращаются в onEmit, поэтому все новые частицы появляются прямо под курсором.

this.input.on('pointermove', pointer => {
    pointerX = pointer.worldX;
    pointerY = pointer.worldY;
});

x: {
    onEmit: (particle, key, t, value) => {
        return pointerX;
    }
},
y: {
    onEmit: (particle, key, t, value) => {
        return pointerY;
    }
}

Собственная физика: коллбэк onUpdate

Сердце примера — коллбэк onUpdate. Он вызывается для каждой активной частицы на каждом кадре игры, пока частица «жива». Его параметр `t` — это нормализованное время жизни частицы (значение от 0 до 1, где 0 — рождение, а 1 — смерть).

Для координаты X мы просто возвращаем текущее значение (value), оставляя её неизменной — частицы не двигаются горизонтально.

Вся магия происходит с координатой Y. К её текущему значению мы прибавляем (t * 10). Так как `tплавно увеличивается от 0 до 1, это создаёт равномерно ускоряющееся движение вниз, имитирующее гравитацию, но без использования свойстваgravity`.

y: {
    onUpdate: (particle, key, t, value) => {
        return value + (t * 10);
    }
}

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

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

Свойство lifespan определяет время жизни частицы в миллисекундах (здесь 2000 мс или 2 секунды). Именно за это время параметр `tвonUpdate` пройдёт от 0 до 1.

Свойство scale делает частицы исчезающими: они рождаются с масштабом 0.5 (start) и к концу жизни уменьшаются до 0 (end).

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

{
    frame: 'Match3_Icon_23',
    // ... кастомные x и y ...
    scale: { start: 0.5, end: 0 },
    speed: 200,
    lifespan: 2000
}

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

Коллбэки onEmit и onUpdate — это ключ к созданию уникальных партикловых систем в Phaser, когда стандартных инструментов недостаточно. Они дают полный контроль над жизненным циклом каждой частицы. Попробуйте поэкспериментировать: заставьте частицы двигаться по синусоиде, вращаться вокруг курсора или менять цвет в зависимости от `t. Например, для орбитального движения можно вonUpdateдляxиyиспользоватьMath.sinиMath.cos`, умножая результат на время жизни.