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

Одной из самых мощных возможностей партиклов в Phaser является система `moveTo`. Она позволяет частицам не просто лететь по прямой, а перемещаться к динамически изменяющейся цели. Это открывает двери для создания сложных визуальных эффектов, таких как магические потоки, летящие за персонажем, или энергетические лучи, преследующие цель. В этой статье мы разберем конкретный пример, где источник частиц испускает поток, который плавно следует за координатами курсора мыши. Вы узнаете, как использовать функции обратного вызова `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/skies/gradient26.png');
        this.load.image('ball', 'assets/demoscene/green_ball.png');
    }

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

        let x = 400;
        let y = 570;

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

        this.add.particles(0, 0, 'ball', {
            x: { min: 300, max: 500 },
            y: -32,
            advance: 2000,
            moveToX: {
                onEmit: () => {
                    return x;
                },
                onUpdate: () => {
                    return x;
                }
            },
            moveToY: {
                onEmit: () => {
                    return y;
                },
                onUpdate: () => {
                    return y;
                }
            },
            lifespan: 2000,
            sortProperty: 'lifeT',
            sortOrderAsc: true
        });
    }
}

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

const game = new Phaser.Game(config);

Идея: Частицы с интеллектом

Обычно, задав для эмиттера координаты `xиy, мы получаем статический источник частиц. Но что, если нужно, чтобы каждая новая частица летела к точке, которая постоянно двигается? Например, к позиции игрока или курсора. Для этого в Phaser есть свойстваmoveToXиmoveToY`.

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

Именно благодаря onUpdate частицы могут плавно перенацеливаться, создавая эффект "живого", следящего потока.

Настройка сцены и отслеживание мыши

В методе create() сцены, после добавления фона, мы создаем две переменные для хранения целевых координат и привязываем их к движению мыши.

let x = 400;
let y = 570;

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

Здесь `xиyинициализируются начальными значениями. Слушатель события'pointermove'постоянно обновляет эти переменные, присваивая им мировые координаты (pointer.worldX,pointer.worldY) курсора. Таким образом,xиy` всегда актуальны.

Создание и конфигурация эмиттера

Создадим сам эмиттер частиц с помощью this.add.particles. Ключевая магия происходит в конфигурационном объекте.

this.add.particles(0, 0, 'ball', {
    x: { min: 300, max: 500 },
    y: -32,
    advance: 2000,
    moveToX: {
        onEmit: () => { return x; },
        onUpdate: () => { return x; }
    },
    moveToY: {
        onEmit: () => { return y; },
        onUpdate: () => { return y; }
    },
    lifespan: 2000,
    sortProperty: 'lifeT',
    sortOrderAsc: true
});

Разберем важные параметры: 1. **Источник (`x,y`)**: Частицы появляются в случайной точке по горизонтали (от 300 до 500) и чуть выше верхней границы экрана (y = -32). 2. **Динамическая цель (moveToX, moveToY)**: Обе функции возвращают текущие значения переменных `xиy.onEmitзадает точку, к которой частица полетит в момент рождения.onUpdate` перенаправляет её к новой цели на каждом кадре. Поскольку цели для всех частиц одинаковы, весь поток стремится к курсору. 3. **advance: 2000**: Этот параметр указывает системе частиц заранее просчитать и сгладить траекторию движения на 2000 миллисекунд. Без него резкое изменение цели в onUpdate могло бы выглядеть как резкий скачок. 4. **Сортировка (sortProperty, sortOrderAsc)**: Частицы сортируются по оставшемуся времени жизни (lifeT) по возрастанию. Это нужно для корректного порядка отрисовки частиц, движущихся по сложной траектории.

Как это работает в движении

Представьте цепочку частиц, вылетающих из верхней части экрана. В момент создания (onEmit) первая частица получает координаты курсора (например, [100, 200]) и начинает движение к ним. Через мгновение курсор перемещается. Функция onUpdate для этой же частицы вызывается в следующем кадре и возвращает новые координаты ([150, 250]). Система частиц, благодаря параметру advance, плавно корректирует траекторию, направляя частицу к новой точке.

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

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

Использование moveTo с динамическими функциями onEmit и onUpdate превращает обычный эмиттер частиц в интерактивный инструмент. Вы можете привязать целевые координаты не только к курсору, но и к спрайту игрока (sprite.x), к случайной точке в области (Phaser.Math.Between), или даже к результату физического расчета. **Идеи для экспериментов:** 1. Замените pointermove на клик, чтобы частицы летели к последней нажатой точке. 2. Сделайте несколько целей, разделив частицы на группы с разными moveTo функциями. 3. Добавьте в функции onUpdate небольшие случайные отклонения (+ Math.sin(time) * 10), чтобы поток "дрожал" или вихрился.