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

Работа с группой объектов по геометрической форме — мощный приём для создания сложных визуальных эффектов с минимальным кодом. В этом примере мы рассмотрим, как равномерно разместить спрайты по окружности и анимировать их движение, создавая гипнотизирующий вращающийся узор. Этот подход полезен для создания меню, индикаторов загрузки, спецэффектов или любого системного расположения объектов в вашей игре.

Версия 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 ()
    {
        const circle = new Phaser.Geom.Circle(400, 300, 260);

        this.group = this.add.group({ key: 'ball', frameQuantity: 32 });

        Phaser.Actions.PlaceOnCircle(this.group.getChildren(), circle);

        this.tween = this.tweens.addCounter({
            from: 260,
            to: 0,
            duration: 3000,
            delay: 2000,
            ease: 'Sine.easeInOut',
            repeat: -1,
            yoyo: true
        });
    }

    update ()
    {
        Phaser.Actions.RotateAroundDistance(this.group.getChildren(), { x: 400, y: 300 }, 0.02, this.tween.getValue());
    }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    backgroundColor: '#2d2d2d',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

Подготовка сцены и загрузка ресурсов

В методе preload мы устанавливаем базовый URL для загрузки ресурсов из репозитория примеров Phaser и загружаем одно изображение спрайта — блестящий шарик. Этот спрайт будет использован для всех элементов группы.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('ball', 'assets/sprites/shinyball.png');
}

Создание геометрии и группы объектов

В методе create определяется логика инициализации. Сначала создаётся геометрический объект Phaser.Geom.Circle, представляющий окружность с центром в точке (400, 300) и начальным радиусом 260 пикселей.

Затем создаётся группа (this.add.group). Ключевой параметр key указывает на загруженное изображение 'ball', а frameQuantity определяет, сколько одинаковых спрайтов будет создано в этой группе — в нашем случае 32.

const circle = new Phaser.Geom.Circle(400, 300, 260);
this.group = this.add.group({ key: 'ball', frameQuantity: 32 });

Размещение объектов по окружности

Сразу после создания группы все её дочерние элементы (спрайты) равномерно размещаются на ранее заданной окружности с помощью статического метода Phaser.Actions.PlaceOnCircle. Первым аргументом передаётся массив детей группы, полученный через this.group.getChildren(), вторым — объект окружности.

Это действие мгновенно рассчитывает позиции для всех 32 спрайтов и расставляет их по кругу.

Phaser.Actions.PlaceOnCircle(this.group.getChildren(), circle);

Анимация пульсирующего радиуса

Чтобы оживить композицию, создаётся твин для числового значения (Counter). Он плавно изменяет значение от 260 (начальный радиус) до 0 и обратно. Параметры repeat: -1 и yoyo: true заставляют анимацию бесконечно повторяться в режиме «туда-обратно». Задержка delay в 2000 мс даёт игроку время рассмотреть начальное расположение.

Объект твина сохраняется в свойство сцены this.tween для дальнейшего использования.

this.tween = this.tweens.addCounter({
    from: 260,
    to: 0,
    duration: 3000,
    delay: 2000,
    ease: 'Sine.easeInOut',
    repeat: -1,
    yoyo: true
});

Динамическое вращение в реальном времени

Сердцевина анимации находится в методе `update`, который вызывается на каждом кадре. Здесь используется метод `Phaser.Actions.RotateAroundDistance`. Он выполняет две операции одновременно для каждого спрайта в группе:
1.  **Вращение:** Поворачивает позицию каждого спрайта вокруг центральной точки { x: 400, y: 300 } на угол 0.02 радиана за кадр.
2.  **Изменение дистанции:** Устанавливает расстояние от спрайта до центра вращения равным текущему значению твина, полученному через `this.tween.getValue()`. Это заставляет круг из шариков пульсировать.

Таким образом, спрайты не просто вращаются, а делают это по постоянно сжимающейся и расширяющейся орбите.

update ()
{
    Phaser.Actions.RotateAroundDistance(this.group.getChildren(), { x: 400, y: 300 }, 0.02, this.tween.getValue());
}

Что попробовать дальше

Комбинация PlaceOnCircle для начального размещения и RotateAroundDistance в цикле обновления — элегантный и производительный способ анимировать сложные паттерны. Для экспериментов попробуйте: изменить количество и тип спрайтов в группе, использовать другую геометрическую форму (например, PlaceOnEllipse), варьировать скорость вращения или радиус через твин, а также добавить обработку кликов по отдельным шарикам для создания интерактивных интерфейсов.