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

При работе с путями и кривыми в Phaser разработчики часто используют методы `getPoints` и `getSpacedPoints`. На первый взгляд они делают одно и то же — возвращают массив точек. Однако разница в их работе фундаментальна и влияет на то, как объекты будут распределены по пути. Понимание этой разницы критично для создания плавного движения, равномерного размещения преград или генерации траекторий. В этой статье мы наглядно разберем пример из официальной документации и объясним, когда использовать каждый метод.

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

Живой запуск

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

Исходный код


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

        const path = new Phaser.Curves.Path(100, 100);

        path.lineTo(500, 200);
        path.lineTo(200, 300);
        path.lineTo(400, 500);

        const spacedPoints = path.getSpacedPoints(4);
        const points = path.getPoints(4);

        let showPoints = true;

        const text = this.add.text(10, 10);

        const togglePoints = () => {

            graphics.clear();

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

            path.draw(graphics, 64);

            if (showPoints)
            {
                graphics.fillStyle(0xffff00, 1);

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

                text.setText('Click to toggle\nType: Path.getPoints(4)');
            }
            else
            {
                graphics.fillStyle(0x00ff00, 1);

                for (var i = 0; i < spacedPoints.length; i++)
                {
                    graphics.fillCircle(spacedPoints[i].x, spacedPoints[i].y, 4);
                }

                text.setText('Click to toggle\nType: Path.getSpacedPoints(4)');
            }

            showPoints = !showPoints;

        };

        this.input.on('pointerdown', togglePoints);

        togglePoints();
    }

}

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

const game = new Phaser.Game(config);

Создание пути и подготовка данных

В примере создается простая сцена, где мы рисуем ломаную линию (Path) и изучаем точки, которые генерируют два разных метода.

Сначала инициализируется объект Graphics для отрисовки и создается путь, начинающийся в точке (100, 100). Метод lineTo последовательно добавляет в путь отрезки прямой.

const graphics = this.add.graphics();
const path = new Phaser.Curves.Path(100, 100);
path.lineTo(500, 200);
path.lineTo(200, 300);
path.lineTo(400, 500);

Затем вызываются оба ключевых метода с аргументом 4. Результаты сохраняются в разные массивы.

const spacedPoints = path.getSpacedPoints(4);
const points = path.getPoints(4);

На этом этапе у нас есть два массива точек, но их содержимое и логика формирования — разные.

Логика getPoints: точки на сегментах

Метод path.getPoints(division) работает по принципу деления каждого сегмента пути. Аргумент division определяет, на сколько частей делится КАЖДЫЙ сегмент (отрезок) пути.

В нашем примере путь состоит из трех сегментов: (100,100)-(500,200), (500,200)-(200,300), (200,300)-(400,500). При вызове getPoints(4) каждый из этих трех сегментов будет разделен на 4 части. Это означает, что на каждом отрезке мы получим 5 точек (начало + 3 внутренние точки + конец).

// Логика getPoints для одного сегмента:
// Сегмент A---B. division=4.
// Получаем точки: A, A + 1/4*(B-A), A + 2/4*(B-A), A + 3/4*(B-A), B.

Таким образом, общее количество точек в массиве points будет равно (количество сегментов * division) + 1. В примере это (3 * 4) + 1 = 13 точек. На визуализации они отображаются желтым цветом и распределены равномерно по длине каждого отдельного сегмента, но расстояние между точками на разных сегментах может отличаться, если сегменты разной длины.

Логика getSpacedPoints: точки по всей длине пути

Метод path.getSpacedPoints(division) имеет другую цель — он пытается расположить точки на РАВНОМ расстоянии друг от друга по ВСЕЙ длине пути, а не по отдельным сегментам.

Аргумент division здесь означает общее количество шагов (интервалов) вдоль всей объединенной длины пути. Количество возвращаемых точек будет равно division + 1.

// Логика getSpacedPoints для всего пути:
// Общая длина пути = L. division=4.
// Шаг = L / 4.
// Получаем точки на расстояниях: 0, L/4, 2L/4, 3L/4, L от начала пути.

В нашем примере getSpacedPoints(4) вернет 5 точек (4 + 1). Эти точки, отображаемые зеленым цветом, будут расположены так, чтобы расстояния между ними вдоль всей ломаной линии были примерно одинаковыми. Это означает, что на более длинных сегментах точек может быть условно "больше", а на коротких — "меньше", но физически точки просто равномерно распределены по общей длине.

Визуализация и переключение

Пример предоставляет интерактивную визуализацию. По клику мыши (pointerdown) происходит переключение между отображением двух наборов точек с помощью функции togglePoints.

Функция сначала очищает холст graphics.clear(), задает стиль линии и рисует сам путь с высоким разрешением (64 точки).

graphics.lineStyle(1, 0xffffff, 1);
path.draw(graphics, 64);

Затем, в зависимости от флага showPoints, она отрисовывает либо желтые точки из points (результат getPoints), либо зеленые точки из spacedPoints (результат getSpacedPoints). Текст в углу экрана также меняется, чтобы подписать, какой метод сейчас отображен.

if (showPoints) {
    graphics.fillStyle(0xffff00, 1);
    for (var i = 0; i < points.length; i++) {
        graphics.fillCircle(points[i].x, points[i].y, 4);
    }
    text.setText('Click to toggle\nType: Path.getPoints(4)');
} else {
    graphics.fillStyle(0x00ff00, 1);
    for (var i = 0; i < spacedPoints.length; i++) {
        graphics.fillCircle(spacedPoints[i].x, spacedPoints[i].y, 4);
    }
    text.setText('Click to toggle\nType: Path.getSpacedPoints(4)');
}

Инициализирующий вызов togglePoints() в create() запускает первичную отрисовку.

Практические отличия и сценарии использования

Выбор между методами зависит от задачи.

Используйте getPoints(division), когда вам важна структура пути и вам нужно равномерно обработать каждый его сегмент независимо. Например: * Размещение одинакового количества декораций на каждом прямолинейном участке дороги. * Сэмплирование данных (например, высоты) с фиксированным шагом по каждому сегменту траектории.

// Для анимации с фиксированным шагом на каждом сегменте
const segmentPoints = path.getPoints(10);

Используйте getSpacedPoints(division), когда вам важна равномерность распределения по всей длине пути. Например: * Плавное перемещение объекта с постоянной скоростью вдоль всего сложного пути. * Равномерное размещение объектов (врагов, монет) вдоль извилистой тропы.

// Для плавного движения объекта
const uniformPoints = path.getSpacedPoints(50);
// Объект будет перемещаться между этими точками с равными временными интервалами

Ключевой вывод: getPoints управляет детализацией сегментов, getSpacedPoints управляет плотностью точек на единицу длины пути.

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

Методы getPoints и getSpacedPoints в Phaser.Curves.Path решают разные задачи. Первый равномерно делит каждый сегмент пути, второй — равномерно распределяет точки по общей длине. Для экспериментов попробуйте: изменить количество сегментов в пути, вызвать методы с разным значением division и понаблюдать за распределением точек, а также применить оба метода для анимации спрайта через followPath и сравнить smoothness движения.