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

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