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

Анимация множества объектов по отдельности может превратиться в рутину. Phaser предлагает элегантное решение — метод `tweens.stagger()`. Он позволяет легко создавать последовательные, волнообразные и визуально привлекательные эффекты для групп объектов, управляя задержкой между началом анимации каждого элемента. Эта статья на практическом примере покажет, как использовать `stagger` для анимации сетки из 256 спрайтов с различными настройками, превращая статичную картинку в динамичное зрелище.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    constructor ()
    {
        super();
    }

    preload ()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.image('bg', 'assets/skies/darkstone.png');
        this.load.image('block', 'assets/sprites/32x32-white.png');
    }

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

        const hsv = Phaser.Display.Color.HSVColorWheel();

        const gw = 16;
        const gh = 16;
        const bs = 32;

        const group = this.add.group({
            key: 'block',
            quantity: gw * gh,
            gridAlign: {
                width: gw,
                height: gh,
                cellWidth: bs,
                cellHeight: bs,
                x: (800 - (bs * gw)) / 2,
                y: (600 - (bs * gh) + bs / 2) / 2
            }
        });

        const size = gw * gh;

        //  Tint them
        group.getChildren().forEach((child, index) => {

            const c = Math.floor(index * (360 / size));

            child.setTint(hsv[c].color);

        });

        this.variations = [
            [ 200, { grid: [ gw, gh ], from: 'first' } ],
            [ 200, { grid: [ gw, gh ], from: 'center' } ],
            [ 200, { grid: [ gw, gh ], from: 'last' } ],
            [ 30, { from: 'center' } ],
            [ 30, { from: 'first' } ],
            [ 30, { from: 'last' } ],
            [ 30, { from: 201 } ],
            [ 30, { from: 46 } ],
            [ 30, { ease: 'quad.out' } ],
            [ 30, { ease: 'quart.in' } ],
            [ 30, { ease: 'sine.inout' } ],
            [ 30, { from: 'last', ease: 'quad.inout' } ],
            [ [ 1500, 3000 ] ],
            [ [ 0, 5000 ], { from: 'center' } ],
            [ 20, { ease: 'cubic.inout', from: 'center' } ],
            [ [ 500, 5000 ], { from: 'center' } ],
            [ 20, { ease: 'power2', from: 'center' } ],
            [ [ 100, 600 ], { ease: 'cubic.inout' } ],
        ];

        const text = this.add.text(400, 24, '', { font: '18px Courier', fill: '#ffffff' }).setOrigin(0.5);

        this.getStaggerTween(0, group, text);
    }

    getStaggerTween (i, group, text)
    {
        const stagger = this.variations[i];

        text.setText(`this.tweens.stagger(${JSON.stringify(stagger[0])}, ${JSON.stringify(stagger[1])})`);

        this.tweens.add({
            targets: group.getChildren(),
            scale: 0.2,
            ease: 'linear',
            duration: 500,
            delay: this.tweens.stagger(...stagger),
            completeDelay: 1000,
            onComplete: () =>
            {
                group.getChildren().forEach(child => {

                    child.setScale(1);

                });

                if (i < this.variations.length - 1)
                {
                    i++;
                }
                else
                {
                    i = 0;
                }

                this.getStaggerTween(i, group, text);
            }
        });
    }
}

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

const game = new Phaser.Game(config);

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

В методе preload() загружаются два изображения: фон и белый квадрат 32x32 пикселя, который будет использован как спрайт.

В create() первым делом добавляется фон. Затем создается массив hsv, представляющий цветовое колесо в формате HSV, что позволит легко раскрасить сетку в радужные цвета.

Ключевой момент — создание группы (Group) с помощью this.add.group(). В конфигурации указывается: * key: ключ изображения для всех элементов группы. * quantity: общее количество элементов (16x16 = 256). * gridAlign: параметры для автоматического выравнивания элементов в сетку. Здесь задаются размеры сетки, размер ячейки и центральное позиционирование на экране.

После создания группы каждый её элемент (ребёнок, child) получает уникальный оттенок из подготовленного цветового колеса с помощью метода setTint().

const group = this.add.group({
    key: 'block',
    quantity: gw * gh,
    gridAlign: {
        width: gw,
        height: gh,
        cellWidth: bs,
        cellHeight: bs,
        x: (800 - (bs * gw)) / 2,
        y: (600 - (bs * gh) + bs / 2) / 2
    }
});
group.getChildren().forEach((child, index) => {
    const c = Math.floor(index * (360 / size));
    child.setTint(hsv[c].color);
});

Сердце примера: массив вариаций stagger

Вся демонстрация построена на массиве this.variations. Каждый его элемент — это массив, который будет передан в this.tweens.stagger(). Первый элемент подмассива — это значение задержки, второй (если есть) — объект конфигурации.

Именно здесь показана гибкость stagger: 1. **Базовое использование:** [ 200, { grid: [ gw, gh ], from: 'first' } ] — задержка 200 мс, применяется к сетке, начиная с первого элемента. 2. **Разные точки старта:** from может быть 'first', 'center', 'last' или числовым индексом конкретного элемента (например, 201). 3. **Разные функции плавности (easing):** 'quad.out', 'sine.inout' и другие. 4. **Случайная задержка:** [ [1500, 3000] ] — задержка будет случайным образом выбираться между 1500 и 3000 мс для каждого объекта.

Массив вариаций — это готовый набор рецептов для разных визуальных эффектов, которые будут проигрываться по циклу.

this.variations = [
    [ 200, { grid: [ gw, gh ], from: 'first' } ],
    [ 30, { from: 201 } ],
    [ 30, { ease: 'sine.inout' } ],
    [ [ 1500, 3000 ] ],
    [ 20, { ease: 'power2', from: 'center' } ],
];

Запуск анимации: функция getStaggerTween

Функция getStaggerTween(i, group, text) — это двигатель демо. Она принимает индекс текущей вариации из массива, группу спрайтов и текстовый объект для отображения информации.

1.  **Отображение кода:** Текст на экране обновляется, показывая точный вызов `this.tweens.stagger()` с параметрами текущей вариации.
2.  **Создание твина:** Создаётся стандартный твин с помощью `this.tweens.add()`. Его цель (`targets`) — все дети группы. Он масштабирует каждый спрайт до 0.2.
3.  **Применение stagger:** Волшебство происходит в свойстве `delay`. В него передаётся результат вызова `this.tweens.stagger(...stagger)`. Этот вызов на основе переданных параметров (`stagger[0]` и `stagger[1]`) вычисляет индивидуальную задержку для *каждого* объекта в группе. Таким образом, твин запускается не одновременно для всех, а в красивой последовательности.
4.  **Цикличность:** После завершения анимации (`onComplete`) все спрайты возвращаются к исходному масштабу, индекс вариации обновляется (или сбрасывается к нулю), и функция вызывается рекурсивно для запуска следующего эффекта.
this.tweens.add({
    targets: group.getChildren(),
    scale: 0.2,
    ease: 'linear',
    duration: 500,
    delay: this.tweens.stagger(...stagger),
    completeDelay: 1000,
    onComplete: () => {
        group.getChildren().forEach(child => { child.setScale(1); });
        i = (i < this.variations.length - 1) ? i + 1 : 0;
        this.getStaggerTween(i, group, text);
    }
});

Ключевые параметры метода stagger

Давайте подробнее разберем аргументы, которые принимает this.tweens.stagger():

* **Первый аргумент (value):** Определяет величину задержки. Это может быть: * Число (например, 30) — фиксированная задержка в миллисекундах между объектами. * Массив из двух чисел (например, [100, 600]) — задержка будет случайным значением в этом диапазоне для каждого объекта. * **Второй аргумент (config):** Объект с дополнительными настройками: * grid: [width, height] — Указывает, что объекты расположены в сетке. Задержка будет рассчитываться не просто по списку, а учитывая соседние элементы по горизонтали и вертикали, создавая волну. * from: 'first' | 'center' | 'last' | number — Точка, от которой начинает распространяться задержка. 'center' создает эффект расходящейся из центра волны. * ease: string — Функция плавности (easing function) применяется не к самой анимации масштаба, а к *распределению задержек*. Это меняет характер последовательности (например, 'cubic.inout' ускоряет и замедляет появление задержки между объектами).

Сочетание этих параметров порождает всё многообразие эффектов в демонстрации.

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

Метод tweens.stagger() — это мощный инструмент для создания сложных групповых анимаций с минимальным кодом. Он избавляет от необходимости вручную рассчитывать тайминги для каждого объекта. Для экспериментов попробуйте: изменить форму сетки (например, на 32x8), применить stagger к другим свойствам (углу поворота, прозрачности, цвету), комбинировать несколько твинов с разными stagger-настройками на одну группу или создать собственный массив вариаций для уникальной последовательности эффектов.