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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    angle;

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

        const ellipse = new Phaser.Geom.Ellipse(400, 300, 250, 150);

        let circumferencePoint = new Phaser.Math.Vector2(275, 300);
        const centerPoint = new Phaser.Math.Vector2(400, 300);

        const line = new Phaser.Geom.Line(400, 300, 275, 300);

        const text1 = this.add.text(20, 50, 'Circumference Point:');
        const text2 = this.add.text(20, 75, 'Angle:');

        this.input.on('pointermove', pointer =>
        {

            this.angle = Phaser.Math.Angle.Between(400, 300, pointer.x, pointer.y);

            circumferencePoint = Phaser.Geom.Ellipse.CircumferencePoint(ellipse, this.angle);

            line.x2 = circumferencePoint.x;
            line.y2 = circumferencePoint.y;

            text1.setText(`Circumference Point: (${circumferencePoint.x}, ${circumferencePoint.y})`);
            text2.setText(`Angle: ${this.angle}`);

            graphics.fillPointShape(circumferencePoint, 20);

            draw();
        });

        draw();

        function draw ()
        {
            graphics.clear();
            graphics.lineStyle(2, 0x00aaaa);
            graphics.strokeEllipseShape(ellipse);
            graphics.strokeLineShape(line);
            graphics.fillPointShape(centerPoint, 10);
            graphics.fillPointShape(circumferencePoint, 20);
        }
    }
}

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

const game = new Phaser.Game(config);

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

В начале создается графика для отрисовки, определяется эллипс и несколько ключевых точек. Векторы используются для удобной работы с координатами.

const graphics = this.add.graphics({ fillStyle: { color: 0x2266aa } });
const ellipse = new Phaser.Geom.Ellipse(400, 300, 250, 150);
let circumferencePoint = new Phaser.Math.Vector2(275, 300);
const centerPoint = new Phaser.Math.Vector2(400, 300);
const line = new Phaser.Geom.Line(400, 300, 275, 300);
const text1 = this.add.text(20, 50, 'Circumference Point:');
const text2 = this.add.text(20, 75, 'Angle:');

Эллипс задается центром (400, 300) и радиусами (250 по X, 150 по Y). Линия и начальная точка circumferencePoint визуализируют связь между центром и точкой на контуре. Текстовые объекты будут выводить актуальные данные.

Обработка движения указателя и расчет точки

Сердце примера — обработчик события pointermove. При каждом движении мыши вычисляется угол между центром эллипса и курсором, а затем находится соответствующая точка на контуре фигуры.

this.input.on('pointermove', pointer => {
    this.angle = Phaser.Math.Angle.Between(400, 300, pointer.x, pointer.y);
    circumferencePoint = Phaser.Geom.Ellipse.CircumferencePoint(ellipse, this.angle);
    line.x2 = circumferencePoint.x;
    line.y2 = circumferencePoint.y;
    text1.setText(`Circumference Point: (${circumferencePoint.x}, ${circumferencePoint.y})`);
    text2.setText(`Angle: ${this.angle}`);
    graphics.fillPointShape(circumferencePoint, 20);
    draw();
});

Phaser.Math.Angle.Between возвращает угол в радианах. Этот угол передается в Phaser.Geom.Ellipse.CircumferencePoint, который и выполняет главную работу: рассчитывает координаты на эллипсе, учитывая его растяжение по осям. После этого обновляется линия, текст и вызывается функция перерисовки.

Функция отрисовки графики

Функция draw отвечает за визуализацию всех элементов на холсте. Важно очищать графику перед каждой отрисовкой, чтобы избежать наложения.

function draw ()
{
    graphics.clear();
    graphics.lineStyle(2, 0x00aaaa);
    graphics.strokeEllipseShape(ellipse);
    graphics.strokeLineShape(line);
    graphics.fillPointShape(centerPoint, 10);
    graphics.fillPointShape(circumferencePoint, 20);
}

Сначала graphics.clear() удаляет все, что было нарисовано ранее. Затем задается стиль линии, и отрисовываются контур эллипса, соединительная линия, а также две точки: центр (меньшего размера) и текущая точка на контуре (большего размера).

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

Этот метод выходит за рамки простой демонстрации. Его можно использовать для: * **Орбитального движения:** Поместите спрайт в точку circumferencePoint и изменяйте угол в update для плавного вращения по эллипсу. * **Траектории снаряда:** Рассчитайте несколько точек по дуге для отрисовки предсказания полета. * **Генерации патрульных путей:** Задайте эллипс как зону патрулирования для NPC.

// Пример: перемещение спрайта по орбите
function update(time, delta) {
    this.angle += 0.01; // Медленно увеличиваем угол
    const orbitPoint = Phaser.Geom.Ellipse.CircumferencePoint(ellipse, this.angle);
    this.playerSprite.setPosition(orbitPoint.x, orbitPoint.y);
}

Ключевое отличие от работы с окружностью — учет разных радиусов эллипса, что делает движение визуально более интересным и естественным для вытянутых форм.

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

Метод CircumferencePoint — это мощный и точный инструмент для привязки объектов и вычислений к контуру эллипса. Он избавляет разработчика от необходимости реализации сложной тригонометрической формулы. Для экспериментов попробуйте анимировать радиусы эллипса во время движения точки, создать несколько объектов, движущихся по одной орбите с разной скоростью, или использовать эту технику для определения точки столкновения снаряда с elliptical hitbox.