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