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

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

Версия 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.spritesheet('diamonds', 'assets/sprites/diamonds32x24x5.png', { frameWidth: 32, frameHeight: 24 });
    }

    create ()
    {
        this.group = this.add.group();

        for (var i = 0; i < 256; i++)
        {
            this.group.create(Phaser.Math.Between(200, 600), Phaser.Math.Between(100, 500), 'diamonds', Phaser.Math.Between(0, 4));
        }

        this.geomPoint = new Phaser.Math.Vector2(400, 300);

        this.input.on('pointermove', function (pointer) {
            this.geomPoint.setTo(pointer.x, pointer.y);
        }, this);
    }

    update ()
    {
        Phaser.Actions.RotateAroundDistance(this.group.getChildren(), this.geomPoint, 0.1, 100);
    }
}

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

const game = new Phaser.Game(config);

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

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

В create инициализируется группа (Group) — мощный контейнер Phaser для управления коллекциями игровых объектов. Цикл создает 256 спрайтов, случайно размещая их на поле и назначая случайный кадр из спрайтшита. Все они автоматически добавляются в группу this.group.

Также создается вектор geomPoint, который будет хранить целевую точку вращения. Изначально она установлена в центр экрана (400, 300). Событие pointermove обновляет координаты этой точки, следуя за курсором мыши.

this.group = this.add.group();

for (var i = 0; i < 256; i++)
{
    this.group.create(Phaser.Math.Between(200, 600), Phaser.Math.Between(100, 500), 'diamonds', Phaser.Math.Between(0, 4));
}

this.geomPoint = new Phaser.Math.Vector2(400, 300);

this.input.on('pointermove', function (pointer) {
    this.geomPoint.setTo(pointer.x, pointer.y);
}, this);

Магия одного вызова: RotateAroundDistance

Вся анимация вращения происходит в методе update, который вызывается каждый кадр. Ключевая роль здесь у статического метода Phaser.Actions.RotateAroundDistance.

Этот метод принимает четыре аргумента:
1.  Массив объектов для вращения (`this.group.getChildren()`).
2.  Целевую точку (`this.geomPoint`).
3.  Угол поворота за вызов (в радианах). Значение `0.1` обеспечивает плавное вращение.
4.  Фиксированное расстояние от объекта до точки вращения (в пикселях). Все объекты будут находиться на расстоянии 100 пикселей от `geomPoint`.

Метод вычисляет новую позицию для каждого спрайта в массиве, перемещая его по воображаемой окружности с заданным радиусом вокруг целевой точки.

Phaser.Actions.RotateAroundDistance(this.group.getChildren(), this.geomPoint, 0.1, 100);

Как это работает "под капотом"?

Важно понимать, что RotateAroundDistance не изменяет свойства вращения (rotation или angle) самих спрайтов. Вместо этого он напрямую пересчитывает и устанавливает их координаты `xиy` на основе тригонометрических формул.

Для каждого объекта вычисляется текущий угол относительно центральной точки, к нему прибавляется дельта (0.1), а затем по новому углу и заданному радиусу (100) рассчитываются новые декартовы координаты.

Поскольку расстояние фиксировано, все спрайты выстраиваются в идеальный круг, центром которого является geomPoint. Их первоначальное случайное положение игнорируется после первого же вызова метода в update. Движение мыши меняет центр этого круга, и вся система мгновенно перестраивается вокруг новой точки.

Конфигурация игры и запуск

Пример завершается стандартной конфигурацией игрового экземпляра Phaser. Ключевые параметры: * type: Phaser.AUTO позволяет движку выбрать WebGL или Canvas рендеринг. * width/height: Размеры игрового поля. * scene: Указывает класс, который будет управлять логикой (наш Example).

Инициализация игры с этой конфигурацией запускает жизненный цикл сцены (preload, create, update).

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

const game = new Phaser.Game(config);

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

Phaser.Actions.RotateAroundDistance — это элегантное и производительное решение для анимации групп объектов по круговой траектории. Всего одна строка кода заменяет ручные тригонометрические расчеты в цикле. Для экспериментов попробуйте: динамически менять радиус вращения в зависимости от времени или положения мыши; привязать угол поворота к скорости движения курсора; использовать другой метод из Phaser.Actions, например, Call или PlaceOnCircle, для комбинирования поведения. Это откроет путь к созданию сложных и визуально насыщенных эффектов с минимальными усилиями.