О чем этот пример
Работа с геометрией — фундамент для множества игровых механик: от определения зон поражения до создания динамических границ уровня. В этой статье разберем мощный, но часто упускаемый из виду метод `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.
