О чем этот пример
Визуальные эффекты часто требуют точного расположения множества объектов. В Phaser для этих целей есть мощный модуль `Phaser.Actions`. В этой статье мы разберем, как с его помощью равномерно разместить группу спрайтов по форме эллипса и анимировать их, динамически изменяя параметры этой фигуры. Этот прием полезен для создания орбит, аудиовизуализаторов, магических кругов или просто для оживления фоновых элементов.
Версия 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('ball', 'assets/sprites/shinyball.png');
}
create ()
{
this.ellipse = new Phaser.Geom.Ellipse(400, 300, 200, 500);
this.group = this.add.group({ key: 'ball', frameQuantity: 48 });
Phaser.Actions.PlaceOnEllipse(this.group.getChildren(), this.ellipse);
this.tweens.add({
targets: this.ellipse,
width: 700,
height: 100,
delay: 1000,
duration: 2000,
ease: 'Sine.easeInOut',
repeat: -1,
yoyo: true
});
}
update ()
{
Phaser.Actions.PlaceOnEllipse(this.group.getChildren(), this.ellipse);
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#2d2d2d',
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и создание геометрии
В методе preload() мы загружаем текстуру спрайта. Основная логика начинается в create(). Первым делом мы создаем объект эллипса, который будет служить нашим шаблоном для размещения.
this.ellipse = new Phaser.Geom.Ellipse(400, 300, 200, 500);
Здесь задаются координаты центра эллипса (400x300), его начальная ширина (200 пикселей) и высота (500 пикселей). Затем мы создаем группу из 48 одинаковых спрайтов с ключом 'ball'.
this.group = this.add.group({ key: 'ball', frameQuantity: 48 });
Первоначальное размещение спрайтов
Сразу после создания группы мы используем статический метод Phaser.Actions.PlaceOnEllipse. Он принимает массив детей группы и объект эллипса, после чего мгновенно расставляет все спрайты по его периметру с равными интервалами.
Phaser.Actions.PlaceOnEllipse(this.group.getChildren(), this.ellipse);
Важно: this.group.getChildren() возвращает массив всех спрайтов в группе. Метод ничего не возвращает, а модифицирует координаты `xиy` каждого переданного ему игрового объекта.
Анимация формы эллипса
Чтобы картина стала динамичной, мы настраиваем твин для объекта this.ellipse. Твин будет циклически изменять его ширину и высоту, создавая эффект "пульсации" или "деформации".
this.tweens.add({
targets: this.ellipse,
width: 700,
height: 100,
delay: 1000,
duration: 2000,
ease: 'Sine.easeInOut',
repeat: -1,
yoyo: true
});
Параметры width: 700 и height: 100 — это целевые значения. Твин плавно преобразует эллипс из высокого и узкого в широкий и плоский. repeat: -1 заставляет анимацию повторяться бесконечно, а yoyo: true означает, что после достижения цели анимация проиграется в обратном порядке.
Непрерывное обновление позиций
Поскольку твин изменяет геометрический объект ellipse в реальном времени, нам нужно постоянно обновлять позиции спрайтов в соответствии с новой формой. Это делается в методе update(), который вызывается на каждом кадре.
update ()
{
Phaser.Actions.PlaceOnEllipse(this.group.getChildren(), this.ellipse);
}
Вызов того же метода PlaceOnEllipse на каждом кадре заставляет спрайты непрерывно "прилипать" к изменяющемуся контуру эллипса. Это создает иллюзию сложного синхронного движения, хотя мы анимируем всего один параметр — геометрическую фигуру.
Что попробовать дальше
Метод Phaser.Actions.PlaceOnEllipse — это отличный инструмент для быстрого и эффективного расположения объектов по сложной траектории. Объединив его с системой твинов, можно создавать живые, сложноорганизованные паттерны движения минимальными усилиями. Для экспериментов попробуйте: заменить эллипс на другую фигуру (например, Phaser.Geom.Triangle), анимировать не форму, а положение центра эллипса, или менять не все спрайты в группе, а только их часть, передавая в метод отфильтрованный массив.
