О чем этот пример
Работа с геометрией — частый этап в разработке игр: проверка столкновений, расчёт зон видимости или позиционирование интерфейса. В Phaser для этого есть мощный модуль `Phaser.Geom`. В этой статье разберём метод `Rectangle.FitOutside()`, который позволяет подогнать один прямоугольник так, чтобы он полностью вмещался в другой, сохраняя исходные пропорции. Это особенно полезно для создания динамических камер, обработки ресайза игры или размещения элементов UI с соблюдением соотношения сторон.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
create ()
{
const graphics = this.add.graphics({ fillStyle: { color: 0x0000aa }, lineStyle: { color: 0xaa0000 } });
const rectangles = [];
for (let x = 0; x < 4; x++)
{
rectangles[x] = [];
for (let y = 0; y < 3; y++)
{
const width = Phaser.Math.Between(50, 100);
const height = Phaser.Math.Between(50, 100);
rectangles[x][y] = new Phaser.Geom.Rectangle(0, 0, width, height);
Phaser.Geom.Rectangle.CenterOn(rectangles[x][y], x * 200, y * 200);
}
}
const rect = new Phaser.Geom.Rectangle(0, 0, 150, 100);
this.input.on('pointermove', pointer =>
{
const x = Math.floor(pointer.x / 200);
const y = Math.floor(pointer.y / 200);
Phaser.Geom.Rectangle.FitOutside(rect, rectangles[x][y]);
redraw();
});
redraw();
function redraw ()
{
graphics.clear();
graphics.fillRectShape(rect);
for (let x = 0; x < 4; x++)
{
for (let y = 0; y < 3; y++)
{
graphics.strokeRectShape(rectangles[x][y]);
}
}
}
}
}
const config = {
width: 800,
height: 600,
type: Phaser.AUTO,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
Что делает Rectangle.FitOutside?
Статический метод Phaser.Geom.Rectangle.FitOutside(target, source) изменяет размер и положение прямоугольника target так, чтобы он полностью вмещал прямоугольник source, но при этом сам target помещался внутрь другого прямоугольника (в примере это ячейка сетки). Ключевой момент: пропорции target сохраняются.
Представьте, что у вас есть картинка (source), которую нужно вписать в ограничивающую область (target), но так, чтобы вся картинка была видна, а сама область не выходила за пределы какой-то внешней рамки. Именно эту задачу и решает FitOutside.
В исходном примере мы имеем:
- Сетку из 12 прямоугольников разного размера (rectangles[x][y]).
- Один подвижный прямоугольник rect фиксированного размера 150x100.
При движении курсора rect подгоняется так, чтобы вместить в себя прямоугольник из текущей ячейки сетки, но сам rect не меняет своих пропорций.
Разбор примера: создание сцены и сетки
Сначала в методе create() создаётся объект graphics для отрисовки. Затем формируется двумерный массив rectangles.
const graphics = this.add.graphics({ fillStyle: { color: 0x0000aa }, lineStyle: { color: 0xaa0000 } });
const rectangles = [];
В двойном цикле для каждой ячейки сетки генерируется прямоугольник со случайными шириной и высотой от 50 до 100 пикселей. После создания он центрируется относительно координат ячейки сетки (шаг 200 пикселей).
for (let x = 0; x < 4; x++) {
rectangles[x] = [];
for (let y = 0; y < 3; y++) {
const width = Phaser.Math.Between(50, 100);
const height = Phaser.Math.Between(50, 100);
rectangles[x][y] = new Phaser.Geom.Rectangle(0, 0, width, height);
Phaser.Geom.Rectangle.CenterOn(rectangles[x][y], x * 200, y * 200);
}
}
Также создаётся целевой прямоугольник rect, который будет изменяться.
const rect = new Phaser.Geom.Rectangle(0, 0, 150, 100);
Обработка движения курсора и применение FitOutside
На событие pointermove вешается обработчик. Он вычисляет индексы текущей ячейки сетки на основе координат курсора.
this.input.on('pointermove', pointer => {
const x = Math.floor(pointer.x / 200);
const y = Math.floor(pointer.y / 200);
Phaser.Geom.Rectangle.FitOutside(rect, rectangles[x][y]);
redraw();
});
Здесь происходит самое важное: вызов Phaser.Geom.Rectangle.FitOutside(rect, rectangles[x][y]). Прямоугольник rect (150x100) трансформируется так, чтобы он мог полностью вместить в себя случайный прямоугольник из текущей ячейки (rectangles[x][y]). При этом rect сохраняет свои исходные пропорции (соотношение 1.5:1). После изменения вызывается функция redraw() для перерисовки графики.
Визуализация: функция redraw
Функция redraw очищает предыдущий кадр, заново рисует заполненным синим цветом изменённый прямоугольник rect и обводит контуром все прямоугольники сетки.
function redraw() {
graphics.clear();
graphics.fillRectShape(rect);
for (let x = 0; x < 4; x++) {
for (let y = 0; y < 3; y++) {
graphics.strokeRectShape(rectangles[x][y]);
}
}
}
Методы fillRectShape и strokeRectShape принимают объекты типа Phaser.Geom.Rectangle. Это демонстрирует удобство работы: геометрические объекты можно сразу передавать в графику для отрисовки.
Конфигурация и запуск игры
Стандартная конфигурация Phaser Game. Обратите внимание, что в scene передаётся класс нашей сцены Example.
const config = {
width: 800,
height: 600,
type: Phaser.AUTO,
parent: 'phaser-example',
scene: Example
};
const game = new Phaser.Game(config);
После создания экземпляра игры автоматически вызывается метод create() сцены Example, инициируя весь описанный выше процесс.
Что попробовать дальше
Метод Phaser.Geom.Rectangle.FitOutside — это мощный и простой инструмент для решения задач позиционирования с сохранением пропорций. В примере мы увидели его применение для динамического изменения прямоугольника относительно сетки. На практике его можно использовать для:
- Подгонки области просмотра камеры под группу объектов.
- Создания responsive UI-элементов, которые должны вмещать контент, но не выходить за границы экрана.
- Реализации логики "миникарты", которая показывает всю игровую область, оставаясь в своём углу.
Попробуйте изменить начальные размеры rect или использовать FitOutside в связке с другими методами геометрии, например, Phaser.Geom.Rectangle.Union, для более сложных расчётов областей.
