О чем этот пример
Создание динамичного движения — ключ к привлечению внимания игрока. Вращение объектов по орбитам, от планет вокруг звезды до спутников вокруг космического корабля, добавляет сцене жизни и сложности. В этой статье мы разберем, как с помощью встроенной функции Phaser `RotateAroundDistance` легко реализовать такие орбитальные системы, от простого вращения до сложных цепочек объектов, вращающихся друг вокруг друга.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
distance2 = 80;
angle2 = 0;
distance1 = 200;
angle1 = 0;
ball3;
ball2;
ball1;
preload ()
{
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ballRed', 'assets/demoscene/ball.png');
this.load.image('ballBlue', 'assets/demoscene/blue_ball.png');
this.load.image('ballSmall', 'assets/demoscene/ball-tlb.png');
}
create ()
{
this.ball1 = this.add.sprite(400, 300, 'ballRed');
this.ball2 = this.add.sprite(400, 300, 'ballBlue');
this.ball3 = this.add.sprite(400, 300, 'ballSmall');
}
update ()
{
// Reset the position so the rotation gets the correct _new_ position
this.ball2.setPosition(400, 300);
this.ball3.setPosition(400, 300);
Phaser.Math.RotateAroundDistance(this.ball2, this.ball1.x, this.ball1.y, this.angle1, this.distance1);
Phaser.Math.RotateAroundDistance(this.ball3, this.ball2.x, this.ball2.y, this.angle2, this.distance2);
this.angle1 = Phaser.Math.Angle.Wrap(this.angle1 + 0.02);
this.angle2 = Phaser.Math.Angle.Wrap(this.angle2 + 0.03);
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Подготовка сцены и спрайтов
Основа примера — один класс сцены, наследующий от Phaser.Scene. В его полях мы заранее объявляем свойства для хранения углов, расстояний и ссылок на спрайты. Это хорошая практика для удобства доступа к ним из разных методов.
В методе preload() загружаются три различные текстуры для шаров. Обратите внимание на использование setBaseURL — это позволяет указывать относительные пути к ассетам.
this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('ballRed', 'assets/demoscene/ball.png');
this.load.image('ballBlue', 'assets/demoscene/blue_ball.png');
this.load.image('ballSmall', 'assets/demoscene/ball-tlb.png');
В методе create() происходит инициализация спрайтов. Важный момент: все три шара изначально создаются с одинаковыми координатами (400, 300), то есть в центре экрана. Их визуальное разделение произойдет позже, в update().
this.ball1 = this.add.sprite(400, 300, 'ballRed');
this.ball2 = this.add.sprite(400, 300, 'ballBlue');
this.ball3 = this.add.sprite(400, 300, 'ballSmall');
Сердце анимации: метод update()
Вся магия движения происходит внутри метода update(), который вызывается на каждом кадре игры. Логику можно разделить на три ключевых шага.
**Шаг 1: Сброс позиции.** Перед вычислением новой позиции для вращающихся шаров (ball2 и ball3) их нужно вернуть в точку вращения. Иначе они будут вращаться не вокруг заданной точки, а по спирали, удаляясь от центра.
this.ball2.setPosition(400, 300);
this.ball3.setPosition(400, 300);
**Шаг 2: Вычисление новой позиции.** Здесь используется главный инструмент — статический метод Phaser.Math.RotateAroundDistance. Он принимает объект (или любую структуру с полями `xиy`), координаты центра вращения, текущий угол, расстояние до центра и возвращает новые координаты для объекта.
Phaser.Math.RotateAroundDistance(this.ball2, this.ball1.x, this.ball1.y, this.angle1, this.distance1);
Phaser.Math.RotateAroundDistance(this.ball3, this.ball2.x, this.ball2.y, this.angle2, this.distance2);
Первая строка заставляет синий шар (ball2) вращаться вокруг красного (ball1). Вторая строка создает цепочку: маленький шар (ball3) вращается уже вокруг синего (ball2), который, в свою очередь, вращается вокруг красного.
**Шаг 3: Обновление углов.** После перемещения спрайтов нужно увеличить угол вращения для следующего кадра. Чтобы угол не выходил за пределы диапазона от -π до π (что предотвратит возможные ошибки с большими числами), используется метод Phaser.Math.Angle.Wrap.
this.angle1 = Phaser.Math.Angle.Wrap(this.angle1 + 0.02);
this.angle2 = Phaser.Math.Angle.Wrap(this.angle2 + 0.03);
Разные значения приращения (0.02 и 0.03) задают разную скорость вращения, что делает анимацию более интересной.
Настройка игры: объект config
Запуск игры осуществляется стандартным для Phaser способом. Создается объект конфигурации, где указывается тип рендерера, элемент-контейнер, размеры холста и класс основной сцены.
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
После этого экземпляр игры game начинает свой жизненный цикл, автоматически вызывая preload(), create(), а затем update() в цикле.
Как работает RotateAroundDistance
Понимание механики метода помогает применять его в нестандартных ситуациях. Функция RotateAroundDistance не "двигает" спрайт сама по себе, а **возвращает новые координаты** точки в пространстве, повернутой на заданный угол относительно центра.
Внутри она использует тригонометрические формулы. Новые координаты вычисляются так:
- x = centerX + distance * Math.cos(angle)
- y = centerY + distance * Math.sin(angle)
Метод напрямую модифицирует поля `xиy` переданного ему объекта. Именно поэтому мы сначала сбрасываем позицию спрайта в центр вращения — метод берет текущие координаты объекта как точку отсчета для преобразования.
Практические применения и вариации
Этот паттерн вращения универсален. Вот несколько идей для использования:
* **Система планет:** Сделайте центральный спрайт (ball1) солнцем, а вращающиеся вокруг (ball2, ball3) — планетами с их собственными лунами (ball3).
* **Щит или кольцо артефактов:** Несколько спрайтов, вращающихся с одним углом, но разным начальным смещением, создадут эффект защитного кольца вокруг персонажа.
* **Сложные траектории:** Комбинируйте вращение с другими видами движения. Например, заставьте сам центр вращения (ball1) двигаться по экрану — вся цепочка будет следовать за ним по сложной орбите.
// Пример: движение центра вращения
update() {
// Двигаем центральный шар по горизонтали
this.ball1.x += 1;
if (this.ball1.x > 800) { this.ball1.x = 0; }
// Далее стандартная логика сброса позиции и вращения...
}
Экспериментируйте с динамическим изменением distance и скорости приращения угла в реальном времени для создания эффектов притяжения, отталкивания или ускорения.
Что попробовать дальше
Метод Phaser.Math.RotateAroundDistance — это мощный и простой инструмент для создания сложного на вид орбитального движения. Он избавляет разработчика от ручного написания тригонометрических расчетов. Для экспериментов попробуйте: изменить расстояния и скорости в реальном времени по клику, создать не цепочку, а «рои» из множества спрайтов, вращающихся вокруг одного центра, или привязать вращение не к спрайту, а к физическому телу для создания орбитальных гравитационных эффектов.
