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

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

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    graphics;
    path;
    follower;

    create ()
    {
        this.graphics = this.add.graphics();

        this.follower = { t: 0, vec: new Phaser.Math.Vector2() };

        this.path = new Phaser.Curves.Path(50, 500);

        this.path.splineTo([ 164, 446, 274, 542, 412, 457, 522, 541, 664, 464 ]);

        this.path.lineTo(700, 300);

        this.path.lineTo(600, 350);

        this.path.ellipseTo(200, 100, 100, 250, false, 0);

        this.path.cubicBezierTo(222, 119, 308, 107, 208, 368);

        this.path.ellipseTo(60, 60, 0, 360, true);

        this.tweens.add({
            targets: this.follower,
            t: 1,
            ease: 'Sine.easeInOut',
            duration: 4000,
            yoyo: true,
            repeat: -1
        });
    }

    update ()
    {
        this.graphics.clear();
        this.graphics.lineStyle(2, 0xffffff, 1);

        this.path.draw(this.graphics);

        this.path.getPoint(this.follower.t, this.follower.vec);

        this.graphics.fillStyle(0xff0000, 1);
        this.graphics.fillCircle(this.follower.vec.x, this.follower.vec.y, 12);
    }
}

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

const game = new Phaser.Game(config);

Подготовка сцены и создание Path

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

create () {
    this.graphics = this.add.graphics();
    this.follower = { t: 0, vec: new Phaser.Math.Vector2() };
    this.path = new Phaser.Curves.Path(50, 500);
}

В этом коде мы создаём объект Graphics для визуализации пути на сцене. Объект follower будет хранить текущую позицию на пути (значение `tот 0 до 1) и вектор для вычисления координат. КонструкторPath` принимает начальные координаты (x, y) — точку, от которой начнётся построение первого сегмента.

Собираем путь из сегментов

Сила Path в возможности комбинирования разных типов кривых. Методы добавления сегментов всегда продолжают путь от конечной точки предыдущего сегмента.

this.path.splineTo([ 164, 446, 274, 542, 412, 457, 522, 541, 664, 464 ]);
this.path.lineTo(700, 300);
this.path.lineTo(600, 350);

Метод splineTo создаёт плавную кривую (сплайн), проходящую через массив точек. Далее lineTo добавляет прямолинейные сегменты до указанных координат.

this.path.ellipseTo(200, 100, 100, 250, false, 0);
this.path.cubicBezierTo(222, 119, 308, 107, 208, 368);
this.path.ellipseTo(60, 60, 0, 360, true);

ellipseTo рисует дугу эллипса. Первые два параметра — радиус по X и Y. Следующие два — начальный и конечный угол в градусах. Параметр clockwise задаёт направление обхода. cubicBezierTo добавляет кубическую кривую Безье, определяемую двумя контрольными точками и конечной точкой.

Анимируем движение по пути

Для перемещения объекта по пути используется параметр `t` (нормализованное расстояние от 0 до 1). Его анимацию удобно поручить встроенному твинеру Phaser.

this.tweens.add({
    targets: this.follower,
    t: 1,
    ease: 'Sine.easeInOut',
    duration: 4000,
    yoyo: true,
    repeat: -1
});

Этот твин плавно изменяет свойство `tобъектаfollowerот 0 до 1 за 4 секунды с плавным ускорением и замедлением (Sine.easeInOut). Опцииyoyo: trueиrepeat: -1` заставляют объект ходить по пути туда и обратно бесконечно.

Визуализация и получение координат

В методе update мы выполняем две ключевые операции: рисуем сам путь и вычисляем текущие координаты нашего "последователя" (follower).

update () {
    this.graphics.clear();
    this.graphics.lineStyle(2, 0xffffff, 1);
    this.path.draw(this.graphics);

    this.path.getPoint(this.follower.t, this.follower.vec);

    this.graphics.fillStyle(0xff0000, 1);
    this.graphics.fillCircle(this.follower.vec.x, this.follower.vec.y, 12);
}

Сначала очищаем холст Graphics и задаём стиль линии. Метод path.draw(graphics) отрисовывает весь составной путь. Затем ключевой метод path.getPoint(t, vector) вычисляет координаты точки на пути для заданного значения `tи записывает их в переданный векторthis.follower.vec`. Эти координаты мы используем, чтобы нарисовать красный кружок, символизирующий движущийся объект.

Практическое применение: враги и кат-сцены

В реальном проекте вместо рисования кружка вы будете обновлять позицию спрайта.

// В create()
this.sprite = this.physics.add.image(50, 500, 'enemy');

// В update()
this.path.getPoint(this.follower.t, this.follower.vec);
this.sprite.setPosition(this.follower.vec.x, this.follower.vec.y);

Такой подход идеален для: * **Патрулирования врагов:** Задайте замкнутый путь для стража. * **Траекторий снарядов:** Создайте дугу для метательного оружия. * **Движения камеры:** Для плавных пролётов в кат-сценах анимируйте позицию камеры, используя точку на пути. * **Сложных анимаций UI:** Движение иконок, бонусов по заданной траектории.

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

Phaser.Curves.Path — это мощный и гибкий конструктор для создания сложных траекторий из простых сегментов. Вы можете проектировать маршруты любой формы, комбинируя примитивы. Для экспериментов попробуйте: создать замкнутый путь для патруля, изменить параметры эллипса и Безье для получения новых форм, привязать к пути не спрайт, а саму камеру для кинематографичных переходов или управлять значением `t` не твинером, а вводом с клавиатуры для ручного контроля движения.