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

Создание сложных и плавных траекторий движения — частый запрос в играх. Это может быть полёт снаряда, патрулирование врага или анимация меню. Вручную рассчитывать такие пути через координаты каждый кадр неэффективно. В Phaser есть встроенный инструмент — `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() };

        //  Path starts at 100x100
        this.path = new Phaser.Curves.Path(50, 500);

        this.path.lineTo(150, 200);

        // cubicBezierTo: function (x, y, control1X, control1Y, control2X, control2Y)
        this.path.cubicBezierTo(400, 500, 200, 100, 400, 100);

        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, lineTo и cubicBezierTo

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

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

Здесь путь начнётся в точке с координатами (50, 500). Далее мы добавляем два сегмента. Первый — прямая линия до точки (150, 200).

this.path.lineTo(150, 200);

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

this.path.cubicBezierTo(400, 500, 200, 100, 400, 100);

В этом вызове: (400, 500) — конечная точка кривой, (200, 100) — первая контрольная точка, (400, 100) — вторая контрольная точка. Путь теперь представляет собой непрерывную ломаную-кривую линию.

Анимируем движение по пути с помощью Tween

Чтобы двигать объект по пути, нам нужен параметр `t, изменяющийся от 0 до 1, где 0 — начало пути, а 1 — его конец. Вместо того чтобы вручную менять этот параметр вupdate`, мы используем систему твинов Phaser.

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

Мы создаём объект-последователь (follower), который хранит текущее положение на пути (`t) и вектор (vec) для расчёта координат. Затем настраиваем твин для плавного изменения свойстваt` этого объекта.

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

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

Визуализация: рисуем путь и объект каждый кадр

Логика отрисовки находится в методе update(), который вызывается на каждом кадре игры. Сначала мы очищаем старое изображение с помощью clear().

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

Затем задаём стиль линии (толщина 2px, белый цвет) и рисуем весь путь методом draw().

this.path.draw(this.graphics);

Теперь нужно получить текущие координаты для нашего последователя. Метод getPoint вычисляет точку на пути для заданного значения `tи записывает результат в переданный векторthis.follower.vec`.

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);

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

Класс Phaser.Curves.Path — мощный инструмент для декларативного описания траекторий. Вы комбинируете простые примитивы (lineTo, cubicBezierTo), а система сама рассчитывает плавное движение. Для экспериментов попробуйте: изменить контрольные точки кривой Безье, добавить больше сегментов lineTo, прицепить спрайт вместо круга или создать несколько независимых путей для группы объектов.