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

При разработке игр часто нужно, чтобы объект двигался по идеальной окружности — будь то вращение спутника вокруг планеты, траектория пули или орбитальная камера. Вручную рассчитывать координаты через синусы и косинусы каждый раз неудобно. Phaser предоставляет для этой задачи готовый и точный метод. В этой статье мы разберем, как с помощью `Phaser.Geom.Circle.CircumferencePoint` заставить объект плавно перемещаться по краю круга, и как это можно применить в ваших проектах.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    a = 0;
    point;
    circle;
    graphics;

    create ()
    {
        this.graphics = this.add.graphics({ fillStyle: { color: 0x00ff00 } });

        this.circle = new Phaser.Geom.Circle(400, 300, 150);
        this.point = new Phaser.Geom.Rectangle(0, 0, 8, 8);
    }

    update ()
    {
        this.a += 0.04;

        if (this.a >= Phaser.Math.PI2)
        {
            this.a -= Phaser.Math.PI2;
        }

        Phaser.Geom.Circle.CircumferencePoint(this.circle, this.a, this.point);

        this.graphics.clear();
        this.graphics.fillRect(this.point.x - 4, this.point.y - 4, this.point.width, this.point.height);
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и объектов

В начале, в методе create(), мы создаем необходимые графические объекты и геометрические фигуры. Это основа для визуализации движения.

this.graphics = this.add.graphics({ fillStyle: { color: 0x00ff00 } });

Здесь мы создаем объект Graphics для отрисовки. Мы задаем ему зеленый цвет заливки (0x00ff00), который будет использоваться для нашего движущегося объекта.

this.circle = new Phaser.Geom.Circle(400, 300, 150);
this.point = new Phaser.Geom.Rectangle(0, 0, 8, 8);

Создаем геометрическое представление круга с центром в точке (400, 300) и радиусом 150 пикселей. Объект this.point — это не точка, а прямоугольник (Rectangle). Почему? Потому что метод CircumferencePoint будет записывать результат (координаты X и Y) в свойства .x и .y переданного ему объекта. Удобно использовать Rectangle или Circle, так как они уже имеют эти свойства. Мы инициализируем его с нулевыми координатами и размером 8x8 пикселя — это будет размер нашего маркера на экране.

Магия метода CircumferencePoint

Вся логика движения происходит в методе update(). Ключевую роль играет статический метод Phaser.Geom.Circle.CircumferencePoint.

this.a += 0.04;
if (this.a >= Phaser.Math.PI2) {
    this.a -= Phaser.Math.PI2;
}

Переменная this.a — это наш угол в радианах. Каждый кадр мы увеличиваем его на 0.04, создавая плавное изменение. Phaser.Math.PI2 — это константа, равная 2 * PI (примерно 6.283). Когда угол достигает полного круга, мы вычитаем PI2, чтобы сбросить его, предотвращая переполнение и бесконечный рост.

Phaser.Geom.Circle.CircumferencePoint(this.circle, this.a, this.point);
Это сердце примера. Метод принимает три аргумента:
1.  Объект круга (`this.circle`), по окружности которого мы движемся.
2.  Угол в радианах (`this.a`), определяющий текущую позицию на окружности (0 радиан соответствует точке справа от центра).
3.  Объект (`this.point`), в свойства `.x` и `.y` которого будут записаны рассчитанные координаты точки на окружности.
Метод не создает новый объект, а модифицирует переданный, что эффективно с точки зрения производительности.

Визуализация результата

После расчета координат нужно их отрисовать.

this.graphics.clear();
this.graphics.fillRect(this.point.x - 4, this.point.y - 4, this.point.width, this.point.height);

Поскольку update() вызывается каждый кадр, мы сначала очищаем холст graphics от старого кадра с помощью clear(). Затем рисуем зеленый квадрат. Мы используем рассчитанные this.point.x и this.point.y как центр нашего квадрата. Вычитание 4 пикселя (this.point.width / 2) необходимо, чтобы квадрат размером 8x8 был отцентрирован относительно полученной точки на окружности. В результате мы видим плавно движущийся по кругу зеленый маркер.

Практическое применение в играх

Этот паттерн полезен не только для отрисовки точек. Вы можете применить его к любому игровому объекту (Sprite, Image).

// В create()
this.satellite = this.physics.add.image(400 + 150, 300, 'satellite');
this.circle = new Phaser.Geom.Circle(400, 300, 150);
this.angle = 0;

// В update()
this.angle += 0.01;
const position = new Phaser.Geom.Point();
Phaser.Geom.Circle.CircumferencePoint(this.circle, this.angle, position);
this.satellite.setPosition(position.x, position.y);

Таким образом, спрайт satellite будет вращаться вокруг точки (400, 300). Вы можете менять радиус и центр окружности динамически, создавая сложные орбитальные движения, или привязать к этому движению камеру для кругового обзора сцены.

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

Метод Phaser.Geom.Circle.CircumferencePoint — это чистый и производительный способ получить координаты точки на окружности. Он избавляет от ручных тригонометрических расчетов и легко интегрируется в игровую логику. Для экспериментов попробуйте: изменить скорость и направление вращения; привязать к движущейся точке луч или частицу (Emitter); использовать несколько точек для создания вращающихся паттернов атак; комбинировать движение по кругу с другими типами движения для создания сложных траекторий (например, по спирали).