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

Создание плавных траекторий и путей — неотъемлемая часть разработки игр, будь то движение врагов, полёт снаряда или анимация интерфейса. Встроенный в Phaser класс `Phaser.Curves.CubicBezier` позволяет легко описывать такие пути с помощью кубических кривых Безье. Эта статья на практическом примере покажет, как создать кривую, отрисовать её на экране и получить набор точек, равномерно распределённых вдоль неё — ключевой навык для расчёта позиций объектов, следующих по сложным маршрутам.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    create ()
    {
        const graphics = this.add.graphics();

        const p0 = new Phaser.Math.Vector2(100, 500);
        const p1 = new Phaser.Math.Vector2(50, 100);
        const p2 = new Phaser.Math.Vector2(600, 100);
        const p3 = new Phaser.Math.Vector2(700, 500);

        const curve = new Phaser.Curves.CubicBezier(p0, p1, p2, p3);

        graphics.lineStyle(1, 0xffffff, 1);

        curve.draw(graphics, 64);

        //  16 points evenly spaced out across the curve
        const points = curve.getPoints(16);

        graphics.fillStyle(0x00ff00, 1);

        for (let i = 0; i < points.length; i++)
        {
            graphics.fillCircle(points[i].x, points[i].y, 3);
        }

        const pointA = curve.getPoint(0.95);

        graphics.fillStyle(0xff00ff, 1);

        graphics.fillCircle(pointA.x, pointA.y, 3);

    }
}

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

const game = new Phaser.Game(config);

Создание кривой Безье

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

Сначала мы создаём векторные объекты для каждой из четырёх точек. Затем инициализируем саму кривую, передавая эти векторы в конструктор. В данном примере p0 — начальная точка, p3 — конечная, а p1 и p2 — контрольные точки, «притягивающие» кривую.

const p0 = new Phaser.Math.Vector2(100, 500);
const p1 = new Phaser.Math.Vector2(50, 100);
const p2 = new Phaser.Math.Vector2(600, 100);
const p3 = new Phaser.Math.Vector2(700, 500);

const curve = new Phaser.Curves.CubicBezier(p0, p1, p2, p3);

Отрисовка кривой на Graphics

Чтобы визуализировать созданную кривую, мы используем объект Graphics. Сначала задаём стиль линии, а затем вызываем метод draw у самой кривой, передавая в него контекст отрисовки (graphics) и количество отрезков, на которые будет разбита кривая для отображения. Чем больше отрезков, тем плавнее будет выглядеть линия.

const graphics = this.add.graphics();
graphics.lineStyle(1, 0xffffff, 1);
curve.draw(graphics, 64);

Код выше создаёт белый контур кривой, разбитой на 64 сегмента.

Получение равномерно распределённых точек

Одна из самых полезных функций кривой — метод getPoints(divisions). Он возвращает массив точек (Phaser.Math.Vector2), равномерно распределённых по длине кривой. Количество точек равно переданному аргументу divisions плюс одна (начальная точка). Эти точки идеально подходят для размещения объектов вдоль пути или для дискретного вычисления позиции.

const points = curve.getPoints(16);
graphics.fillStyle(0x00ff00, 1);
for (let i = 0; i < points.length; i++) {
    graphics.fillCircle(points[i].x, points[i].y, 3);
}

В примере мы получаем 17 точек (16 сегментов), и на каждой рисуем небольшой зелёный круг.

Получение точки по параметру t

Иногда требуется получить не набор точек, а конкретную позицию на кривой. Для этого используется метод getPoint(t). Параметр `t` — это значение от 0 до 1, где 0 соответствует началу кривой, а 1 — её концу. Это позволяет точно позиционировать объект в любой момент движения по траектории.

const pointA = curve.getPoint(0.95);
graphics.fillStyle(0xff00ff, 1);
graphics.fillCircle(pointA.x, pointA.y, 3);

В данном случае мы получаем точку, расположенную в 95% от начала пути, и отрисовываем её пурпурным цветом.

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

Кривые Безье в Phaser — это мощный и гибкий инструмент для работы с нелинейными путями. Вы можете экспериментировать: попробуйте анимировать спрайт, заставляя его двигаться по кривой, вычисляя его позицию через getPoint(t) в методе update. Или используйте getPoints() для создания патрульного маршрута врага, состоящего из нескольких ключевых точек. Изменяя контрольные точки (p1, p2) в реальном времени, можно создавать динамически меняющиеся траектории для снарядов с эффектом самонаведения.