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

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

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

Живой запуск

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

Исходный код


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

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

        // triangle = new Phaser.Geom.Triangle.BuildEquilateral(400, 200, 300);

        // triangle = new Phaser.Geom.Triangle.BuildRight(200, 400, 300, 200);

        //  Random
        const x1 = Phaser.Math.Between(50, 400);
        const y1 = Phaser.Math.Between(50, 300);

        const x2 = Phaser.Math.Between(450, 750);
        const y2 = Phaser.Math.Between(50, 300);

        const x3 = Phaser.Math.Between(50, 750);
        const y3 = Phaser.Math.Between(350, 550);

        this.triangle = new Phaser.Geom.Triangle(x1, y1, x2, y2, x3, y3);

        this.point = new Phaser.Geom.Rectangle(0, 0, 16, 16);
    }

    update ()
    {
        this.a += 0.005;

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

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

        this.graphics.clear();
        this.graphics.lineStyle(2, 0x00ff00);
        this.graphics.strokeTriangleShape(this.triangle);

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

Создание геометрии: Треугольник и Точка

В методе create() происходит инициализация всех необходимых объектов. Сначала создается объект Graphics для рисования.

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

Затем, в примере используется один из трех вариантов создания треугольника. Комментированные строки показывают, как можно создать равносторонний или прямоугольный треугольник, но активным остается вариант со случайными координатами.

//  Random
const x1 = Phaser.Math.Between(50, 400);
const y1 = Phaser.Math.Between(50, 300);
const x2 = Phaser.Math.Between(450, 750);
const y2 = Phaser.Math.Between(50, 300);
const x3 = Phaser.Math.Between(50, 750);
const y3 = Phaser.Math.Between(350, 550);
this.triangle = new Phaser.Geom.Triangle(x1, y1, x2, y2, x3, y3);

Важно: this.triangle — это объект данных (Phaser.Geom.Triangle), хранящий только координаты вершин. Он не отрисовывается сам по себе.

Также создается объект точки. Для удобства отрисовки и работы с методом getPoint(), точка представлена не простым объектом {x, y}, а прямоугольником Phaser.Geom.Rectangle. Это позволяет хранить не только позицию (x, y), но и размеры (width, height), что пригодится позже для центрирования графики.

this.point = new Phaser.Geom.Rectangle(0, 0, 16, 16);

Магия метода getPoint() и интерполяции

Сердце анимации бьется в методе update(). Ключевую роль играет метод Phaser.Geom.Triangle.getPoint(position, output). Его задача — вычислить точку на периметре треугольника на основе параметра position.

- position (число от 0 до 1): Определяет, где на контуре находится точка. 0 соответствует первой вершине (x1, y1). При увеличении значения точка движется по сторонам треугольника. Значение 1 снова возвращает нас к первой вершине, замыкая круг. - output (объект): Любой объект со свойствами `xиy(например,Phaser.Geom.Rectangle,Phaser.Math.Vector2или обычный{x:0, y:0}`). Метод запишет в него вычисленные координаты.

В коде значение position хранится в переменной this.a. Каждый кадр оно увеличивается на небольшую величину.

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

Затем вызывается сам метод, который модифицирует переданный ему прямоугольник this.point, обновляя его свойства `xиy`.

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

Теперь this.point.x и this.point.y содержат актуальные координаты на стороне треугольника. Изменяя шаг приращения (0.005), вы контролируете скорость движения точки по контуру.

Визуализация: Отрисовка кадра за кадром

После вычисления новой позиции точки, сцена перерисовывается. Первым делом очищается canvas от графики предыдущего кадра.

this.graphics.clear();

Затем задается стиль линии и рисуется контур треугольника. Обратите внимание на использование strokeTriangleShape(), который принимает объект геометрии this.triangle.

this.graphics.lineStyle(2, 0x00ff00);
this.graphics.strokeTriangleShape(this.triangle);

Наконец, отрисовывается движущаяся точка. Поскольку this.point — это прямоугольник с центром в вычисленных координатах, для его отрисовки в виде квадрата мы используем fillRect(). Координаты для отрисовки (this.point.x - 8, this.point.y - 8) смещены на половину ширины и высоты, чтобы квадрат был центрирован относительно линии контура.

this.graphics.fillStyle(0xff00ff);
this.graphics.fillRect(this.point.x - 8, this.point.y - 8, this.point.width, this.point.height);

Этот цикл (вычисление -> очистка -> рисование) повторяется каждый кадр, создавая иллюзию плавного движения.

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

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

// В create()
this.sprite = this.physics.add.sprite(0, 0, 'enemy');

// В update()
this.triangle.getPoint(this.a, this.sprite);

Можно использовать несколько точек с разной скоростью (разным шагом this.a) для создания сложных эффектов. Метод getPoint() работает и с другими геометрическими объектами, например, Phaser.Geom.Line.getPoint() или Phaser.Geom.Circle.getPoint(), открывая путь для движения по любым траекториям.

Важно помнить: метод возвращает точку именно на *периметре* фигуры. Для получения точки внутри треугольника (интерполяции по площади) потребуются другие методы или ручные вычисления.

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

Метод Phaser.Geom.Triangle.getPoint() — это элегантный инструмент для работы с линейной интерполяцией вдоль контура. Он абстрагирует сложные математические вычисления, позволяя сосредоточиться на игровой логике. Для экспериментов попробуйте

  1. Привязать к точке спрайт с физическим телом
  2. Создать массив из нескольких треугольников и заставить объект переходить с одного на другой
  3. Использовать Phaser.Geom.Triangle.GetPoints() для получения сразу нескольких точек на контуре и создать, например, эффект "бегущих огней" по границе зоны