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

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