О чем этот пример
Анимация множества объектов по отдельности может превратиться в рутину. 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-настройками на одну группу или создать собственный массив вариаций для уникальной последовательности эффектов.
