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

В игровой разработке часто требуется перемещать объекты по заданному пути, будь то полёт снаряда, движение камеры или патрулирование врага. Вручную рассчитывать координаты на каждом кадре — неэффективно. Phaser предоставляет геометрический инструмент `Phaser.Geom.Line.getPoint()`, который делает это за вас. Эта статья покажет, как с помощью всего одного метода создавать плавные и предсказуемые перемещения объекта по линии, что идеально подходит для траекторий, прогресс-баров или интерполяции значений.

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

Живой запуск

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

Исходный код


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

    create ()
    {
        this.graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x00ff00 }, fillStyle: { color: 0xff0000 }});

        this.line = new Phaser.Geom.Line(100, 100, 600, 500);
        this.point = new Phaser.Geom.Rectangle(0, 0, 16, 16);
    }

    update ()
    {
        this.a += 0.005;

        if (this.a > 1)
        {
            this.a = 0;
        }

        this.line.getPoint(this.a, this.point);

        this.graphics.clear();
        this.graphics.lineStyle(2, 0x00ff00);
        this.graphics.strokeLineShape(this.line);

        this.graphics.fillStyle(0xff00ff);
        this.graphics.fillRect(this.point.x - 8, this.point.y - 8, 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);

Принцип работы: параметрическое представление линии

Метод getPoint(a, point) использует параметрическое уравнение линии. Параметр `a` — это нормализованное значение (от 0 до 1), где 0 соответствует начальной точке линии, 1 — конечной, а 0.5 — точке ровно посередине.

Метод не создаёт новый объект точки, а записывает вычисленные координаты (x, y) в переданный ему объект. Это эффективно для производительности, так как избегает лишнего создания объектов в цикле update().

// line - объект Phaser.Geom.Line
// point - объект с полями x и y (например, Phaser.Geom.Rectangle)
this.line.getPoint(this.a, this.point);

Разбор примера: от линии до движущейся точки

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

create ()
{
    this.graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x00ff00 }, fillStyle: { color: 0xff0000 }});
    this.line = new Phaser.Geom.Line(100, 100, 600, 500);
    this.point = new Phaser.Geom.Rectangle(0, 0, 16, 16);
}

Интересно, что в качестве точки используется Phaser.Geom.Rectangle. Это возможно, потому что getPoint() ожидает любой объект с полями `xиy, и прямоугольник им соответствует. В цикле обновления мы увеличиваем параметрa` и получаем новую позицию.

update ()
{
    this.a += 0.005;
    if (this.a > 1) { this.a = 0; } // Сброс для зацикленной анимации
    this.line.getPoint(this.a, this.point); // Ключевой вызов
}

Визуализация: рисуем линию и точку

После расчёта позиции необходимо очистить холст и нарисовать всё заново. Phaser.Graphics позволяет это делать с помощью методов clear(), lineStyle() и fillRect().

Обратите внимание, что координаты для отрисовки прямоугольника (fillRect) смещаются на половину его ширины и высоты. Это делается, чтобы центр прямоугольника совпадал с вычисленной точкой на линии.

this.graphics.clear();
this.graphics.lineStyle(2, 0x00ff00);
this.graphics.strokeLineShape(this.line);

this.graphics.fillStyle(0xff00ff);
// Рисуем прямоугольник так, чтобы его центр был в точке (point.x, point.y)
this.graphics.fillRect(this.point.x - 8, this.point.y - 8, this.point.width, this.point.height);

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

1. **Патрулирование:** Задайте линию между двумя точками и перемещайте спрайт врага по ней с помощью getPoint(). Изменив знак приращения `a`, вы заставите врага ходить туда-обратно. 2. **Плавные переходы:** Методом можно интерполировать не только позицию, но и другие значения. Например, цвет объекта от точки A (цвет1) до точки B (цвет2). 3. **Прогресс и таймеры:** Визуализируйте зарядку способности или отсчёт времени, рисуя линию, по которой движется индикатор.

// Пример для патрулирования туда-обратно
update() {
    this.a += this.speed * this.direction; // direction = 1 или -1
    if (this.a >= 1 || this.a <= 0) {
        this.direction *= -1; // Меняем направление
    }
    this.path.getPoint(Phaser.Math.Clamp(this.a, 0, 1), this.sprite);
    this.sprite.x = this.sprite.x; // Используем x из объекта point
    this.sprite.y = this.sprite.y;
}

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

Метод Phaser.Geom.Line.getPoint() — это элегантное и производительное решение для задач, связанных с движением по прямой. Он избавляет от ручных вычислений и легко интегрируется в игровой цикл. Для экспериментов попробуйте: * Привязать к движущейся точке не квадрат, а спрайт игрока или частицу. * Создать массив линий и заставить объект переходить с одной на другую, создавая ломаную траекторию. * Использовать значение `aне линейно, а через easing-функции (Phaser.Math.Easing`) для более сложной скорости движения (ускорение, замедление).