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

В игровой разработке часто возникает задача определить область, охватывающую набор объектов или точек. Например, для создания камеры, следящей за группой юнитов, или для проверки групповых столкновений. Вручную рассчитывать такой прямоугольник — утомительно. К счастью, Phaser предоставляет для этого удобный математический метод. В этой статье мы разберем пример использования `Phaser.Math.GetVec2Bounds`, который автоматически вычисляет минимальный прямоугольник (AABB — axis-aligned bounding box), содержащий все заданные векторы. Мы увидим, как это работает интерактивно, и как можно применить этот подход в реальных игровых механиках.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    create ()
    {
        const graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x2266aa }, fillStyle: { color: 0x2266aa } });

        const points = [
            new Phaser.Math.Vector2(Math.random() * 400 + 200, Math.random() * 300 + 150),
            new Phaser.Math.Vector2(Math.random() * 400 + 200, Math.random() * 300 + 150),
            new Phaser.Math.Vector2(Math.random() * 400 + 200, Math.random() * 300 + 150)
        ];

        this.input.on('pointermove', pointer =>
        {

            points[0].copy(pointer);

            redraw();
        });

        this.input.on('pointerdown', pointer =>
        {

            points.push(new Phaser.Math.Vector2(pointer.x, pointer.y));

            redraw();
        });

        redraw();

        function redraw ()
        {
            graphics.clear();

            const rect = Phaser.Math.GetVec2Bounds(points);

            graphics.strokeRectShape(rect);

            for (let i = 0; i < points.length; i++)
            {
                graphics.fillPointShape(points[i], 5);
            }
        }
    }
}

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

const game = new Phaser.Game(config);

Суть метода GetVec2Bounds

Метод Phaser.Math.GetVec2Bounds принимает массив объектов типа Phaser.Math.Vector2 и возвращает объект типа Phaser.Geom.Rectangle. Этот прямоугольник является выровненным по осям (его стороны параллельны осям X и Y) и имеет минимально возможные размеры, чтобы вместить все переданные точки.

Это крайне полезно для быстрого определения границ любого набора позиций в игровом мире. Внутри метода происходит поиск минимальных и максимальных значений по осям X и Y среди всех векторов.

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

В примере создается массив points, изначально содержащий три случайные точки. Затем на события указателя мыши добавляется интерактивность.

При движении указателя (pointermove) первая точка массива перезаписывается текущими координатами курсора с помощью метода copy().

При клике (pointerdown) в массив добавляется новая точка в позиции клика.

После каждого изменения вызывается функция redraw(), которая перерисовывает сцену.

const points = [
    new Phaser.Math.Vector2(Math.random() * 400 + 200, Math.random() * 300 + 150),
    new Phaser.Math.Vector2(Math.random() * 400 + 200, Math.random() * 300 + 150),
    new Phaser.Math.Vector2(Math.random() * 400 + 200, Math.random() * 300 + 150)
];

this.input.on('pointermove', pointer => {
    points[0].copy(pointer);
    redraw();
});

this.input.on('pointerdown', pointer => {
    points.push(new Phaser.Math.Vector2(pointer.x, pointer.y));
    redraw();
});

Вычисление прямоугольника и отрисовка

Ядро логики находится в функции redraw(). Сначала очищается графика graphics.clear(). Затем для текущего массива точек вычисляется ограничивающий прямоугольник.

Полученный объект rect передается в метод graphics.strokeRectShape() для отрисовки его контура.

После этого в цикле отрисовываются сами точки с помощью graphics.fillPointShape(), чтобы визуализировать исходные данные.

function redraw ()
{
    graphics.clear();

    const rect = Phaser.Math.GetVec2Bounds(points);

    graphics.strokeRectShape(rect);

    for (let i = 0; i < points.length; i++)
    {
        graphics.fillPointShape(points[i], 5);
    }
}

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

1. **Интеллектуальная камера:** Рассчитав прямоугольник, охватывающий всех игроков в кооперативной игре или ключевые объекты уровня, вы легко можете задать положение и масштаб камеры (this.cameras.main), чтобы все поместилось в поле зрения. 2. **Оптимизация физики:** Вместо проверки столкновений каждого объекта в группе с целью, можно сначала проверить столкновение ограничивающих прямоугольников групп. Если прямоугольники не пересекаются, то и ни один объект из групп не сталкивается, что экономит ресурсы. 3. **Выделение области (Drag-select):** В стратегиях, когда игрок выделяет юнитов рамкой, полученные координаты углов рамки можно использовать для быстрого проверки, какие юниты (их точки позиции) находятся внутри рассчитанного прямоугольника.

Метод работает именно с векторами (Vector2), поэтому если у вас есть спрайты, сначала получите их мировые координаты через sprite.x и sprite.y.

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

Метод Phaser.Math.GetVec2Bounds — это небольшой, но мощный инструмент для работы с пространственными данными в Phaser. Он избавляет от необходимости писать циклы для поиска min/max значений вручную. **Для экспериментов попробуйте:** - Связать точки не случайным образом, а позициями спрайтов из группы (Phaser.GameObjects.Group). - Использовать вычисленный прямоугольник для динамического изменения зума камеры так, чтобы он всегда вмещал все точки с небольшим отступом. - Реализовать систему подсветки области при наведении, используя прямоугольник от GetVec2Bounds как основу для графики.