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