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

Работа с геометрией — фундамент для множества игровых механик: от определения зон поражения до создания динамических границ уровня. В этой статье разберем мощный, но часто упускаемый из виду метод `Phaser.Geom.Rectangle.MergePoints`. Он позволяет автоматически подгонять размеры прямоугольника так, чтобы он вмещал в себя набор произвольных точек. Это избавляет от ручных расчетов и идеально подходит для создания обобщенных хитбоксов, камер, следящих за группой объектов, или динамических областей выделения.

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

Живой запуск

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

Исходный код


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

        const points = [];

        const rect = new Phaser.Geom.Rectangle(350, 250, 100, 100);

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

            points.push(pointer.position.clone());

            redraw();

        });

        redraw();

        function redraw ()
        {
            graphics.clear();

            for (let i = 0; i < points.length; i++)
            {
                const p = points[i];

                graphics.fillCircle(p.x, p.y, 4);
            }

            Phaser.Geom.Rectangle.MergePoints(rect, points);

            graphics.strokeRectShape(rect);
        }
    }
}

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

const game = new Phaser.Game(config);

Зачем нужно MergePoints?

Представьте ситуацию: у вас есть несколько разбросанных по сцене объектов (противников, предметов, контрольных точек). Вам необходимо найти минимальную прямоугольную область, которая охватит их все. Ручной пересчет координат и размеров каждый раз при движении объектов — трудоемко и чревато ошибками.

Метод MergePoints класса Phaser.Geom.Rectangle решает эту задачу. Он принимает исходный прямоугольник и массив точек, а затем изменяет его ширину, высоту и положение так, чтобы результирующий прямоугольник стал минимальным, способным содержать в себе все переданные точки. Исходный прямоугольник модифицируется на месте.

Это особенно полезно для: * Создания общего хитбокса для группы врагов. * Определения границ камеры, которые должны включать всех игроков на арене. * Реализации инструмента выделения нескольких юнитов в стратегиях.

Разбор кода: от нажатий до перерисовки

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

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

const graphics = this.add.graphics({ lineStyle: { width: 2, color: 0x0000aa }, fillStyle: { color: 0x00aa00} });
const points = [];
const rect = new Phaser.Geom.Rectangle(350, 250, 100, 100);

Затем устанавливается обработчик события клика мыши. Каждое нажатие добавляет копию позиции указателя в массив points и вызывает функцию перерисовки.

this.input.on('pointerdown', pointer => {
    points.push(pointer.position.clone());
    redraw();
});

Ключевой момент — использование .clone(). Без этого мы бы сохраняли ссылку на объект позиции мыши, который постоянно обновляется системой ввода. Клонирование создает независимую копию координат на момент клика.

Функция redraw() — сердце примера. Она очищает холст, рисует все точки из массива, применяет MergePoints и отрисовывает итоговый прямоугольник.

function redraw ()
{
    graphics.clear();
    for (let i = 0; i < points.length; i++)
    {
        const p = points[i];
        graphics.fillCircle(p.x, p.y, 4);
    }
    // Вот где происходит магия:
    Phaser.Geom.Rectangle.MergePoints(rect, points);
    graphics.strokeRectShape(rect);
}

Именно вызов Phaser.Geom.Rectangle.MergePoints(rect, points) изменяет свойства rect.x, rect.y, rect.width и rect.height.

Как работает MergePoints изнутри

Метод выполняет последовательность простых, но эффективных шагов. Он не создает новый объект, а модифицирует переданный прямоугольник (rect).

1. Если массив точек пуст, метод завершает работу, не внося изменений. 2. Он проходит по всем точкам в массиве, чтобы найти минимальные и максимальные значения по осям X и Y. 3. На основе найденных минимумов и максимумов вычисляются новые параметры прямоугольника: * rect.x становится равным минимальному найденному X. * rect.y становится равным минимальному найденному Y. * rect.width вычисляется как разница между максимальным и минимальным X. * rect.height вычисляется как разница между максимальным и минимальным Y.

Таким образом, после вызова метода rect гарантированно будет содержать все точки, причем его левый верхний угол будет в точке с минимальными координатами, а размеры будут минимально необходимыми.

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

Давайте модифицируем пример, чтобы сделать его более игровым. Например, будем обрамлять прямоугольником не точки кликов, а спрайты, которые можно перетаскивать.

Создадим несколько спрайтов и добавим им возможность перетаскивания.

// Создаем спрайты и включаем для них инпут
let sprites = this.physics.add.group({
    key: 'item',
    frameQuantity: 5,
    setXY: { x: Phaser.Math.Between(100, 700), y: Phaser.Math.Between(100, 500) }
});
sprites.children.iterate((child) => {
    child.setInteractive();
    this.input.setDraggable(child);
});

Теперь в функции redraw (которую можно вызывать в update или по событию перетаскивания) мы соберем позиции спрайтов в массив и применим MergePoints.

function updateBoundaryRect() {
    // Собираем текущие позиции всех спрайтов
    const spritePoints = [];
    sprites.children.iterate((child) => {
        spritePoints.push(new Phaser.Geom.Point(child.x, child.y));
    });
    // Сбрасываем прямоугольник в первую точку, если нужно, или используем существующий
    Phaser.Geom.Rectangle.MergePoints(boundaryRect, spritePoints);
    graphics.clear();
    graphics.strokeRectShape(boundaryRect);
}

Теперь boundaryRect будет в реальном времени охватывать все перемещаемые спрайты. Это основа для системы группового выделения или расчета общей зоны столкновений.

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

Метод Phaser.Geom.Rectangle.MergePoints — это элегантное и производительное решение для задач, связанных с определением общих границ. Он избавляет разработчика от написания циклов для поиска минимумов и максимумов, инкапсулируя эту логику в один вызов API. Идеи для экспериментов: * **Динамическая камера:** Привяжите свойства Phaser.Cameras.Scene2D.Camera.setBounds к прямоугольнику, обработанному MergePoints для позиций игрока и его спутников. * **Зона агрессии моба:** Определите, находится ли игрок внутри прямоугольника, охватывающего стаю врагов, чтобы триггерить их атаку. * **Оптимизация:** Используйте полученный прямоугольник для грубой проверки столкновений перед точными, но дорогими расчетами с помощью Phaser.Geom.Intersects.RectangleToRectangle.