О чем этот пример
Управление множеством объектов и создание сложных анимаций — частые задачи в разработке игр. В этом примере наглядно показано, как использовать мощь системы контейнеров (Container) и твинов (Tween) в Phaser для генерации гипнотического визуального эффекта "вихря" из сотен спрайтов. Вы научитесь группировать объекты, применять к ним анимации и управлять их свойствами как единым целым, что критически важно для оптимизации и создания динамичных сцен.
Версия 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('rick2', 'assets/demoscene/large-raster32.png');
this.load.image('rick', 'assets/demoscene/raster-bw-800x16.png');
// this.load.image('rick2', 'assets/demoscene/rastercarpet32.png');
// this.load.image('rick2', 'assets/demoscene/purple-raster.png');
}
create ()
{
const sprite1 = this.add.sprite(0, 100, 'rick');
const sprite2 = this.add.sprite(-100, 0, 'rick').setAngle(90);
const sprite3 = this.add.sprite(0, -100, 'rick').setAngle(180);
const sprite4 = this.add.sprite(100, 0, 'rick').setAngle(270);
const containers = [];
for (let i = 0; i < 128; i++)
{
const container = this.add.container(400, 300);
if (i > 0)
{
container.setExclusive(false);
}
container.add([ sprite1, sprite2, sprite3, sprite4 ]);
containers.push(container);
}
this.tweens.add({
targets: sprite1,
y: -200,
ease: 'Sine.easeInOut',
duration: 2000,
repeat: -1,
yoyo: true
});
this.tweens.add({
targets: sprite2,
x: 200,
ease: 'Sine.easeInOut',
duration: 4000,
repeat: -1,
yoyo: true
});
this.tweens.add({
targets: sprite3,
y: 200,
ease: 'Sine.easeInOut',
duration: 2000,
repeat: -1,
yoyo: true
});
this.tweens.add({
targets: sprite4,
x: -200,
ease: 'Sine.easeInOut',
duration: 4000,
repeat: -1,
yoyo: true
});
this.tweens.add({
targets: containers,
angle: { value: 360, duration: 6000 },
scaleX: { value: 2, duration: 6000, yoyo: true, ease: 'Quad.easeInOut' },
scaleY: { value: 4.5, duration: 6000, yoyo: true, ease: 'Quad.easeInOut' },
repeat: -1,
delay: function (target, key, value, index, total, tween)
{
return index * 128;
}
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#000000',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и загрузка ассетов
В методе preload загружаются два растровых изображения. Основное изображение 'rick' — это черно-белая текстура, которая будет использоваться для создания базовых спрайтов. Изображение 'rick2' загружается, но в данном примере не используется напрямую — оно закомментировано, что показывает возможность быстрого переключения визуального стиля для экспериментов.
this.load.image('rick2', 'assets/demoscene/large-raster32.png');
this.load.image('rick', 'assets/demoscene/raster-bw-800x16.png');
Создание базового модуля из спрайтов
В методе create создаются четыре спрайта из одной текстуры 'rick'. Ключевой момент — они позиционируются относительно точки (0, 0) и поворачиваются на 90 градусов друг относительно друга с помощью метода setAngle(). В результате получается крестообразная фигура.
const sprite1 = this.add.sprite(0, 100, 'rick');
const sprite2 = this.add.sprite(-100, 0, 'rick').setAngle(90);
const sprite3 = this.add.sprite(0, -100, 'rick').setAngle(180);
const sprite4 = this.add.sprite(100, 0, 'rick').setAngle(270);
Группировка объектов в контейнеры
Далее в цикле создаются 128 контейнеров. Контейнер (this.add.container) — это объект, который может содержать в себе другие игровые объекты, позволяя управлять их трансформацией (позиция, масштаб, поворот) как одной группой. Каждый контейнер позиционируется в центре экрана (400, 300). В каждый контейнер добавляются одни и те же четыре спрайта, созданные ранее. Важный нюанс — для всех контейнеров, кроме первого, вызывается container.setExclusive(false). Это позволяет спрайтам принадлежать нескольким контейнерам одновременно, что экономит память.
const container = this.add.container(400, 300);
if (i > 0) {
container.setExclusive(false);
}
container.add([ sprite1, sprite2, sprite3, sprite4 ]);
Анимация движения спрайтов
Чтобы оживить базовый модуль, к каждому из четырех спрайтов применяется отдельный твин с помощью this.tweens.add. Твины плавно меняют координаты `xилиyспрайтов, создавая эффект пульсации или "дыхания" крестообразной фигуры. Параметрыrepeat: -1иyoyo: true` обеспечивают бесконечное повторение анимации туда-обратно.
this.tweens.add({
targets: sprite1,
y: -200,
ease: 'Sine.easeInOut',
duration: 2000,
repeat: -1,
yoyo: true
});
Массовая анимация контейнеров
Самый зрелищный эффект создается твином, который воздействует сразу на массив из всех 128 контейнеров (targets: containers). Этот твин выполняет три действия одновременно: вращает контейнеры на 360 градусов, масштабирует их по осям X и Y с разной интенсивностью. Функция в параметре delay — это изюминка примера. Она вычисляет задержку для старта анимации каждого контейнера в зависимости от его индекса в массиве. Это создает волнообразный, последовательный эффект вращения и масштабирования.
delay: function (target, key, value, index, total, tween) {
return index * 128;
}
Что попробовать дальше
Этот пример демонстрирует элегантную композицию из простых элементов: несколько спрайтов, множество контейнеров и твины. Для экспериментов попробуйте изменить количество контейнеров, параметры твинов (например, scaleY или angle), функцию задержки или заменить текстуру на одну из закомментированных в preload. Вы можете анимировать не контейнеры, а сами спрайты внутри них, чтобы создать еще более сложные композиционные движения.
