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

Движущиеся частицы — ключевой элемент визуальной динамики в играх. Phaser предоставляет мощную систему Particle Emitter, но её возможности часто остаются нераскрытыми. В этой статье разберем, как заставить частицы проигрывать анимацию кадров текстуры по циклу, создавая сложные и живые эффекты, такие как мерцающие искры или вращающиеся кристаллы, без написания кастомной логики для каждой частицы. Мы сосредоточимся на параметре `frame` (и его внутреннем аналоге `_frame`) эмиттера, который управляет выбором кадра из спрайтшита. Понимание его работы с флагами `cycle` и `quantity` позволит вам создавать не просто статичные точки, а миниатюрные анимированные спрайты, составляющие сложную визуальную картину.

Версия 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.spritesheet('diamonds', 'assets/sprites/diamonds32x24x5.png', { frameWidth: 32, frameHeight: 24 });
    }

    create ()
    {
        const shape1 = new Phaser.Geom.Circle(0, 0, 200);

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

        particles.createEmitter({
            frame: { frames: [ 0, 1, 2, 3, 4 ], cycle: true, quantity: 32 },

            // _frame: { frames: [ 0, 1, 2, 3, 4 ], cycle: true },
            // _frame: { frames: [ 0, 1 ], cycle: true },
            _frame: { frames: [ 0, 1 ], cycle: true },
            x: 400,
            y: 300,
            frequency: 32,
            lifespan: 400,
            alpha: { start: 1, end: 0 },
            zone: { type: 'edge', source: shape1, quantity: 32 }
        });
    }
}

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

const game = new Phaser.Game(config);

Подготовка спрайтшита

Всё начинается с загрузки ресурса. В данном примере используется спрайтшит diamonds, который содержит 5 кадров (5 алмазов разного цвета), упакованных в одну текстуру.

Ключевой момент — корректное указание размеров одного кадра при загрузке. Это позволит системе партиклов в дальнейшем правильно нарезать текстуру.

this.load.spritesheet('diamonds', 'assets/sprites/diamonds32x24x5.png', { frameWidth: 32, frameHeight: 24 });

Метод load.spritesheet принимает ключ ресурса, путь к файлу и объект конфигурации с шириной и высотой кадра. После этой операции в кеше загрузчика появляется текстура, разделённая на пронумерованные кадры от 0 до 4.

Создание эмиттера и настройка зоны

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

const shape1 = new Phaser.Geom.Circle(0, 0, 200);
const particles = this.add.particles('diamonds');

particles.createEmitter({
    // ... остальные параметры ...
    zone: { type: 'edge', source: shape1, quantity: 32 }
});

Параметр zone.type со значением 'edge' указывает, что частицы должны появляться по границе фигуры (source). quantity в контексте зоны определяет, сколько частиц будет создано за один выброс. Фигура Circle создаётся с центром в (0,0), но её позиция в мире задаётся отдельно через `xиy` эмиттера.

Волшебство параметра `frame`

Это сердце механизма анимации частиц. Параметр frame управляет тем, какой кадр из спрайтшита будет использован для частицы.

Базовый объект конфигурации позволяет задать массив кадров и режим их перебора. Флаг cycle включает циклическое проигрывание последовательности кадров для каждой отдельной частицы на протяжении её жизни (lifespan).

frame: { frames: [ 0, 1, 2, 3, 4 ], cycle: true, quantity: 32 }

Здесь frames — массив индексов кадров для анимации. cycle: true включает циклическое проигрывание этого массива. quantity в этом контексте — это количество кадров в анимации (можно не указывать, система возьмёт длину массива frames). Частица будет плавно переключаться от кадра 0 к кадру 4 и затем снова к 0, пока не истечёт её время жизни.

Скрытая сила `_frame` (экспериментальный параметр)

В исходном коде примера закомментированы несколько вариантов параметра _frame. Это экспериментальная, более низкоуровневая версия настройки кадров, которая может вести себя иначе, чем публичный frame. В оставленном варианте используется только два кадра.

_frame: { frames: [ 0, 1 ], cycle: true }

Важно: если указаны оба параметра (frame и _frame), система будет использовать значение _frame, так как оно имеет более высокий приоритет (это видно в итоговом примере — частицы мигают только двумя цветами). Используйте _frame для тонкой настройки, но помните, что его API может измениться в будущих версиях Phaser.

Сборка полной конфигурации эмиттера

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

particles.createEmitter({
    _frame: { frames: [ 0, 1 ], cycle: true }, // Используется этот параметр
    x: 400, // Мировая позиция эмиттера (и его зоны)
    y: 300,
    frequency: 32, // Период между выбросами (в мс). 32мс ~ 30 FPS
    lifespan: 400, // Время жизни одной частицы (в мс)
    alpha: { start: 1, end: 0 }, // Частица плавно исчезает
    zone: { type: 'edge', source: shape1, quantity: 32 }
});

Эмиттер расположен в центре экрана (400, 300). Каждые 32 миллисекунды по краю круга появляется 32 новые частицы. Каждая частица живёт 400 миллисекунд, за это время она проигрывает циклическую анимацию из кадров 0 и 1 и постепенно становится прозрачной. Параметр frequency создаёт непрерывный поток.

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

Использование циклической анимации кадров в системе частиц Phaser открывает путь к созданию сложных эффектов из простых спрайтшитов: от мерцающего магического тумана до падающих анимированных листьев. Экспериментируйте: попробуйте увеличить lifespan и добавить больше кадров в массив frames для более плавной анимации. Измените zone.type на 'random', чтобы частицы появлялись не по краю, а случайным образом внутри фигуры. Комбинируя это с другими параметрами, такими как scale или tint, вы сможете генерировать уникальные визуальные стили для своей игры с минимальными затратами на производительность.