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

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

Версия 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.group1 = this.add.group({ key: 'ball', frameQuantity: 36 });
        this.group2 = this.add.group({ key: 'ball', frameQuantity: 32 });
        this.group3 = this.add.group({ key: 'ball', frameQuantity: 26 });
        this.group4 = this.add.group({ key: 'ball', frameQuantity: 16 });

        this.circle1 = new Phaser.Geom.Circle(400, 300, 200);
        this.circle2 = new Phaser.Geom.Circle(400, 300, 160);
        this.circle3 = new Phaser.Geom.Circle(400, 300, 120);
        this.circle4 = new Phaser.Geom.Circle(400, 300, 80);

        Phaser.Actions.PlaceOnCircle(this.group1.getChildren(), this.circle1);
        Phaser.Actions.PlaceOnCircle(this.group2.getChildren(), this.circle2);
        Phaser.Actions.PlaceOnCircle(this.group3.getChildren(), this.circle3);
        Phaser.Actions.PlaceOnCircle(this.group4.getChildren(), this.circle4);
    }

    update ()
    {
        Phaser.Actions.RotateAroundDistance(this.group1.getChildren(), this.circle1, -0.030, this.circle1.radius);
        Phaser.Actions.RotateAroundDistance(this.group2.getChildren(), this.circle2, 0.025, this.circle2.radius);
        Phaser.Actions.RotateAroundDistance(this.group3.getChildren(), this.circle3, -0.020, this.circle3.radius);
        Phaser.Actions.RotateAroundDistance(this.group4.getChildren(), this.circle4, 0.015, this.circle4.radius);
    }
}

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.add.group. Ключевой параметр frameQuantity определяет, сколько копий спрайта будет создано в каждой группе.

this.group1 = this.add.group({ key: 'ball', frameQuantity: 36 });
this.group2 = this.add.group({ key: 'ball', frameQuantity: 32 });
this.group3 = this.add.group({ key: 'ball', frameQuantity: 26 });
this.group4 = this.add.group({ key: 'ball', frameQuantity: 16 });

Каждая группа содержит разное количество спрайтов. Это важно для визуального разнообразия итогового эффекта. Группы в Phaser — это мощный инструмент для управления коллекциями похожих игровых объектов (спрайтов, частиц).

Геометрия: создание концентрических окружностей

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

this.circle1 = new Phaser.Geom.Circle(400, 300, 200);
this.circle2 = new Phaser.Geom.Circle(400, 300, 160);
this.circle3 = new Phaser.Geom.Circle(400, 300, 120);
this.circle4 = new Phaser.Geom.Circle(400, 300, 80);

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

Первоначальное размещение объектов

Метод Phaser.Actions.PlaceOnCircle — это рабочая лошадка этого примера. Он принимает массив объектов (в нашем случае — детей группы, полученных через getChildren()) и окружность (Phaser.Geom.Circle). Алгоритм равномерно распределяет все переданные объекты по периметру заданной окружности.

Phaser.Actions.PlaceOnCircle(this.group1.getChildren(), this.circle1);
Phaser.Actions.PlaceOnCircle(this.group2.getChildren(), this.circle2);
// ... и так для остальных групп

После этого вызова 36 шариков первой группы окажутся равномерно расставленными по большой окружности радиусом 200 пикселей, 32 шарика второй группы — по окружности радиусом 160 пикселей, и так далее. На этом этапе формируется статичная, но уже впечатляющая картина.

Анимация с помощью RotateAroundDistance

Статичное изображение оживает в методе update, который вызывается на каждом кадре игры. За движение отвечает Phaser.Actions.RotateAroundDistance. Этот метод вращает массив объектов вокруг заданной точки (центра окружности) на определенный угол и автоматически пересчитывает их позиции, чтобы сохранить исходное расстояние до центра.

Phaser.Actions.RotateAroundDistance(this.group1.getChildren(), this.circle1, -0.030, this.circle1.radius);
Phaser.Actions.RotateAroundDistance(this.group2.getChildren(), this.circle2, 0.025, this.circle2.radius);

Второй аргумент — это сама окружность. Phaser использует ее свойство `xиyкак центр вращения. Третий аргумент — угол поворота в радианах за один кадр. Обратите внимание, что знаки углов (-0.030и0.025`) разные — это заставляет группы вращаться в противоположных направлениях, создавая более сложный и динамичный визуальный паттерн. Четвертый аргумент фиксирует расстояние от объекта до центра вращения.

Настройка игры (config)

Конфигурационный объект игры стандартен. Важные параметры: - width: 800, height: 600 — задают размер игрового поля. - backgroundColor: '#2d2d2d' — устанавливает темно-серый фон, на котором яркие шарики будут смотреться контрастно. - scene: Example — указывает, что стартовой сценой будет наш класс Example.

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

Инициализация игры происходит строкой new Phaser.Game(config);, после чего запускается жизненный цикл сцены (preload, create, update).

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

Пример наглядно демонстрирует мощь и элегантность Phaser.Actions для групповых операций. Всего несколько строк кода заменяют десятки ручных вычислений позиций для каждого спрайта. Для экспериментов попробуйте изменить количество спрайтов в группах, радиусы окружностей или угловые скорости в update. Можно добавить интерактивность: например, при клике мыши менять направление вращения конкретной группы или динамически создавать новые группы объектов. Также интересно будет привязать скорость вращения к физическому времени (this.game.loop.delta) для независимости от частоты кадров.