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

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